From c7106703510fc036e6ad811cddd5aaaff23f5a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=84=B1=ED=98=B8=20saintho?= Date: Fri, 23 Dec 2022 16:11:40 +0900 Subject: [PATCH] update (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Upgrade to Reactor 2020.0.25 See gh-29464 * Polish AOT ref docs * Update testing chapter regarding Servlet 6.0 baseline for mocks * Document TestSocketUtils in the testing chapter * Fix Javadoc formatting issue in TestSocketUtils * Revert "Ignore HttpComponents Javadoc" This reverts commit f50b472ceb6fc2fa25675672bdd0ca422ed8000f. HttpComponents Javadoc is available again. See gh-29479 See https://issues.apache.org/jira/browse/HTTPCLIENT-2227 * Fix section formatting in the testing chapter * Document AOT support in the TestContext framework Closes gh-29482 * Assert fixed in DefaultErrorResponseBuilder Fixed assert on wrong constructor fields * Polish * Align javadoc of DefaultParameterNameDiscoverer with its behavior * Polishing deprecated methods Added since and forRemoval to Deprecated methods. * Add milestone repo for optional Netty 5 support Closes gh-29498 * Next development version (v6.0.1-SNAPSHOT) * Suppress "removal" warnings in CronSequenceGeneratorTests * Polishing * Fix Javadoc formatting issues for headings Headings in method-level Javadoc must be h4 or higher in recent versions of Java. * Update Jackson-based decoders to reflect 2.14 baseline See gh-29508 * Refactor Asciidoctor attributes in reference docs This commit reorganizes the asciidoctor attributes for the reference documentation. Instead of being contributed partially by the build and individual documents, attributes are now shared in common files that are included in top sections. * Polishing * Add callouts to Kotlin examples for parity * Add missing callouts * Work around code example callout bug This commit adds callouts to two sets of Java/Kotlin code examples in the @ModelAttribute section for Web MVC in order to work around the "Code example has callout from a different code example" bug. This also aligns the Web MVC examples with the WebFlux examples. As a side note, the bug can also be seen in the WebFlux documentation if the callouts are removed from the first Java/Kotlin examples in the @ModelAttribute section for WebFlux. Thus it appears to be a general issue related to examples within a given section of the documentation when some examples have callouts and others do not, likely due to a bug in the Javascript that provides this feature. See gh-29505 * Polishing * Generalize Jackson version numbers This commit removes specific version info from Jackson codecs and converters, in favor of generic info or removing the version information all together. See gh-29508 * Use lambda expression for Callable example in ref docs * Document GraalVM substitutions upcoming removal * Update documentation to mention Java 17+ baseline Closes gh-29514 * Fix link to WebFlux section Closes gh-29513 * Set error status in Observation Servlet filter Prior to this commit, the Observation Servlet filter would record unhandled exceptions on the observation context but would leave the default HTTP response status as is. Servlet containers do set the response status in that case to 500 by default. Not doing that at the Servlet filter level results in invalid observations, stating that the HTTP response status is 200 (because the error status hasn't been set yet by the container) and as a result, the outcome is SUCCESS. This commit ensures that the error status is set in those cases, aligning the behavior with Servlet containers. Fixes gh-29512 * Polish AbstractAutowireCapableBeanFactory and use instanceof pattern matching Closes gh-29499 * Polish contribution * Polishing * Polish asciidoc attributes * Reorganize and modularize the Testing chapter in the reference manual Closes gh-29522 * Document RuntimeHints testing strategies Closes gh-29523 * Disable checkstyle for reference docs code snippets * Fix broken link to Web MVC Testing section * Remove TODOs in WebFlux ref docs * Polishing * Fix link to WebFlux section in reference manual Closes gh-29525 * Introduce appendix in Testing chapter in the reference manual This commit moves the Annotations and Further Resources sections to the new Appendix. See gh-29522 * Reinstate `chapter` asciidoc attribute for Web MVC * Polish asciidoc attributes * Ensure source code in framework-docs is compiled in the build This also ensures that the source code can be properly imported into an IDE. * Polish ref docs - stop referring to Java Config as new - stop referring to Struts 2.x as if it were new - polish AOT documentation - etc. * Polish ref docs * Ensure code listing callouts are displayed incorrectly in core-beans.adoc Closes gh-29457 * Fix a syntax error in an XML listing by adding a missing double-quote Closes gh-29456 * Update LogAdapter to allow build-time code removal Allow for example to remove those classes and 90 related methods when Logback is used: - org.apache.commons.logging.LogAdapter$JavaUtilAdapter - org.apache.commons.logging.LogAdapter$JavaUtilLog - org.apache.commons.logging.LogAdapter$LocationResolvingLogRecord - org.apache.commons.logging.LogAdapter$Log4jAdapter - org.apache.commons.logging.LogAdapter$Log4jLog - org.apache.commons.logging.LogAdapter$LogApi - org.apache.logging.log4j.message.ObjectMessage - org.apache.logging.log4j.message.ReusableObjectMessage - org.apache.logging.log4j.simple.SimpleLoggerContext - org.apache.logging.log4j.simple.SimpleLoggerContextFactory Closes gh-29506 * Do not use LocalVariableTableParameterNameDiscoverer in AOT mode Closes gh-29531 * Revert "Ensure source code in framework-docs is compiled in the build" This reverts commit c45f8b7072ab0846af9bbe0080d807d38b1a40ef. * Make GeneratorStrategy.generate unreachable on native This change provides also more information to the user about the missing generated class when that happens. Closes gh-29521 * Apply 'instanceof pattern matching' in spring-web Closes gh-29530 * Polish contribution See gh-29530 * Apply additional 'instanceof pattern matching' in spring-web See gh-29530 * Use Set.of() for constant sets where appropriate * Fix link to Bean Utils Light Library in BeanUtils Javadoc The URL for the BULL library has changed (not sure when, probably way back). This updates it to the correct location. Closes gh-29534 * Make SourceHttpMessageConverter optional As a follow-up to gh-29277, and since the JAXB support is now triggered by the classpath presence of a JAXB implementation, it makes sense to make SourceHttpMessageConverter, previously configured unconditionally, optional. That makes a big difference on native (1M of RSS reduction with current typical Spring Boot 3 arrangement, 3.4M when other usages of XML are not reachable). It also brings more consistency between Spring MVC and Spring WebFlux, and means that XML support for Spring web applications now needs to be enabled explicitly. As a consequence, Spring web applications using javax.xml.transform.Source now needs to configure SourceHttpMessageConverter explicitly in RestTemplate or Spring MVC. Closes gh-29535 * Upgrade to Jackson 2.14.1 Fix an important memory consumption regression, see https://github.com/FasterXML/jackson-databind/issues/3665 for more details. Closes gh-29539 * Fix some typos in Kotlin WebClient example code Closes gh-29538 * Upgrade to Kotlin 1.7.21 Closes gh-29543 * Refine LogAdapter#isPresent Align LogAdapter#isPresent with ClassUtils#isPresent in order to catch NoClassDefFoundError and other errors. Closes gh-29506 * Fix Javadoc link text in BindingResult Closes gh-29551 * Polishing * Polish ServletWebRequest and DefaultServerWebExchange - The return values of ServletWebRequest.validateIfUnmodifiedSince and DefaultServerWebExchange.validateIfUnmodifiedSince are not used. So I think that it is better to remove the return statements. - Add missing @Nullable declarations to eTag method parameters. - Simplify if statements Closes gh-29460 * Polish contribution See gh-29460 * Apply 'instanceof pattern matching' * Exclude LocalVariableTableParameterNameDiscoverer based on native image check See gh-29531 * Apply 'instanceof pattern matching' * Polishing * Use AssertJ's hasSize() for collections and maps Achieved via a global search-and-replace. * User AssertJ's hasSize() for arrays Achieved via global search-and-replace. * Use AssertJ's isEmpty() instead of hasSize(0) Achieved via global search-and-replace. * Temporarily re-enable ReactorNetty2StompBrokerRelayIntegrationTests To see if it still fails on the CI server as it doesn't fail locally for me, and if it does to get details to investigate. See gh-29287 * Deprecate LocalVariableTableParameterNameDiscoverer completely LocalVariableTableParameterNameDiscoverer is not registered by default anymore now. Java sources should be compiled with `-parameters` instead (available since Java 8). Also retaining standard Java parameter names for all of Spring's Kotlin sources now. Closes gh-29531 * Add since attribute to Deprecated annotation Also retaining standard Java parameter names for Spring's AspectJ sources now. See gh-29531 * Increase logging for spring-messaging tests See gh-29287 * Fix javadoc link in AOP extensibility documentation Closes gh-29554 * Retain default LocalVariableTableParameterNameDiscoverer with warn log entries For a transition period, LocalVariableTableParameterNameDiscoverer logs a warning for each successful resolution attempt now, suggesting that -parameters was missed. See gh-29531 See gh-29559 * Next development version (v6.0.2-SNAPSHOT) * Log connection info in StompBrokerRelayMessageHandler See gh-29287 * Document removal of CommonsMultipartResolver in MVC setup documentation Closes gh-29562 * Reduce deprecation warn logging to one entry per introspected class Closes gh-29563 * Rely on standard parameter name resolution in Bean Validation 3.0 Just configuring additional Kotlin reflection if Kotlin is present. Closes gh-29566 * ResponseStatusException sets detail from reason again Closes gh-29567 * Additional documentation notes on Java/Kotlin parameter name retention See gh-29563 * Next development version (v6.0.3-SNAPSHOT) * Early support for Jetty 12 (developed against 12.0.0.alpha2) Reflective getHeaders calls to be revisited; see GitHub issue #8938 in Jetty project. HttpOutput optimization commented out still in order to avoid alpha build dependency. See gh-29575 * Deprecate JettyWebSocketClient in favor of StandardWebSocketClient JettyWebSocketClient only supported on Jetty 11, to be phased out. Closes gh-29576 * Consistent documentation references to Jakarta WebSocket (2.1) Closes gh-29581 * Reinstate checkstyle for reference docs code snippets This commit also ensures that checks are performed before the application is rendered to get early feedback on errors. * Fix unrendered titles in websocket section This commit fixes the rendering of titles in the websocket section of the reference documentation. Fixes gh-29569 * Split integration chapter in smaller documents This commit splits the integration chapter of the reference documentation in smaller documents for easier maintenance. * Document Observability support in reference docs Closes gh-29524 * Upgrade to Gradle 7.6 Closes gh-29583 * Upgrade Gradle wrapper See gh-29583 * Consistently register CGLIB hints for lazy resolution proxy classes Core JDK/CGLIB proxy registration code extracted to ClassHintUtils. Closes gh-29584 * Remove erroneous Javadoc link * Improve logging in TestContextManager * Forbid loading of test ApplicationContext in AOT mode if AOT processing failed Prior to this commit, if AOT processing of a test's ApplicationContext failed, the TestContext Framework (TCF) still attempted to load the context via standard (JVM) mechanisms when running in AOT mode. For example, if a test class uses Spring Boot's @MockBean, AOT processing of that test's context will fail with a WARN log message, and there will no mapping from that test class to an AOT-generated ApplicationContextInitializer (ACI). Consequently, when the test suite is run in AOT mode that particular test class will currently fail with a confusing stack trace due to the fact that Spring Boot's SpringApplication attempts to locate a "main" ACI instead of the missing "test" ACI (missing because it was not generated during AOT processing). In general, the TCF should not attempt to load an ApplicationContext in "JVM mode" while running in "AOT mode". This commit therefore reworks the logic in DefaultCacheAwareContextLoaderDelegate to fail fast (with a meaningful error message) if an AOT-generated ACI cannot be found while running in AOT mode. This avoids the aforementioned confusion when @MockBean tests fail in AOT mode (on the JVM or within a native image), and it also helps to diagnose build problems if AOT processing has not yet been performed for the project's test suite. Closes gh-29579 * Polish TestContextManager internals * Use JUnit Jupiter annotations as examples in TestContextManager JavaDoc * Update Javadoc regarding JUnit versions * Add MessageSource getters See gh-29574 * Polish Testing chapter * Fix errors in Testing chapter - group code example callouts to ensure callouts are displayed for the correct examples - add missing callouts - fix syntax, annotation attribute names, etc. * Fix typo in observability documentation Closes gh-29590 * Add title to SockJS iFrames for accessibility compliance Closes gh-29594 * Polish contribution See gh-29594 * Polishing * Fix broken tests in SockJsServiceTests See gh-29594 * Avoid deprecation warnings in tests * Rename to AbstractReactiveWebSocketIntegrationTests to avoid duplicate class names * Polishing * Polishing * Add "missing" callout for parity * Fix code example callouts in reference manual * Add missing callout * Apply project formatting rules for ternary operator Discovered via RegEx: ^\s+\? * Apply "instanceof pattern matching" * Introduce update_copyright_headers.sh shell script In order to automate maintenance of copyright headers in our source code (especially when merging PRs from external contributors), this commit introduces an update_copyright_headers.sh script (tested on mac OS) that will update the copyright headers of all Java, Kotlin, and Groovy source files that have been added or modified this year (or at least as far back as the git log history supports it). For example, running this script currently outputs the following. Updating copyright headers in Java, Kotlin, and Groovy source code for year 2022 warning: log for 'main' only goes back to Tue, 16 Aug 2022 16:24:55 +0200 * Update copyright headers for source code changed since August 2022 The changes in this commit were performed using the newly introduced update_copyright_headers.sh script. * Update Javadoc for MBeanTestUtils * Apply update_copyright_headers.sh to staged files as well * Avoid unnecessary parameter name inspection for constructor-arg resolution Closes gh-29612 * Add equals/hashCode methods to ProblemDetail Closes gh-29606 * Polishing * Polish Closes gh-29619 * Use resolved factory method return type for supplier code generation Closes gh-29598 * Polishing * Revised support for Jetty 12 (tested against 12.0.0.alpha2) Avoids HttpFields optimization completely, relying on Servlet header access instead. ServletServerHttpResponse provides applyHeaders/adaptHeaders split for better reuse. See gh-29575 * ResponseStatusException delegates to protected constructor This ensures that by default the reason is used to set the "detail" field. It's a follow-up fix to a27f2e994b632a63ca1bbd6f3de5d9b60246ccaa which resolved the issue partially. Closes gh-29608 * Deprecate GraphQL media type in favor of new one This commit deprecates the `"application/graphql+json"` media type in favor of the new `"application/graphql-response+json"`, since the former has been removed in graphql/graphql-over-http#215. Closes gh-29617 * Fix URI override for HttpExchange Closes gh-29624 * Upgrade to Apache HttpClient 5.2 Includes JRuby 9.4, Groovy 4.0.6, Hibernate ORM 5.6.14, HSQLDB 2.7.1, SLF4J 2.0.5, Caffeine 3.1.2, Gson 2.10, POI 5.2.3, Protobuf 3.21.10, WebJars Locator 0.52, HtmlUnit 2.67, Mockito 4.9, Checkstyle 10.5 Closes gh-29627 * Fix ErrorResponse#type documentation Closes gh-29632 * Apply "instanceof pattern matching" * Polish Javadoc for ErrorResponse etc. * Reinstate test for JmxUtils.locateMBeanServer() * Update copyright headers * Polish overview for consistency * Do not refer to HTML ref docs from HTML ref docs * Revise PDF ref docs to include TOC, authors, and legal sections * Polish ref docs build * Upgrade JMH build plugins This commit upgrades the Gradle JMH plugin to 0.6.8 and the companion JMH version to 1.36. * Reduce heap memory usage in ConcurrentLruCache Prior to this commit, the `ConcurrentLruCache` implementation would use arrays of `AtomicReference` as operation buffers, and the buffer count would be calculated with the nearest power of two for the CPU count. This can result in significant heap memory usage as each `AtomicReference` buffer entry adds to the memory pressure. As seen in FasterXML/jackson-databind#3665, this can add a significant overhead for no real added benefit for the current use case. This commit changes the current implementation to use `AtomicReferenceArray` as buffers and reduce the number of buffers. JMH benchmarks results are within the error margin so we can assume that this does not change the performance characteristics for the typical use case in Spring Framework. Fixes gh-29520 * Improve invalid Content-Type handling in WebFlux Closes gh-29565 * Simplify form data handling in HttpRequestValues Closes gh-29615 * Improve Netty code sample See gh-29622 * Polishing contribution Closes gh-29622 * Fix canWrite of PartHttpMessageWriter See gh-29631 * Push canWrite down into MultipartHttpMessageWriter The implementation in the base class only matches the MultipartHttpMessageWriter subclass. The other two override it anyway. Closes gh-29631 * Polishing, suppression of deprecation warnings, copyright headers, etc. * Enable backport bot for pull requests * Correct event name in backport bot config * Add write permission in backport bot config * Correct permissions key in backport bot config * Correct (again) permissions key in backport bot config * Fall back to JdkClientHttpConnector as ClientHttpConnector * Fix issue with getHeaders in NoHandlerFoundException Closes gh-29626 * Add convenience methods for binding error messages Closes gh-29574 * Apply "instanceof pattern matching" in spring-webflux Closes gh-29635 * Polishing * Rename MultipartWebClientIntegrationTests classes to avoid duplicate names * Polishing * Reintroduce component index support for Jakarta annotations Spring Framework 6.0 GA introduced a regression in the component index support for Jakarta annotations such as @Named and @ManagedBean. Prior to this commit, @Named and @ManagedBean components were registered in the component index at build time; however, component scanning failed to find those component at run time. This commit updates ClassPathScanningCandidateComponentProvider so that `jakarta.*` annotation types are once again supported for component scanning via the component index at run time. Closes gh-29641 * Polish var-args declarations Closes gh-29640 * Fix InputStream violation in DataBufferInputStream This commit fixes an issue in DataBufferInputStream::mark, which before did not follow the contract defined by InputStream. Closes gh-29642 * Fix BindingReflectionHintsRegistrar anonymous classes support This commit ensures that giving an anonymous class for reflection hints registration does not result in a NullPointerException, since the canonical name of anonymous classes is null. Fixes gh-29657 * Update copyright headers * Improve Javadoc for SqlLobValue * Introduce @Suite classes for individual modules * Polishing * Fix SpEL support for quotes within String literals Prior to this commit, there were two bugs in the support for quotes within String literals in SpEL expressions. - Two double quotes ("") or two single quotes ('') were always replaced with one double quote or one single quote, respectively, regardless of which quote character was used to enclose the original String literal. This resulted in the loss of one of the double quotes when the String literal was enclosed in single quotes, and vice versa. For example, 'x "" y' became 'x " y'. - A single quote which was properly escaped in a String literal enclosed within single quotes was not escaped in the AST string representation of the expression. For example, 'x '' y' became 'x ' y'. This commit fixes both of these related issues in StringLiteral and overhauls the structure of ParsingTests. Closes gh-29604, gh-28356 * Apply 'instanceof pattern matching' in spring-jdbc * Apply 'instanceof pattern matching' in spring-expression * Polishing * Support arrays in AST string representations of SpEL expressions Prior to this commit, SpEL's ConstructorReference did not provide support for arrays when generating a string representation of the internal AST. For example, 'new String[3]' was represented as 'new String()' instead of 'new String[3]'. This commit introduces support for standard array construction and array construction with initializers in ConstructorReference's toStringAST() implementation. Closes gh-29665 * Polishing - primarily automated "clean up" using Eclipse IDE * Polish CGLIB fork - primarily automated "clean up" using Eclipse IDE * Remove top-level permissions from backport-bot.yml * Optimize object creation PartialMatchHelper See gh-29634 * Polishing contribution Closes gh-29634 * Restore top-level read permission for backport bot * Apply 'instanceof pattern matching' in spring-test and Servlet mocks * Polish tests in spring-test * Use URI#create instead of URI constructor where feasible in spring-test * Use URI#create instead of URI constructor where feasible in spring-web * Use URI#create instead of URI constructor where feasible in spring-webflux * Use URI#create instead of URI constructor where feasible in spring-webmvc * Use URI#create instead of URI constructor where feasible in spring-websocket * Introduce additional constructors in MockClientHttp[Request|Response] This commit introduces additional constructors in MockClientHttpRequest and MockClientHttpResponse that were previously only present in the internal "test fixtures" in spring-web. This commit also aligns the mocks in spring-test with the test fixtures in spring-web to simplify continued maintenance of the mocks and test fixtures. Closes gh-29670 * Remove dead code in MockClientHttpRequest * Support properties on records in BindingReflectionHintsRegistrar Closes gh-29571 * Refine BindingReflectionHintsRegistrar Kotlin support Closes gh-29593 * Make @ModelAttribute and @InitBinder reflective Closes gh-29572 * Upgrade to Micrometer 1.10.2 Closes gh-29678 * Start building against Reactor 2022.0.1 See gh-29679 * Upgrade optional dependencies * Use consistent visibility for ResponseEntityExceptionHandler.getMessageSource() See gh-29574 Closes gh-29676 * Clean up Javadoc and source code regarding " ." typos * Align multipart codecs on client and server This commit ensures that the same multipart codecs are registered on both client and server. Previously, only the client enabled only sending multipart, and the server only receiving. Closes gh-29630 * Detect SQL state 23505/40001 as DuplicateKeyException/CannotAcquireLockException Favors PessimisticLockingFailureException over plain ConcurrencyFailureException. Deprecates CannotSerializeTransactionException and DeadlockLoserDataAccessException. Closes gh-29511 Closes gh-29675 * Avoid NPE on BeanDescriptor access with SimpleBeanInfoFactory Closes gh-29681 * Polishing * Polish Javadoc * Update copyright date * Add missing Javadoc See gh-29574 * Revise RepeatableContainersTests * Support repeatable annotation containers with multiple attributes Prior to this commit, there was a bug in the implementation of StandardRepeatableContainers.computeRepeatedAnnotationsMethod() which has existed since Spring Framework 5.2 (when StandardRepeatableContainers was introduced). Specifically, StandardRepeatableContainers ignored any repeatable container annotation if it declared attributes other than `value()`. However, Java permits any number of attributes in a repeatable container annotation. In addition, the changes made in conjunction with gh-20279 made the bug in StandardRepeatableContainers apparent when using the getMergedRepeatableAnnotations() or findMergedRepeatableAnnotations() method in AnnotatedElementUtils, resulting in regressions for the behavior of those two methods. This commit fixes the regressions and bug by altering the logic in StandardRepeatableContainers.computeRepeatedAnnotationsMethod() so that it explicitly looks for the `value()` method and ignores any other methods declared in a repeatable container annotation candidate. Closes gh-29685 * Remove obsolete AttributeMethods.hasOnlyValueAttribute() method See gh-29685 * Upgrade to Reactor 2022.0.1 Closes gh-29679 * Support non-standard HTTP methods in FrameworkServlet This commit ensures that HTTP methods not supported by HttpServlet, for instance WebDAV methods, are still supported in FrameworkServlet. Closes gh-29689 * Remove duplicated test code * Improve Javadoc for RepeatableContainers * Next development version (v6.0.4-SNAPSHOT) * Fix manipulating property sources example in Javadoc for ConfigurableEnvironment The "manipulating property sources" example in the Javadoc for `ConfigurableEnvironment` states that `MutablePropertySources` expect a `Map`; whereas it expects a `Map`. Closes gh-29693 * Polishing * Polishing * Improve documentation for literals in SpEL expressions Closes gh-29700 * Extract ResourceEntityResolver HTTPS schema resolution fallback This commit extracts the DTD/XSD remote lookup fallback from the resolveEntity() method into a protected method. A WARN-level logging statement is added to the extracted fallback in order to make it clear that remote lookup happened. Overriding the protected method would allow users to avoid this fallback entirely if it isn't desirable, without the need to duplicate the local resolution code. Closes gh-29697 * Polish contribution See gh-29697 * Fix typos in reference manual Closes gh-29694 * Fix link to Jakarta Mail Closes gh-29694 * Remove ref to JOTM, inactive since 2009 Closes gh-29694 * Remove statement that users could be on Java < 5 Closes gh-29694 * Rework linking to Spring MVC Async support vs WebFlux section The link was previously named "Compared to WebFlux", which is easy to mix up with the various links to equivalent sections in the WebFlux chapter. Here the links point to a small section comparing the Servlet Async API to the WebFlux stack from a high perspective. In this commit we eliminate most of these links, except at the beginning of the Asynchronous section. We also add a small mention of the Servlet configuration in the comparison paragraphs, since the Configuring section is the one furthest from the comparison paragraphs that used to have a link to it. Closes gh-29694 * Fix subsection style in WebFlux Concurrency Model The block title style previously used was not rendered in HTML and the title couldn't be differentiated from the text. Though, it was in the PDF, as italics. Introducing delimited blocks in the open (`--`) style did introduce styling, but the vertical alignment isn't great. This commit turns these block titles to actual (deep) section titles. In the final HTML, at this depth there is no numbering but bold styling is there. The PDF rendering has also been verified to have relevant style. Closes gh-29694 * Change plain 'WebFlux' links to 'See equivalent in the Reactive stack' Closes gh-29694 * Change plain 'WebMVC' links to 'See equivalent in the Servlet stack' Closes gh-29694 * Cross reference WebTestClient section * Update Jakarta Mail info in ref docs Closes gh-29707 * Remove duplicate words in reference manual Found using regular expression: \b(\w+)\s+\1\b * Remove duplicated words in Javadoc * Fix typos in reference manual * Fix formatting in examples * Fix typos in anchors * Polishing * Update copyright headers * Apply "instanceof pattern matching" (#29710) * Polishing * Update Trigger & TriggerContext reference documentation Closes gh-29702 * Revert incorrect change to ParamAware Javadoc * Apply "instanceof pattern matching" in spring-aop * Fix path within mapping when pattern contains ".*" Prior to this commit, extracting the path within handler mapping would result in "" if the matching path element would be a Regex and contain ".*". This could cause issues with resource handling if the handler mapping pattern was similar to `"/folder/file.*.extension"`. This commit introduces a new `isLiteral()` method in the `PathElement` abstract class that expresses whether the path element can be compared as a String for path matching or if it requires a more elaborate matching process. Using this method for extracting the path within handler mapping avoids relying on wildcard count or other properties. Fixes gh-29712 * Apply update_copyright_headers.sh Co-authored-by: Juergen Hoeller Co-authored-by: Sam Brannen Co-authored-by: Andriy Co-authored-by: Stephane Nicoll Co-authored-by: Andy Wilkinson Co-authored-by: Arjen Poutsma Co-authored-by: Spring Builds Co-authored-by: Brian Clozel Co-authored-by: Sébastien Deleuze Co-authored-by: wizard <925553434@qq.com> Co-authored-by: divcon Co-authored-by: David Costanzo Co-authored-by: Marten Deinum Co-authored-by: André Gasser Co-authored-by: jiangying Co-authored-by: rstoyanchev Co-authored-by: Yanming Zhou Co-authored-by: Aashay Chapatwala <49100046+Aashay-Chapatwala@users.noreply.github.com> Co-authored-by: Johnny Lim Co-authored-by: Baljit Singh Co-authored-by: Spark61 Co-authored-by: CoderYellow Co-authored-by: Moritz Halbritter Co-authored-by: divcon Co-authored-by: Sam Brannen Co-authored-by: ShenFeng312 <49786112+ShenFeng312@users.noreply.github.com> Co-authored-by: koo.taejin Co-authored-by: Carlos Belizón Co-authored-by: Simon Baslé Co-authored-by: diguage --- .github/workflows/backport-bot.yml | 3 + build.gradle | 13 +- .../build/KotlinConventions.java | 1 + framework-docs/framework-docs.gradle | 35 +- .../src/docs/asciidoc/appendix.adoc | 6 +- .../src/docs/asciidoc/attributes.adoc | 18 + framework-docs/src/docs/asciidoc/core.adoc | 6 +- .../src/docs/asciidoc/core/core-aop-api.adoc | 16 +- .../src/docs/asciidoc/core/core-aop.adoc | 7 +- .../src/docs/asciidoc/core/core-aot.adoc | 312 +- .../src/docs/asciidoc/core/core-appendix.adoc | 4 +- .../src/docs/asciidoc/core/core-beans.adoc | 121 +- .../docs/asciidoc/core/core-expressions.adoc | 47 +- .../docs/asciidoc/core/core-validation.adoc | 2 +- .../src/docs/asciidoc/data-access.adoc | 54 +- .../data-access/data-access-appendix.adoc | 17 +- framework-docs/src/docs/asciidoc/index.adoc | 13 +- .../src/docs/asciidoc/integration.adoc | 5731 +---------- .../src/docs/asciidoc/integration/cache.adoc | 1070 ++ .../src/docs/asciidoc/integration/email.adoc | 303 + .../integration/integration-appendix.adoc | 18 +- .../src/docs/asciidoc/integration/jms.adoc | 1474 +++ .../src/docs/asciidoc/integration/jmx.adoc | 1375 +++ .../asciidoc/integration/observability.adoc | 175 + .../asciidoc/integration/rest-clients.adoc | 517 + .../docs/asciidoc/integration/scheduling.adoc | 968 ++ .../src/docs/asciidoc/languages.adoc | 6 +- .../src/docs/asciidoc/languages/kotlin.adoc | 81 +- .../src/docs/asciidoc/overview.adoc | 7 +- .../src/docs/asciidoc/page-layout.adoc | 4 + framework-docs/src/docs/asciidoc/rsocket.adoc | 5 +- .../docs/asciidoc/spring-framework.adocbook | 25 +- framework-docs/src/docs/asciidoc/testing.adoc | 8995 +---------------- .../asciidoc/testing/integration-testing.adoc | 155 + .../testing/spring-mvc-test-client.adoc | 136 + .../testing/spring-mvc-test-framework.adoc | 1863 ++++ .../testing/testcontext-framework.adoc | 4723 +++++++++ .../asciidoc/testing/testing-annotations.adoc | 1930 ++++ .../asciidoc/testing/testing-appendix.adoc | 6 + .../testing/testing-introduction.adoc | 8 + .../asciidoc/testing/testing-resources.adoc | 32 + .../testing/testing-support-jdbc.adoc | 35 + .../docs/asciidoc/testing/unit-testing.adoc | 168 + .../src/docs/asciidoc/web-reactive.adoc | 10 +- framework-docs/src/docs/asciidoc/web.adoc | 6 +- .../src/docs/asciidoc/web/integration.adoc | 41 +- .../src/docs/asciidoc/web/web-uris.adoc | 12 +- .../src/docs/asciidoc/web/webflux-cors.adoc | 21 +- .../docs/asciidoc/web/webflux-functional.adoc | 27 +- .../src/docs/asciidoc/web/webflux-view.adoc | 22 +- .../docs/asciidoc/web/webflux-webclient.adoc | 22 +- .../docs/asciidoc/web/webflux-websocket.adoc | 12 +- .../src/docs/asciidoc/web/webflux.adoc | 247 +- .../src/docs/asciidoc/web/webmvc-cors.adoc | 17 +- .../docs/asciidoc/web/webmvc-functional.adoc | 58 +- .../src/docs/asciidoc/web/webmvc-test.adoc | 4 +- .../src/docs/asciidoc/web/webmvc-view.adoc | 26 +- .../src/docs/asciidoc/web/webmvc.adoc | 280 +- .../docs/asciidoc/web/websocket-intro.adoc | 8 +- .../src/docs/asciidoc/web/websocket.adoc | 35 +- .../importruntimehints/SpellCheckService.java | 44 + .../aot/hints/testing/SampleReflection.java | 42 + .../SampleReflectionRuntimeHintsTests.java | 54 + .../hints/testing/SpellCheckServiceTests.java | 48 + .../core/aot/refresh/AotProcessingSample.java | 50 + ...tomServerRequestObservationConvention.java | 71 + ...dedServerRequestObservationConvention.java | 37 + .../ServerRequestObservationFilter.java | 38 + framework-platform/framework-platform.gradle | 55 +- gradle.properties | 4 +- gradle/spring-module.gradle | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 18 +- gradlew.bat | 15 +- ...NamespaceHandlerScopeIntegrationTests.java | 4 +- .../EnableCachingIntegrationTests.java | 2 +- ...TransactionManagementIntegrationTests.java | 2 +- .../aop/aspectj/AbstractAspectJAdvice.java | 4 +- .../AspectJAdviceParameterNameDiscoverer.java | 16 +- .../aop/aspectj/AspectJAopUtils.java | 10 +- .../aspectj/AspectJExpressionPointcut.java | 39 +- .../aop/aspectj/AspectJProxyUtils.java | 6 +- .../aop/aspectj/RuntimeTestWalker.java | 6 +- .../SingletonAspectInstanceFactory.java | 6 +- .../aop/aspectj/TypePatternClassFilter.java | 8 +- .../AbstractAspectJAdvisorFactory.java | 35 +- .../BeanFactoryAspectInstanceFactory.java | 11 +- ...ntiationModelAwarePointcutAdvisorImpl.java | 4 +- ...erceptorDrivenBeanDefinitionDecorator.java | 6 +- .../config/ConfigBeanDefinitionParser.java | 18 +- ...BeanFactoryAwareAspectInstanceFactory.java | 8 +- .../AbstractAdvisingBeanPostProcessor.java | 10 +- .../AbstractSingletonProxyFactoryBean.java | 10 +- .../aop/framework/AdvisedSupport.java | 26 +- .../aop/framework/CglibAopProxy.java | 26 +- .../framework/DefaultAdvisorChainFactory.java | 6 +- .../aop/framework/JdkDynamicAopProxy.java | 8 +- .../aop/framework/ProxyCreatorSupport.java | 4 +- .../AdvisorAdapterRegistrationManager.java | 6 +- .../DefaultAdvisorAdapterRegistry.java | 10 +- .../AbstractAdvisorAutoProxyCreator.java | 6 +- .../autoproxy/AbstractAutoProxyCreator.java | 14 +- ...BeanFactoryAwareAdvisingPostProcessor.java | 5 +- ...ctBeanFactoryBasedTargetSourceCreator.java | 6 +- .../target/LazyInitTargetSourceCreator.java | 7 +- .../AsyncExecutionAspectSupport.java | 4 +- .../AsyncExecutionInterceptor.java | 6 +- ...opedProxyBeanRegistrationAotProcessor.java | 2 +- .../AbstractBeanFactoryPointcutAdvisor.java | 6 +- .../springframework/aop/support/AopUtils.java | 12 +- .../aop/support/ClassFilters.java | 14 +- .../aop/support/ComposablePointcut.java | 2 +- .../support/DefaultIntroductionAdvisor.java | 8 +- ...erTargetObjectIntroductionInterceptor.java | 6 +- .../DelegatingIntroductionInterceptor.java | 6 +- .../aop/support/MethodMatchers.java | 6 +- .../aop/support/NameMatchMethodPointcut.java | 8 +- .../aop/support/RootClassFilter.java | 8 +- .../AbstractBeanFactoryBasedTargetSource.java | 2 +- .../AbstractPrototypeBasedTargetSource.java | 10 +- .../aop/target/HotSwappableTargetSource.java | 8 +- .../aop/target/SingletonTargetSource.java | 2 +- .../BeanFactoryRefreshableTargetSource.java | 4 +- .../AbstractAspectJAdvisorFactoryTests.java | 6 +- .../AbstractRegexpMethodPointcutTests.java | 114 - .../support/JdkRegexpMethodPointcutTests.java | 89 +- .../PrototypeBasedTargetSourceTests.java | 2 +- spring-aspects/spring-aspects.gradle | 6 + .../org/springframework/beans/BeanUtils.java | 4 +- .../beans/CachedIntrospectionResults.java | 16 +- .../beans/PropertyEditorRegistrySupport.java | 2 +- .../beans/SimpleBeanInfoFactory.java | 5 + .../beans/factory/InjectionPoint.java | 4 +- .../beans/factory/ListableBeanFactory.java | 42 +- .../AutowiredAnnotationBeanPostProcessor.java | 64 +- .../aot/AutowiredArgumentsCodeGenerator.java | 13 +- .../aot/AutowiredFieldValueResolver.java | 4 +- .../aot/AutowiredMethodArgumentsResolver.java | 9 +- .../aot/BeanDefinitionMethodGenerator.java | 17 +- .../BeanDefinitionMethodGeneratorFactory.java | 18 +- .../factory/aot/BeanInstanceSupplier.java | 106 +- ...eanRegistrationCodeFragmentsDecorator.java | 22 +- .../aot/BeanRegistrationsAotContribution.java | 32 +- .../aot/BeanRegistrationsAotProcessor.java | 23 +- .../aot/InstanceSupplierCodeGenerator.java | 79 +- .../aot/ResolvableTypeCodeGenerator.java | 2 +- .../config/ConstructorArgumentValues.java | 21 +- .../factory/config/RuntimeBeanReference.java | 4 +- .../factory/config/TypedStringValue.java | 4 +- .../beans/factory/config/YamlProcessor.java | 5 +- ...AbstractServiceLoaderBasedFactoryBean.java | 4 +- .../AbstractAutowireCapableBeanFactory.java | 42 +- .../factory/support/AbstractBeanFactory.java | 2 +- .../factory/support/ConstructorResolver.java | 21 +- .../support/DefaultListableBeanFactory.java | 62 +- ...ricTypeAwareAutowireCandidateResolver.java | 18 +- .../factory/support/InstanceSupplier.java | 17 +- .../PropertiesBeanDefinitionReader.java | 2 +- .../support/StaticListableBeanFactory.java | 13 +- .../factory/xml/ResourceEntityResolver.java | 68 +- .../beans/propertyeditors/PathEditor.java | 4 +- .../beans/AbstractPropertyAccessorTests.java | 43 +- .../springframework/beans/BeanUtilsTests.java | 24 +- .../beans/BeanWrapperAutoGrowingTests.java | 14 +- .../beans/BeanWrapperEnumTests.java | 22 +- .../beans/BeanWrapperGenericsTests.java | 4 +- .../beans/BeanWrapperTests.java | 32 + .../beans/ExtendedBeanInfoTests.java | 10 +- .../beans/factory/BeanFactoryUtilsTests.java | 42 +- .../DefaultListableBeanFactoryTests.java | 30 +- ...wiredAnnotationBeanPostProcessorTests.java | 130 +- ...njectAnnotationBeanPostProcessorTests.java | 18 +- .../beans/factory/aot/AotServicesTests.java | 7 +- ...AutowiredMethodArgumentsResolverTests.java | 9 +- .../BeanDefinitionMethodGeneratorTests.java | 72 +- ...BeanRegistrationsAotContributionTests.java | 32 +- .../BeanRegistrationsAotProcessorTests.java | 7 +- .../PropertyResourceConfigurerTests.java | 12 +- .../factory/config/SimpleScopeTests.java | 4 +- .../config/YamlMapFactoryBeanTests.java | 20 +- .../factory/config/YamlProcessorTests.java | 4 +- .../factory/support/BeanDefinitionTests.java | 4 +- .../support/BeanFactoryGenericsTests.java | 46 +- .../DefaultSingletonBeanRegistryTests.java | 8 +- .../support/InstanceSupplierTests.java | 2 +- ...ierAnnotationAutowireBeanFactoryTests.java | 10 +- .../factory/xml/CollectionMergingTests.java | 14 +- .../factory/xml/EventPublicationTests.java | 73 +- .../xml/ResourceEntityResolverTests.java | 128 + .../xml/UtilNamespaceHandlerTests.java | 183 +- .../factory/xml/XmlBeanCollectionTests.java | 12 +- .../xml/XmlBeanDefinitionReaderTests.java | 6 +- .../xml/XmlListableBeanFactoryTests.java | 8 +- .../propertyeditors/CustomEditorTests.java | 10 +- .../PropertiesEditorTests.java | 6 +- .../beans/testfixture/beans/TestBean.java | 4 +- .../context/index/processor/TypeHelper.java | 2 +- .../CandidateComponentsIndexerTests.java | 8 +- .../context/index/processor/Metadata.java | 2 +- .../DefaultJCacheOperationSource.java | 4 +- .../mail/SimpleMailMessage.java | 2 +- .../quartz/LocalTaskExecutorThreadPool.java | 2 +- .../quartz/SchedulerFactoryBean.java | 14 +- .../SchedulerFactoryBeanRuntimeHints.java | 2 + .../AbstractCacheOperationTests.java | 4 +- .../AnnotationCacheOperationSourceTests.java | 4 +- .../interceptor/CachePutOperationTests.java | 4 +- .../CacheRemoveAllOperationTests.java | 4 +- .../CacheRemoveOperationTests.java | 4 +- .../CacheResolverAdapterTests.java | 4 +- .../CacheResultOperationTests.java | 12 +- .../mail/javamail/JavaMailSenderTests.java | 44 +- .../AbstractCachingConfiguration.java | 2 +- .../cache/annotation/Caching.java | 2 +- .../context/MessageSourceAware.java | 15 +- ...athScanningCandidateComponentProvider.java | 25 +- .../annotation/ConfigurationClassParser.java | 5 +- .../ConfigurationClassPostProcessor.java | 1 + .../annotation/ConfigurationClassUtils.java | 13 +- .../annotation/ImportRuntimeHints.java | 21 +- .../annotation/ParserStrategyUtils.java | 23 +- .../context/aot/CglibClassHandler.java | 10 +- ...BeanFactoryInitializationAotProcessor.java | 72 +- .../BeanExpressionContextAccessor.java | 4 +- .../expression/BeanFactoryAccessor.java | 4 +- .../support/AbstractApplicationContext.java | 42 +- .../support/AbstractMessageSource.java | 14 +- .../ApplicationContextAwareProcessor.java | 46 +- .../support/ApplicationListenerDetector.java | 15 +- .../support/DefaultLifecycleProcessor.java | 20 +- .../support/GenericApplicationContext.java | 52 +- .../GenericGroovyApplicationContext.java | 6 +- .../PostProcessorRegistrationDelegate.java | 8 +- ...ReloadableResourceBundleMessageSource.java | 6 +- .../format/datetime/DateFormatter.java | 17 +- .../support/FormattingConversionService.java | 7 +- .../springframework/scheduling/Trigger.java | 1 + .../scheduling/TriggerContext.java | 7 +- .../AbstractAsyncConfiguration.java | 2 +- .../AsyncAnnotationBeanPostProcessor.java | 2 +- .../annotation/ProxyAsyncConfiguration.java | 4 +- .../ScheduledAnnotationBeanPostProcessor.java | 2 +- .../support/CronSequenceGenerator.java | 6 +- .../validation/BindingResult.java | 4 +- .../LocalValidatorFactoryBean.java | 31 +- .../SpringValidatorAdapter.java | 10 +- .../IndexedJakartaManagedBeanComponent.java | 24 + .../indexed/IndexedJakartaNamedComponent.java | 24 + .../scannable/DefaultNamedComponent.java | 3 +- .../JakartaManagedBeanComponent.java | 24 + .../scannable/JakartaNamedComponent.java | 24 + .../aop/aspectj/AroundAdviceBindingTests.java | 6 +- .../AspectAndAdvicePrecedenceTests.java | 4 +- .../DeclarationOrderIndependenceTests.java | 20 +- .../aop/aspectj/ProceedTests.java | 10 +- .../AtAspectJAnnotationBindingTests.java | 4 +- .../aop/framework/AbstractAopProxyTests.java | 16 +- .../factory/xml/XmlBeanFactoryTests.java | 36 +- .../support/CustomNamespaceHandlerTests.java | 8 +- .../concurrent/ConcurrentMapCacheTests.java | 4 +- .../interceptor/ExpressionEvaluatorTests.java | 4 +- ...notationConfigApplicationContextTests.java | 104 +- .../ClassPathBeanDefinitionScannerTests.java | 6 +- ...anningCandidateComponentProviderTests.java | 178 +- ...ommonAnnotationBeanPostProcessorTests.java | 2 +- .../ConfigurationClassAndBeanMethodTests.java | 2 +- .../ConfigurationClassPostProcessorTests.java | 68 +- .../ConfigurationClassWithConditionTests.java | 8 +- .../context/annotation/Gh29105Tests.java | 17 +- .../annotation/ImportSelectorTests.java | 12 +- .../ConfigurationClassProcessingTests.java | 6 +- .../ApplicationContextAotGeneratorTests.java | 14 + .../ConversionServiceContextConfigTests.java | 2 +- .../event/ApplicationContextEventTests.java | 14 +- .../event/PayloadApplicationEventTests.java | 2 +- .../ApplicationContextExpressionTests.java | 2 +- .../ClassPathXmlApplicationContextTests.java | 22 +- .../DefaultLifecycleProcessorTests.java | 36 +- .../ResourceBundleMessageSourceTests.java | 14 +- .../jmx/AbstractMBeanServerTests.java | 2 +- .../jmx/export/NotificationListenerTests.java | 4 +- .../jmx/support/JmxUtilsTests.java | 19 +- .../annotation/EnableSchedulingTests.java | 8 +- ...duledAnnotationBeanPostProcessorTests.java | 62 +- .../support/CronSequenceGeneratorTests.java | 4 +- .../config/ScriptingDefaultsTests.java | 4 +- .../groovy/GroovyScriptFactoryTests.java | 2 +- .../support/ResourceScriptSourceTests.java | 2 +- .../org/springframework/ui/ModelMapTests.java | 30 +- .../springframework/util/MBeanTestUtils.java | 33 +- .../validation/DataBinderTests.java | 6 +- .../beanvalidation/ValidatorFactoryTests.java | 14 +- .../example/scannable/spring.components | 6 +- .../testfixture/cache/AbstractCacheTests.java | 4 +- .../annotation/AutowiredGenericTemplate.java | 31 + .../context/annotation/GenericTemplate.java | 23 + .../GenericTemplateConfiguration.java | 30 + .../CandidateComponentsTestClassLoader.java | 10 +- .../InvocationsRecorderClassTransformer.java | 4 +- .../aot/agent/RecordedInvocation.java | 4 +- .../agent/RuntimeHintsInvocationsAssert.java | 4 +- .../aot/test/agent/RuntimeHintsRecorder.java | 4 +- .../aot/test/generate/CompilerFiles.java | 3 +- .../core/test/tools/DynamicClassLoader.java | 2 +- .../core/test/tools/SourceFile.java | 5 +- .../aot/agent/RecordedInvocationTests.java | 4 +- .../tools/DynamicJavaFileManagerTests.java | 2 +- spring-core/spring-core.gradle | 3 +- .../GenericConversionServiceBenchmark.java | 2 +- .../aot/generate/DefaultMethodReference.java | 12 +- .../aot/generate/MethodReference.java | 4 +- .../aot/hint/AbstractTypeReference.java | 5 +- .../hint/BindingReflectionHintsRegistrar.java | 67 +- .../aot/hint/ReflectionTypeReference.java | 5 +- .../aot/hint/annotation/Reflective.java | 6 +- .../hint/annotation/ReflectiveProcessor.java | 4 + .../ReflectiveRuntimeHintsRegistrar.java | 6 +- .../RegisterReflectionForBinding.java | 23 +- ...RegisterReflectionForBindingProcessor.java | 5 +- .../predicate/ReflectionHintsPredicates.java | 33 +- .../SerializationHintsPredicates.java | 5 +- .../aot/hint/support/ClassHintUtils.java | 76 + .../ObjectToObjectConverterRuntimeHints.java | 48 + .../feature/PreComputeFieldFeature.java | 4 +- .../substitution/Target_ClassFinder.java | 2 + .../substitution/Target_Introspector.java | 2 + .../cglib/beans/BeanCopier.java | 51 +- .../cglib/beans/BeanGenerator.java | 33 +- .../springframework/cglib/beans/BeanMap.java | 88 +- .../cglib/beans/BeanMapEmitter.java | 45 +- .../springframework/cglib/beans/BulkBean.java | 40 +- .../cglib/beans/BulkBeanEmitter.java | 16 +- .../cglib/beans/BulkBeanException.java | 7 +- .../cglib/beans/FixedKeySet.java | 13 +- .../cglib/beans/ImmutableBean.java | 34 +- .../cglib/core/AbstractClassGenerator.java | 52 +- .../org/springframework/cglib/core/Block.java | 2 +- .../cglib/core/ClassEmitter.java | 91 +- .../springframework/cglib/core/ClassInfo.java | 18 +- .../cglib/core/ClassNameReader.java | 46 +- .../cglib/core/ClassesKey.java | 2 +- .../cglib/core/CodeEmitter.java | 14 +- .../cglib/core/CodeGenerationException.java | 3 +- .../cglib/core/CollectionUtils.java | 6 +- .../springframework/cglib/core/Constants.java | 2 +- .../cglib/core/DefaultGeneratorStrategy.java | 7 +- .../cglib/core/DefaultNamingPolicy.java | 5 +- .../cglib/core/DuplicatesPredicate.java | 12 +- .../springframework/cglib/core/EmitUtils.java | 94 +- .../cglib/core/GeneratorStrategy.java | 3 +- .../cglib/core/KeyFactory.java | 33 +- .../org/springframework/cglib/core/Local.java | 4 +- .../cglib/core/LocalVariablesSorter.java | 35 +- .../cglib/core/MethodInfo.java | 57 +- .../cglib/core/MethodWrapper.java | 7 +- .../springframework/cglib/core/Predicate.java | 5 +- .../cglib/core/ReflectUtils.java | 58 +- .../cglib/core/RejectModifierPredicate.java | 5 +- .../springframework/cglib/core/Signature.java | 20 +- .../cglib/core/SpringNamingPolicy.java | 1 + .../springframework/cglib/core/TypeUtils.java | 26 +- .../cglib/core/VisibilityPredicate.java | 13 +- .../core/internal/CustomizerRegistry.java | 4 +- .../cglib/core/internal/LoadingCache.java | 19 +- .../cglib/proxy/BridgeMethodResolver.java | 16 +- .../cglib/proxy/CallbackFilter.java | 5 +- .../cglib/proxy/CallbackGenerator.java | 6 +- .../cglib/proxy/CallbackHelper.java | 63 +- .../cglib/proxy/CallbackInfo.java | 10 +- .../cglib/proxy/DispatcherGenerator.java | 16 +- .../springframework/cglib/proxy/Enhancer.java | 84 +- .../springframework/cglib/proxy/Factory.java | 8 +- .../cglib/proxy/FixedValueGenerator.java | 16 +- .../cglib/proxy/InterfaceMaker.java | 30 +- .../proxy/InvocationHandlerGenerator.java | 19 +- .../cglib/proxy/LazyLoaderGenerator.java | 23 +- .../cglib/proxy/MethodInterceptor.java | 2 +- .../proxy/MethodInterceptorGenerator.java | 7 +- .../cglib/proxy/MethodProxy.java | 3 +- .../cglib/proxy/MixinBeanEmitter.java | 9 +- .../cglib/proxy/MixinEmitter.java | 28 +- .../cglib/proxy/MixinEverythingEmitter.java | 16 +- .../cglib/proxy/NoOpGenerator.java | 15 +- .../springframework/cglib/proxy/Proxy.java | 26 +- .../proxy/UndeclaredThrowableException.java | 2 +- .../cglib/reflect/ConstructorDelegate.java | 30 +- .../cglib/reflect/FastClass.java | 46 +- .../cglib/reflect/FastClassEmitter.java | 75 +- .../cglib/reflect/FastConstructor.java | 6 +- .../cglib/reflect/FastMember.java | 8 +- .../cglib/reflect/FastMethod.java | 9 +- .../cglib/reflect/MulticastDelegate.java | 20 +- .../AbstractClassFilterTransformer.java | 42 +- .../cglib/transform/AbstractClassLoader.java | 55 +- .../transform/AbstractClassTransformer.java | 5 +- .../cglib/transform/AnnotationVisitorTee.java | 35 +- .../cglib/transform/ClassFilter.java | 4 +- .../cglib/transform/ClassReaderGenerator.java | 9 +- .../transform/ClassTransformerChain.java | 11 +- .../cglib/transform/ClassTransformerTee.java | 9 +- .../cglib/transform/ClassVisitorTee.java | 54 +- .../cglib/transform/FieldVisitorTee.java | 18 +- .../transform/MethodFilterTransformer.java | 10 +- .../cglib/transform/MethodVisitorTee.java | 135 +- .../impl/AbstractInterceptFieldCallback.java | 56 +- .../impl/AddDelegateTransformer.java | 10 +- .../impl/AddPropertyTransformer.java | 11 +- .../impl/AddStaticInitTransformer.java | 13 +- .../cglib/transform/impl/FieldProvider.java | 10 +- .../impl/InterceptFieldCallback.java | 2 +- .../impl/InterceptFieldTransformer.java | 30 +- .../impl/UndeclaredThrowableStrategy.java | 13 +- .../impl/UndeclaredThrowableTransformer.java | 9 +- .../cglib/util/ParallelSorter.java | 6 +- .../cglib/util/SorterTemplate.java | 4 +- .../core/CollectionFactory.java | 51 +- .../org/springframework/core/Constants.java | 4 +- .../org/springframework/core/Conventions.java | 7 +- .../core/DefaultParameterNameDiscoverer.java | 19 +- ...tlinReflectionParameterNameDiscoverer.java | 6 +- ...lVariableTableParameterNameDiscoverer.java | 16 + .../core/ReactiveAdapterRegistry.java | 11 +- .../core/ReactiveTypeDescriptor.java | 4 +- ...dardReflectionParameterNameDiscoverer.java | 7 +- .../annotation/AnnotatedElementUtils.java | 6 +- .../annotation/AnnotationTypeMapping.java | 7 +- .../annotation/AnnotationTypeMappings.java | 2 +- .../core/annotation/AnnotationUtils.java | 35 +- .../core/annotation/AttributeMethods.java | 15 +- .../core/annotation/RepeatableContainers.java | 50 +- ...izedMergedAnnotationInvocationHandler.java | 2 +- .../core/annotation/TypeMappedAnnotation.java | 16 +- .../core/codec/StringDecoder.java | 2 +- .../core/convert/converter/Converter.java | 4 +- .../converter/ConvertingComparator.java | 2 +- .../core/env/ConfigurableEnvironment.java | 4 +- .../core/env/PropertySource.java | 8 +- .../io/AbstractFileResolvingResource.java | 3 +- .../core/io/DefaultResourceLoader.java | 2 +- .../springframework/core/io/UrlResource.java | 77 +- .../springframework/core/io/VfsResource.java | 3 +- .../core/io/buffer/DataBuffer.java | 2 +- .../core/io/buffer/DataBufferInputStream.java | 5 +- .../core/io/buffer/DataBufferUtils.java | 12 +- .../core/io/buffer/DefaultDataBuffer.java | 2 +- .../core/io/buffer/Netty5DataBuffer.java | 9 +- .../io/support/PropertiesLoaderSupport.java | 2 +- .../io/support/PropertiesLoaderUtils.java | 2 +- .../io/support/PropertySourceProcessor.java | 17 +- .../core/io/support/ResourceRegion.java | 6 +- .../jfr/FlightRecorderApplicationStartup.java | 2 +- ...AbstractTypeHierarchyTraversingFilter.java | 2 +- .../springframework/util/AntPathMatcher.java | 4 +- .../java/org/springframework/util/Assert.java | 57 +- .../util/ConcurrentLruCache.java | 54 +- .../springframework/util/FileCopyUtils.java | 2 +- .../org/springframework/util/MimeType.java | 2 +- .../springframework/util/MimeTypeUtils.java | 3 +- .../util/PropertiesPersister.java | 2 +- .../springframework/util/ResourceUtils.java | 61 +- .../org/springframework/util/StringUtils.java | 12 +- .../util/UnmodifiableMultiValueMap.java | 2 +- .../util/comparator/NullSafeComparator.java | 2 +- .../util/xml/SimpleNamespaceContext.java | 12 +- .../resources/META-INF/spring/aot.factories | 1 + .../springframework/SpringCoreTestSuite.java | 34 + .../generate/DefaultMethodReferenceTests.java | 3 +- .../BindingReflectionHintsRegistrarTests.java | 55 +- ...ectToObjectConverterRuntimeHintsTests.java | 55 + .../springframework/core/ConstantsTests.java | 106 +- .../core/GenericTypeResolverTests.java | 4 +- ...ableTableParameterNameDiscovererTests.java | 19 +- .../core/ResolvableTypeTests.java | 8 +- .../SpringCoreBlockHoundIntegrationTests.java | 16 +- .../AnnotatedElementUtilsTests.java | 49 +- .../annotation/AnnotationAttributesTests.java | 6 +- .../core/annotation/AnnotationUtilsTests.java | 12 +- .../annotation/AttributeMethodsTests.java | 20 +- .../ComposedRepeatableAnnotationsTests.java | 10 +- .../MergedAnnotationPredicatesTests.java | 2 +- ...ComposedOnSingleAnnotatedElementTests.java | 6 +- ...otationsOnSingleAnnotatedElementTests.java | 18 +- .../annotation/RepeatableContainersTests.java | 284 +- .../codec/ResourceRegionEncoderTests.java | 2 +- .../core/convert/TypeDescriptorTests.java | 34 +- .../DefaultConversionServiceTests.java | 163 +- .../CollectionToCollectionConverterTests.java | 4 +- .../GenericConversionServiceTests.java | 20 +- .../support/MapToMapConverterTests.java | 6 +- .../core/env/MutablePropertySourcesTests.java | 20 +- .../core/env/PropertySourceTests.java | 2 +- .../core/env/StandardEnvironmentTests.java | 24 +- .../core/io/buffer/DataBufferTests.java | 9 + .../SerializationConverterTests.java | 2 +- .../task/SimpleAsyncTaskExecutorTests.java | 2 +- .../core/type/AnnotationMetadataTests.java | 18 +- .../springframework/tests/MockitoUtils.java | 4 +- .../util/AntPathMatcherTests.java | 10 +- .../util/CollectionUtilsTests.java | 14 +- .../util/ConcurrentReferenceHashMapTests.java | 8 +- .../util/FastByteArrayOutputStreamTests.java | 6 +- .../util/LinkedCaseInsensitiveMapTests.java | 24 +- .../springframework/util/MimeTypeTests.java | 4 +- .../util/NumberUtilsTests.java | 144 +- .../util/ObjectUtilsTests.java | 6 +- .../util/ReflectionUtilsTests.java | 4 +- .../util/StringUtilsTests.java | 20 +- .../util/UnmodifiableMultiValueMapTests.java | 8 +- ...linBindingReflectionHintsRegistrarTests.kt | 13 + .../io/buffer/AbstractLeakCheckingTests.java | 2 +- .../expression/ExpressionException.java | 2 +- .../spel/ast/ConstructorReference.java | 50 +- .../expression/spel/ast/Elvis.java | 5 +- .../spel/ast/FunctionReference.java | 6 +- .../expression/spel/ast/Indexer.java | 6 +- .../expression/spel/ast/MethodReference.java | 14 +- .../expression/spel/ast/OperatorBetween.java | 5 +- .../spel/ast/OperatorInstanceof.java | 5 +- .../expression/spel/ast/Projection.java | 9 +- .../expression/spel/ast/Selection.java | 15 +- .../expression/spel/ast/SpelNodeImpl.java | 7 +- .../expression/spel/ast/StringLiteral.java | 21 +- .../expression/spel/ast/Ternary.java | 5 +- .../spel/standard/SpelCompiler.java | 6 +- .../spel/standard/SpelExpression.java | 22 +- .../support/ReflectiveMethodResolver.java | 2 +- .../spel/AbstractExpressionTests.java | 6 +- .../spel/ConstructorInvocationTests.java | 8 +- .../expression/spel/EvaluationTests.java | 81 +- .../spel/LiteralExpressionTests.java | 64 +- .../spel/MethodInvocationTests.java | 8 +- .../expression/spel/ParsingTests.java | 937 +- .../spel/SelectionAndProjectionTests.java | 6 +- .../spel/SpelCompilationCoverageTests.java | 2 +- .../spel/SpelDocumentationTests.java | 4 +- .../expression/spel/SpelReproTests.java | 6 +- .../spel/SpringExpressionTestSuite.java | 34 + .../spel/StandardTypeLocatorTests.java | 4 +- .../spel/TemplateExpressionParsingTests.java | 4 +- .../spel/ast/FormatHelperTests.java | 4 +- .../expression/spel/ast/OpPlusTests.java | 8 +- .../spel/support/ReflectionHelperTests.java | 12 +- .../spel/support/StandardComponentsTests.java | 8 +- .../apache/commons/logging/LogAdapter.java | 56 +- .../jdbc/core/JdbcTemplate.java | 62 +- .../core/PreparedStatementCreatorFactory.java | 12 +- .../core/RowMapperResultSetExtractor.java | 4 +- .../jdbc/core/SingleColumnRowMapper.java | 6 +- .../core/metadata/CallMetaDataContext.java | 6 +- .../AbstractSqlParameterSource.java | 6 +- .../NamedParameterJdbcTemplate.java | 2 +- .../core/namedparam/NamedParameterUtils.java | 8 +- .../jdbc/core/support/JdbcDaoSupport.java | 2 +- .../jdbc/core/support/SqlLobValue.java | 35 +- .../jdbc/datasource/ConnectionHolder.java | 4 +- .../DataSourceTransactionManager.java | 6 +- .../jdbc/datasource/DataSourceUtils.java | 8 +- .../datasource/SimpleDriverDataSource.java | 4 +- .../TransactionAwareDataSourceProxy.java | 6 +- .../embedded/EmbeddedDatabaseFactory.java | 6 +- .../embedded/EmbeddedDatabaseType.java | 2 +- .../jdbc/datasource/init/ScriptUtils.java | 6 +- .../jdbc/object/SqlFunction.java | 6 +- .../jdbc/support/JdbcUtils.java | 8 +- .../SQLErrorCodeSQLExceptionTranslator.java | 5 +- .../SQLExceptionSubclassTranslator.java | 26 +- .../SQLStateSQLExceptionTranslator.java | 92 +- .../SqlServerSequenceMaxValueIncrementer.java | 52 + .../jdbc/support/lob/PassThroughBlob.java | 2 +- .../jdbc/support/lob/PassThroughClob.java | 2 +- .../rowset/ResultSetWrappingSqlRowSet.java | 2 +- .../InitializeDatabaseIntegrationTests.java | 4 +- .../jdbc/core/DataClassRowMapperTests.java | 8 +- .../jdbc/core/JdbcTemplateQueryTests.java | 10 +- .../jdbc/core/JdbcTemplateTests.java | 20 +- .../jdbc/core/RowMapperTests.java | 4 +- .../NamedParameterJdbcTemplateTests.java | 10 +- .../namedparam/NamedParameterQueryTests.java | 8 +- .../namedparam/NamedParameterUtilsTests.java | 12 +- .../jdbc/object/BatchSqlUpdateTests.java | 8 +- .../jdbc/object/RdbmsOperationTests.java | 2 +- .../jdbc/object/SqlUpdateTests.java | 4 +- .../jdbc/object/StoredProcedureTests.java | 4 +- .../support/SQLErrorCodesFactoryTests.java | 14 +- .../SQLExceptionCustomTranslatorTests.java | 11 +- .../support/SQLExceptionSubclassFactory.java | 78 - .../SQLExceptionSubclassTranslatorTests.java | 108 +- .../SQLStateExceptionTranslatorTests.java | 77 - .../SQLStateSQLExceptionTranslatorTests.java | 78 +- .../DataFieldMaxValueIncrementerTests.java | 32 +- .../jms/core/support/JmsGatewaySupport.java | 2 +- .../AbstractAdaptableMessageListener.java | 29 +- .../jms/listener/adapter/JmsResponse.java | 10 +- .../adapter/MessageListenerAdapter.java | 14 +- .../MessagingMessageListenerAdapter.java | 9 +- .../MappingJackson2MessageConverter.java | 2 - .../MarshallingMessageConverter.java | 2 +- .../converter/MessagingMessageConverter.java | 9 +- .../AbstractJmsAnnotationDrivenTests.java | 18 +- .../jms/annotation/EnableJmsTests.java | 8 +- .../JmsListenerEndpointRegistrarTests.java | 6 +- .../jms/core/JmsTemplateTests.java | 4 +- .../MessagingMessageListenerAdapterTests.java | 4 +- .../support/SimpleMessageConverterTests.java | 4 +- .../messaging/MessageHeaders.java | 2 +- .../converter/JsonbMessageConverter.java | 2 +- .../MappingJackson2MessageConverter.java | 4 +- .../MarshallingMessageConverter.java | 6 +- .../MessageMappingReflectiveProcessor.java | 14 +- ...dlerMethodReturnValueHandlerComposite.java | 9 +- .../invocation/InvocableHandlerMethod.java | 4 +- .../AbstractMethodMessageHandler.java | 4 +- .../reactive/ArgumentResolverConfigurer.java | 10 +- .../reactive/ChannelSendOperator.java | 6 +- .../rsocket/DefaultRSocketRequester.java | 8 +- .../messaging/rsocket/MetadataEncoder.java | 16 +- .../messaging/rsocket/RSocketRequester.java | 4 +- .../service/RSocketServiceProxyFactory.java | 300 +- .../broker/AbstractBrokerMessageHandler.java | 4 +- .../stomp/StompBrokerRelayMessageHandler.java | 6 +- .../messaging/simp/stomp/StompDecoder.java | 7 +- .../messaging/support/ErrorMessage.java | 2 +- .../messaging/support/GenericMessage.java | 2 +- .../support/MessageHeaderAccessor.java | 2 +- .../reactor/ReactorNetty2TcpConnection.java | 4 + .../reactor/ReactorNettyTcpConnection.java | 5 + .../messaging/MessageHeadersTests.java | 4 +- .../PayloadMethodArgumentResolverTests.java | 6 +- .../InvocableHandlerMethodTests.java | 8 +- .../handler/invocation/ResolvableMethod.java | 6 +- .../reactive/InvocableHandlerMethodTests.java | 12 +- .../reactive/MethodMessageHandlerTests.java | 4 +- .../messaging/protobuf/Msg.java | 2 +- .../rsocket/DefaultRSocketRequesterTests.java | 4 +- .../messaging/simp/SimpAttributesTests.java | 4 +- .../simp/SimpMessagingTemplateTests.java | 8 +- .../DefaultSubscriptionRegistryTests.java | 46 +- .../SimpleBrokerMessageHandlerTests.java | 6 +- .../MessageBrokerConfigurationTests.java | 24 +- .../stomp/BufferingStompDecoderTests.java | 24 +- .../simp/stomp/DefaultStompSessionTests.java | 6 +- ...etty2StompBrokerRelayIntegrationTests.java | 3 - .../StompBrokerRelayMessageHandlerTests.java | 16 +- .../simp/stomp/StompDecoderTests.java | 38 +- .../simp/stomp/StompHeaderAccessorTests.java | 8 +- .../DefaultUserDestinationResolverTests.java | 22 +- .../user/MultiServerUserRegistryTests.java | 12 +- .../support/ChannelInterceptorTests.java | 6 +- .../support/MessageHeaderAccessorTests.java | 22 +- .../NativeMessageHeaderAccessorTests.java | 8 +- .../src/test/resources/log4j2-test.xml | 6 +- .../support/HibernateDaoSupport.java | 2 +- .../orm/jpa/SharedEntityManagerCreator.java | 36 +- .../PersistenceManagedTypesScanner.java | 3 +- .../PersistenceUnitReader.java | 6 +- .../jpa/support/InjectionCodeGenerator.java | 30 +- ...ersistenceAnnotationBeanPostProcessor.java | 27 +- ...rEntityManagerFactoryIntegrationTests.java | 10 +- ...AbstractEntityManagerFactoryBeanTests.java | 2 +- ...nManagedEntityManagerIntegrationTests.java | 2 +- ...rManagedEntityManagerIntegrationTests.java | 2 +- ...kEntityManagerFactoryIntegrationTests.java | 2 +- ...eEntityManagerFactoryIntegrationTests.java | 6 +- .../PersistenceXmlParsingTests.java | 48 +- .../support/PersistenceInjectionTests.java | 8 +- .../oxm/jaxb/Jaxb2Marshaller.java | 5 +- .../connection/ConnectionFactoryUtils.java | 16 +- .../r2dbc/connection/ConnectionHolder.java | 5 +- .../AbstractRoutingConnectionFactory.java | 4 +- .../r2dbc/core/DatabaseClient.java | 27 +- .../r2dbc/core/DefaultDatabaseClient.java | 19 +- .../r2dbc/core/NamedParameterExpander.java | 4 +- .../r2dbc/core/PreparedOperation.java | 10 +- .../core/binding/AnonymousBindMarkers.java | 2 +- .../core/binding/BindMarkersFactory.java | 4 +- .../ConnectionFactoryUtilsUnitTests.java | 35 +- .../mock/http/MockHttpInputMessage.java | 14 +- .../mock/http/MockHttpOutputMessage.java | 18 +- .../http/client/MockClientHttpRequest.java | 49 +- .../http/client/MockClientHttpResponse.java | 35 +- .../reactive/MockClientHttpResponse.java | 4 +- .../reactive/MockServerHttpRequest.java | 3 +- .../mock/web/HeaderValueHolder.java | 9 +- .../mock/web/MockBodyContent.java | 6 +- .../mock/web/MockHttpServletRequest.java | 51 +- .../mock/web/MockHttpServletResponse.java | 2 +- .../mock/web/MockHttpSession.java | 24 +- .../mock/web/MockJspWriter.java | 6 +- .../mock/web/MockPageContext.java | 12 +- .../mock/web/MockRequestDispatcher.java | 10 +- .../test/annotation/ProfileValueUtils.java | 4 +- .../test/context/ContextLoadException.java | 4 +- .../context/MergedContextConfiguration.java | 4 +- .../test/context/SmartContextLoader.java | 1 - .../test/context/TestContextManager.java | 120 +- .../test/context/TestPropertySource.java | 12 +- .../context/aot/TestContextAotGenerator.java | 57 +- .../aot/TestRuntimeHintsRegistrar.java | 6 + ...efaultCacheAwareContextLoaderDelegate.java | 18 +- .../context/cache/DefaultContextCache.java | 6 +- .../test/context/jdbc/MergedSqlConfig.java | 2 +- .../test/context/jdbc/Sql.java | 6 +- ...AbstractExpressionEvaluatingCondition.java | 19 +- .../junit4/SpringJUnit4ClassRunner.java | 7 +- .../test/context/junit4/SpringRunner.java | 4 +- .../context/junit4/rules/SpringClassRule.java | 4 +- .../junit4/rules/SpringMethodRule.java | 4 +- .../RunBeforeTestExecutionCallbacks.java | 4 +- .../support/AbstractGenericContextLoader.java | 2 +- .../context/support/TestConstructorUtils.java | 6 +- .../AbstractTestNGSpringContextTests.java | 2 +- .../util/TestContextResourceUtils.java | 2 +- .../web/ServletTestExecutionListener.java | 12 +- .../test/util/TestSocketUtils.java | 139 + .../test/util/XmlExpectationsHelper.java | 4 +- .../client/match/ContentRequestMatchers.java | 4 +- .../client/match/JsonPathRequestMatchers.java | 4 +- .../server/DefaultWebTestClientBuilder.java | 5 +- .../web/reactive/server/WiretapConnector.java | 10 +- .../test/web/servlet/MockMvc.java | 12 +- .../web/servlet/TestDispatcherServlet.java | 6 +- .../servlet/client/MockMvcHttpConnector.java | 4 +- .../servlet/client/MockMvcWebTestClient.java | 14 +- .../htmlunit/HtmlUnitRequestBuilder.java | 11 +- ...ockMultipartHttpServletRequestBuilder.java | 6 +- .../servlet/result/ContentResultMatchers.java | 2 +- .../servlet/result/HandlerResultMatchers.java | 17 +- .../servlet/result/ModelResultMatchers.java | 10 +- .../servlet/result/XpathResultMatchers.java | 4 +- .../servlet/setup/AbstractMockMvcBuilder.java | 6 +- .../setup/StubWebApplicationContext.java | 14 +- .../mock/web/MockHttpServletRequestTests.java | 14 +- .../web/MockHttpServletResponseTests.java | 14 +- .../MockMultipartHttpServletRequestTests.java | 6 +- .../mock/web/MockServletContextTests.java | 6 +- .../context/TestContextConcurrencyTests.java | 4 +- ...ntextManagerSuppressedExceptionsTests.java | 4 +- .../test/context/TestContextManagerTests.java | 9 +- .../aot/DeclarativeRuntimeHintsTests.java | 69 + ...arativeRuntimeHintsSpringJupiterTests.java | 76 + .../BootstrapWithTestInterface.java | 4 +- .../ContextConfigurationTestInterface.java | 4 +- .../WebAppConfigurationTestInterface.java | 4 +- .../web/ControllerIntegrationTests.java | 5 +- .../web/DispatcherWacRootWacEarTests.java | 8 +- .../context/hierarchies/web/EarTests.java | 5 +- .../hierarchies/web/RootWacEarTests.java | 5 +- .../context/jdbc/EmptyDatabaseConfig.java | 2 +- .../jupiter/ComposedSpringExtensionTests.java | 6 +- .../jupiter/DisabledIfConditionTests.java | 6 +- .../junit/jupiter/DisabledIfTests.java | 6 +- .../context/junit/jupiter/EnabledIfTests.java | 6 +- ...reAndAfterMethodsSpringExtensionTests.java | 6 +- ...RegisterExtensionSpringExtensionTests.java | 5 - .../SpringExtensionParameterizedTests.java | 6 +- .../junit/jupiter/SpringExtensionTests.java | 4 - ...terAutowiredConstructorInjectionTests.java | 6 +- ...JUnitJupiterConstructorInjectionTests.java | 6 +- ...ConstructorAnnotationIntegrationTests.java | 6 +- .../CatInterfaceDefaultMethodsTests.java | 6 +- .../DogInterfaceDefaultMethodsTests.java | 6 +- ...haractersInterfaceDefaultMethodsTests.java | 6 +- .../junit/jupiter/generics/CatTests.java | 6 +- .../junit/jupiter/generics/DogTests.java | 6 +- .../generics/GenericComicCharactersTests.java | 6 +- .../generics/GenericsAndNestedTests.java | 7 +- .../ConstructorInjectionNestedTests.java | 7 +- ...ltipleWebRequestsSpringExtensionTests.java | 6 +- .../jupiter/web/WebSpringExtensionTests.java | 6 +- ...lizerConfiguredViaMetaAnnotationTests.java | 4 +- ...eResolverWithCustomDefaultsMetaConfig.java | 8 +- ...dProfilesWithCustomDefaultsMetaConfig.java | 4 +- .../ProgrammaticTxMgmtSpringRuleTests.java | 25 +- ...figClassesWithoutAtConfigurationTests.java | 11 +- ...notationConfigContextLoaderUtilsTests.java | 2 +- ...aderUtilsConfigurationAttributesTests.java | 22 +- ...ntextLoaderUtilsContextHierarchyTests.java | 62 +- .../DelegatingSmartContextLoaderTests.java | 2 +- .../support/TestPropertySourceUtilsTests.java | 12 +- .../ProgrammaticTxMgmtTestNGTests.java | 25 +- .../programmatic/ProgrammaticTxMgmtTests.java | 25 +- .../test/util/ReflectionTestUtilsTests.java | 50 +- .../test/util/TestSocketUtilsTests.java | 61 + .../DefaultRequestExpectationTests.java | 21 +- .../client/MockRestServiceServerTests.java | 31 +- .../SimpleRequestExpectationManagerTests.java | 111 +- ...orderedRequestExpectationManagerTests.java | 68 +- .../match/MockRestRequestMatchersTests.java | 64 +- .../response/ResponseCreatorsTests.java | 46 +- ...ontentRequestMatchersIntegrationTests.java | 50 +- ...HeaderRequestMatchersIntegrationTests.java | 32 +- ...onPathRequestMatchersIntegrationTests.java | 27 +- ...ontentRequestMatchersIntegrationTests.java | 15 +- .../XpathRequestMatchersIntegrationTests.java | 4 +- .../web/reactive/server/MockServerTests.java | 20 +- .../server/samples/SoftAssertionTests.java | 9 +- .../DelegatingWebConnectionTests.java | 17 +- .../htmlunit/HtmlUnitRequestBuilderTests.java | 12 +- .../htmlunit/MockWebResponseBuilderTests.java | 6 +- .../MockHttpServletRequestBuilderTests.java | 70 +- .../result/ContentResultMatchersTests.java | 47 +- .../result/JsonPathResultMatchersTests.java | 126 +- .../result/PrintingResultHandlerTests.java | 52 +- .../result/StatusResultMatchersTests.java | 25 +- .../standalone/ExceptionHandlerTests.java | 15 +- .../resultmatches/ContentAssertionTests.java | 54 +- .../resultmatches/XpathAssertionTests.java | 13 +- .../samples/context/JavaConfigTests.java | 5 +- .../spr/MockMvcBuilderMethodChainTests.java | 29 +- .../standalone/ExceptionHandlerTests.java | 15 +- .../standalone/MultipartControllerTests.java | 18 +- .../PrintingResultHandlerSmokeTests.java | 4 +- .../resultmatchers/ContentAssertionTests.java | 10 +- .../resultmatchers/XpathAssertionTests.java | 19 +- .../web/servlet/MockMvcExtensionsTests.kt | 18 +- .../dao/CannotAcquireLockException.java | 5 +- .../CannotSerializeTransactionException.java | 8 +- .../CleanupFailureDataAccessException.java | 4 +- .../dao/ConcurrencyFailureException.java | 10 +- .../dao/DataIntegrityViolationException.java | 11 +- .../dao/DeadlockLoserDataAccessException.java | 8 +- .../dao/DuplicateKeyException.java | 5 +- .../PessimisticLockingFailureException.java | 10 +- .../transaction/TransactionDefinition.java | 28 +- .../transaction/annotation/Isolation.java | 45 +- .../TransactionManagementConfigurer.java | 8 +- .../TransactionAttributeSourceEditor.java | 2 +- .../reactive/TransactionalOperatorImpl.java | 4 +- .../dao/support/DataAccessUtilsTests.java | 4 +- ...ationTransactionNamespaceHandlerTests.java | 4 +- .../EnableTransactionManagementTests.java | 4 +- spring-web/spring-web.gradle | 8 +- .../http/ContentDisposition.java | 22 +- .../org/springframework/http/HttpHeaders.java | 5 +- .../springframework/http/HttpStatusCode.java | 3 +- .../org/springframework/http/MediaType.java | 32 +- .../springframework/http/ProblemDetail.java | 123 +- .../http/ReadOnlyHttpHeaders.java | 2 +- .../springframework/http/ResponseEntity.java | 14 +- .../BufferingClientHttpRequestWrapper.java | 2 +- .../HttpComponentsClientHttpRequest.java | 1 + ...ttpComponentsClientHttpRequestFactory.java | 14 +- ...pComponentsStreamingClientHttpRequest.java | 1 + .../client/InterceptingClientHttpRequest.java | 2 +- .../http/client/OkHttp3ClientHttpRequest.java | 2 +- .../client/OkHttp3ClientHttpResponse.java | 2 +- .../SimpleBufferingClientHttpRequest.java | 2 +- .../SimpleStreamingClientHttpRequest.java | 2 +- ...ultClientRequestObservationConvention.java | 5 +- .../reactive/ClientHttpResponseDecorator.java | 2 +- .../HttpComponentsClientHttpConnector.java | 8 +- .../HttpComponentsClientHttpResponse.java | 2 +- .../reactive/JdkClientHttpResponse.java | 2 +- .../reactive/JettyClientHttpResponse.java | 2 +- .../client/reactive/JettyHeadersAdapter.java | 10 +- .../client/reactive/JettyResourceFactory.java | 16 +- .../client/reactive/NettyHeadersAdapter.java | 2 +- .../client/support/HttpRequestWrapper.java | 2 +- .../support/InterceptingHttpAccessor.java | 6 +- .../http/codec/ClientCodecConfigurer.java | 42 +- .../http/codec/CodecConfigurer.java | 41 + .../http/codec/DecoderHttpMessageReader.java | 13 +- .../http/codec/EncoderHttpMessageWriter.java | 15 +- .../http/codec/ResourceHttpMessageWriter.java | 6 +- .../http/codec/ServerCodecConfigurer.java | 20 +- .../ServerSentEventHttpMessageWriter.java | 15 +- .../codec/json/AbstractJackson2Decoder.java | 12 +- .../codec/json/AbstractJackson2Encoder.java | 2 +- .../http/codec/json/Jackson2CodecSupport.java | 4 +- .../http/codec/json/Jackson2JsonDecoder.java | 4 +- .../http/codec/json/Jackson2JsonEncoder.java | 4 +- .../http/codec/json/Jackson2SmileDecoder.java | 4 +- .../http/codec/json/Jackson2SmileEncoder.java | 4 +- .../DefaultPartHttpMessageReader.java | 4 +- .../multipart/MultipartHttpMessageReader.java | 4 +- .../multipart/MultipartHttpMessageWriter.java | 39 +- .../multipart/MultipartWriterSupport.java | 17 - .../multipart/PartHttpMessageWriter.java | 17 +- .../codec/support/BaseCodecConfigurer.java | 18 +- .../http/codec/support/BaseDefaultCodecs.java | 197 +- .../support/ClientDefaultCodecsImpl.java | 94 +- .../support/DefaultClientCodecConfigurer.java | 17 +- .../support/ServerDefaultCodecsImpl.java | 36 +- .../ByteArrayHttpMessageConverter.java | 2 +- ...ppingJackson2CborHttpMessageConverter.java | 4 +- .../AbstractJackson2HttpMessageConverter.java | 2 - .../json/Jackson2ObjectMapperBuilder.java | 22 +- .../json/Jackson2ObjectMapperFactoryBean.java | 6 +- .../json/JsonbHttpMessageConverter.java | 2 +- .../MappingJackson2HttpMessageConverter.java | 4 +- ...pingJackson2SmileHttpMessageConverter.java | 4 +- ...lEncompassingFormHttpMessageConverter.java | 10 +- ...appingJackson2XmlHttpMessageConverter.java | 4 +- .../xml/MarshallingHttpMessageConverter.java | 12 +- .../xml/SourceHttpMessageConverter.java | 16 +- .../http/server/RequestPath.java | 4 +- .../server/ServletServerHttpResponse.java | 4 +- ...ultServerRequestObservationConvention.java | 6 +- .../ServerHttpObservationDocumentation.java | 6 +- .../ServerRequestObservationContext.java | 8 +- .../ServerRequestObservationConvention.java | 4 +- .../observation/package-info.java | 4 +- .../AbstractListenerReadPublisher.java | 2 +- .../AbstractListenerWriteProcessor.java | 4 +- .../reactive/AbstractServerHttpResponse.java | 8 +- .../server/reactive/ChannelSendOperator.java | 6 +- .../http/server/reactive/DefaultSslInfo.java | 6 +- .../server/reactive/JettyHeadersAdapter.java | 20 +- .../reactive/JettyHttpHandlerAdapter.java | 112 +- .../ReactorNetty2ServerHttpRequest.java | 8 +- .../reactive/ReactorServerHttpRequest.java | 8 +- .../reactive/ServerHttpRequestDecorator.java | 10 +- .../server/reactive/ServerHttpResponse.java | 2 +- .../reactive/ServerHttpResponseDecorator.java | 8 +- .../reactive/ServletHttpHandlerAdapter.java | 21 +- .../reactive/ServletServerHttpRequest.java | 16 +- .../reactive/ServletServerHttpResponse.java | 23 +- .../server/reactive/TomcatHeadersAdapter.java | 2 +- .../reactive/TomcatHttpHandlerAdapter.java | 43 +- .../reactive/UndertowServerHttpResponse.java | 2 +- ...ultServerRequestObservationConvention.java | 13 +- .../ServerHttpObservationDocumentation.java | 6 +- .../ServerRequestObservationContext.java | 37 +- .../ServerRequestObservationConvention.java | 4 +- .../reactive/observation}/package-info.java | 4 +- .../web/DefaultErrorResponseBuilder.java | 209 + .../springframework/web/ErrorResponse.java | 170 +- .../web/ErrorResponseException.java | 4 +- .../web/accept/ContentNegotiationManager.java | 10 +- .../bind/MethodArgumentNotValidException.java | 44 +- ...sfiedServletRequestParameterException.java | 2 +- .../ControllerMappingReflectiveProcessor.java | 14 +- .../ExceptionHandlerReflectiveProcessor.java | 1 + .../web/bind/annotation/InitBinder.java | 4 + .../web/bind/annotation/ModelAttribute.java | 3 + .../support/WebExchangeBindException.java | 33 +- .../bind/support/WebExchangeDataBinder.java | 2 +- .../ExtractingResponseErrorHandler.java | 3 +- .../client/HttpMessageConverterExtractor.java | 8 +- .../web/client/RestOperations.java | 19 +- .../web/client/RestTemplate.java | 52 +- .../client/support/RestGatewaySupport.java | 2 +- .../web/context/ContextCleanupListener.java | 6 +- .../AbstractRequestAttributesScope.java | 2 +- .../request/RequestContextListener.java | 13 +- .../context/request/ServletWebRequest.java | 25 +- .../async/StandardServletAsyncWebRequest.java | 4 +- ...tractRefreshableWebApplicationContext.java | 6 +- .../support/GenericWebApplicationContext.java | 4 +- .../support/ServletContextAwareProcessor.java | 10 +- .../support/StaticWebApplicationContext.java | 2 +- .../support/WebApplicationContextUtils.java | 26 +- .../support/WebApplicationObjectSupport.java | 10 +- .../RelativeRedirectResponseWrapper.java | 2 +- .../filter/ServerHttpObservationFilter.java | 14 +- .../reactive/ServerHttpObservationFilter.java | 21 +- .../jsf/DelegatingNavigationHandlerProxy.java | 6 +- .../web/jsf/FacesContextUtils.java | 14 +- .../web/method/ControllerAdviceBean.java | 6 +- .../web/method/HandlerTypePredicate.java | 2 +- .../method/annotation/MapMethodProcessor.java | 6 +- .../ModelAttributeMethodProcessor.java | 6 +- .../annotation/ModelMethodProcessor.java | 6 +- .../RequestParamMethodArgumentResolver.java | 14 +- .../HandlerMethodReturnValueHandler.java | 4 +- ...dlerMethodReturnValueHandlerComposite.java | 6 +- .../support/InvocableHandlerMethod.java | 20 +- .../method/support/ModelAndViewContainer.java | 4 +- .../web/multipart/MultipartFileResource.java | 8 +- .../StandardServletMultipartResolver.java | 6 +- .../web/server/MethodNotAllowedException.java | 5 +- .../server/MissingRequestValueException.java | 1 - .../server/NotAcceptableStatusException.java | 4 +- .../web/server/ResponseStatusException.java | 9 +- .../web/server/ServerErrorException.java | 2 +- .../UnsatisfiedRequestParameterException.java | 2 +- .../UnsupportedMediaTypeStatusException.java | 2 +- .../AbstractReactiveWebInitializer.java | 10 +- .../adapter/DefaultServerWebExchange.java | 22 +- .../server/adapter/HttpWebHandlerAdapter.java | 10 +- .../server/adapter/WebHttpHandlerBuilder.java | 7 +- .../ResponseStatusExceptionHandler.java | 4 +- .../web/server/session/WebSessionStore.java | 4 +- .../HttpExchangeReflectiveProcessor.java | 3 +- .../service/invoker/HttpRequestValues.java | 50 +- .../invoker/HttpServiceProxyFactory.java | 326 +- .../service/invoker/UrlArgumentResolver.java | 1 - .../web/util/JavaScriptUtils.java | 2 +- .../web/util/ServletRequestPathUtils.java | 4 +- .../springframework/web/util/UriBuilder.java | 4 +- .../web/util/UriComponentsBuilder.java | 14 +- .../pattern/CaptureTheRestPathElement.java | 10 +- .../web/util/pattern/LiteralPathElement.java | 6 +- .../web/util/pattern/PathElement.java | 11 +- .../web/util/pattern/PathPattern.java | 2 +- .../util/pattern/SeparatorPathElement.java | 6 +- .../http/ContentDispositionTests.java | 34 +- .../springframework/http/HttpEntityTests.java | 6 +- .../http/HttpHeadersTests.java | 20 +- .../springframework/http/HttpRangeTests.java | 8 +- .../springframework/http/MediaTypeTests.java | 12 +- .../http/ProblemDetailTests.java | 53 + .../http/RequestEntityTests.java | 25 +- .../http/ResponseEntityTests.java | 31 +- .../AbstractHttpRequestFactoryTests.java | 22 +- ...ufferingClientHttpRequestFactoryTests.java | 4 +- ...mponentsClientHttpRequestFactoryTests.java | 23 +- ...rceptingClientHttpRequestFactoryTests.java | 16 +- .../client/MultipartBodyBuilderTests.java | 4 +- .../client/SimpleClientHttpResponseTests.java | 2 +- ...ngSimpleClientHttpRequestFactoryTests.java | 6 +- .../client/support/ProxyFactoryBeanTests.java | 28 +- .../MultipartHttpMessageWriterTests.java | 10 +- .../PartEventHttpMessageWriterTests.java | 4 +- .../multipart/PartHttpMessageWriterTests.java | 24 +- .../support/ClientCodecConfigurerTests.java | 24 +- .../codec/support/CodecConfigurerTests.java | 30 +- .../support/ServerCodecConfigurerTests.java | 8 +- .../http/codec/xml/Jaxb2XmlDecoderTests.java | 6 +- ...ufferedImageHttpMessageConverterTests.java | 32 +- .../ByteArrayHttpMessageConverterTests.java | 6 +- .../FormHttpMessageConverterTests.java | 95 +- ...jectToStringHttpMessageConverterTests.java | 12 +- .../ResourceHttpMessageConverterTests.java | 6 +- ...sourceRegionHttpMessageConverterTests.java | 4 +- .../StringHttpMessageConverterTests.java | 6 +- .../AtomFeedHttpMessageConverterTests.java | 6 +- .../RssChannelHttpMessageConverterTests.java | 6 +- .../json/GsonHttpMessageConverterTests.java | 10 +- .../json/JsonbHttpMessageConverterTests.java | 10 +- ...pingJackson2HttpMessageConverterTests.java | 10 +- .../ProtobufHttpMessageConverterTests.java | 4 +- ...ufJsonFormatHttpMessageConverterTests.java | 4 +- ...ackson2SmileHttpMessageConverterTests.java | 4 +- ...b2CollectionHttpMessageConverterTests.java | 6 +- ...2RootElementHttpMessageConverterTests.java | 4 +- ...gJackson2XmlHttpMessageConverterTests.java | 4 +- .../MarshallingHttpMessageConverterTests.java | 4 +- .../xml/SourceHttpMessageConverterTests.java | 4 +- .../server/ServletServerHttpRequestTests.java | 12 +- ...rverRequestObservationConventionTests.java | 2 +- .../reactive/AsyncIntegrationTests.java | 2 +- .../reactive/ChannelSendOperatorTests.java | 8 +- .../reactive/CookieIntegrationTests.java | 12 +- .../reactive/EchoHandlerIntegrationTests.java | 2 +- .../ErrorHandlerIntegrationTests.java | 8 +- .../server/reactive/HeadersAdaptersTests.java | 8 +- .../reactive/ListenerWriteProcessorTests.java | 8 +- ...MultipartHttpHandlerIntegrationTests.java} | 23 +- .../RandomHandlerIntegrationTests.java | 4 +- .../ServerHttpRequestIntegrationTests.java | 4 +- .../reactive/ServerHttpRequestTests.java | 14 +- .../reactive/ServerHttpResponseTests.java | 6 +- .../ServerHttpsRequestIntegrationTests.java | 4 +- .../WriteOnlyHandlerIntegrationTests.java | 5 +- .../reactive/ZeroCopyIntegrationTests.java | 4 +- ...rverRequestObservationConventionTests.java | 35 +- .../org/springframework/protobuf/Msg.java | 2 +- .../web/ErrorResponseExceptionTests.java | 36 +- ...HeaderContentNegotiationStrategyTests.java | 6 +- ...appingContentNegotiationStrategyTests.java | 6 +- .../support/WebExchangeDataBinderTests.java | 4 +- .../WebRequestDataBinderIntegrationTests.java | 4 +- .../support/WebRequestDataBinderTests.java | 6 +- .../client/AbstractMockWebServerTests.java | 4 +- .../client/RestTemplateIntegrationTests.java | 10 +- .../client/RestTemplateObservationTests.java | 4 +- .../web/client/RestTemplateTests.java | 10 +- .../ServletRequestAttributesTests.java | 4 +- .../request/ServletWebRequestTests.java | 12 +- .../context/request/SessionScopeTests.java | 6 +- .../StandardServletAsyncWebRequestTests.java | 6 +- .../web/filter/FormContentFilterTests.java | 4 +- .../ServerHttpObservationFilterTests.java | 15 +- .../filter/ShallowEtagHeaderFilterTests.java | 2 +- .../ServerHttpObservationFilterTests.java | 2 +- .../InitBinderDataBinderFactoryTests.java | 4 +- .../method/annotation/ModelFactoryTests.java | 10 +- ...stParamMapMethodArgumentResolverTests.java | 18 +- ...questParamMethodArgumentResolverTests.java | 6 +- .../support/InvocableHandlerMethodTests.java | 8 +- .../support/ModelAndViewContainerTests.java | 8 +- ...faultMultipartHttpServletRequestTests.java | 4 +- ...uestPartServletServerHttpRequestTests.java | 4 +- ...ndardMultipartHttpServletRequestTests.java | 4 +- ...erverWebExchangeCheckNotModifiedTests.java | 2 +- .../ForwardedHeaderTransformerTests.java | 38 +- .../ResponseStatusExceptionHandlerTests.java | 2 +- .../CookieWebSessionIdResolverTests.java | 6 +- .../session/InMemoryWebSessionStoreTests.java | 8 +- .../session/WebSessionIntegrationTests.java | 18 +- .../CookieValueArgumentResolverTests.java | 13 +- .../HttpMethodArgumentResolverTests.java | 14 +- .../invoker/HttpRequestValuesTests.java | 10 +- .../invoker/HttpServiceMethodTests.java | 14 +- .../NamedValueArgumentResolverTests.java | 2 - .../PathVariableArgumentResolverTests.java | 15 +- ...RequestAttributeArgumentResolverTests.java | 12 +- .../RequestBodyArgumentResolverTests.java | 12 +- .../RequestParamArgumentResolverTests.java | 26 +- .../RequestPartArgumentResolverTests.java | 2 +- .../invoker/UrlArgumentResolverTests.java | 9 +- .../web/util/UriComponentsBuilderTests.java | 33 +- .../web/util/UriComponentsTests.java | 17 +- .../web/util/UriTemplateTests.java | 64 +- .../web/util/WebUtilsTests.java | 2 +- .../web/util/pattern/PathPatternTests.java | 7 +- ...ializationCborHttpMessageConverterTests.kt | 4 +- ...ializationJsonHttpMessageConverterTests.kt | 4 +- ...zationProtobufHttpMessageConverterTests.kt | 4 +- .../http/MockHttpInputMessage.java | 24 +- .../http/MockHttpOutputMessage.java | 59 +- .../http/client/MockClientHttpRequest.java | 108 +- .../http/client/MockClientHttpResponse.java | 87 +- .../reactive/MockClientHttpRequest.java | 2 +- .../reactive/MockClientHttpResponse.java | 2 +- .../reactive/MockServerHttpRequest.java | 2 +- .../testfixture/method/ResolvableMethod.java | 6 +- .../servlet/HeaderValueHolder.java | 9 +- .../testfixture/servlet/MockBodyContent.java | 6 +- .../servlet/MockHttpServletRequest.java | 51 +- .../testfixture/servlet/MockHttpSession.java | 24 +- .../testfixture/servlet/MockJspWriter.java | 6 +- .../testfixture/servlet/MockPageContext.java | 12 +- .../servlet/MockRequestDispatcher.java | 10 +- .../reactive/DispatchExceptionHandler.java | 52 + .../web/reactive/DispatcherHandler.java | 51 +- .../web/reactive/HandlerAdapter.java | 45 +- .../web/reactive/HandlerResult.java | 47 +- .../web/reactive/HandlerResultHandler.java | 4 +- .../RequestedContentTypeResolverBuilder.java | 2 +- .../config/ResourceHandlerRegistry.java | 6 +- .../config/WebFluxConfigurerComposite.java | 2 +- .../web/reactive/function/BodyExtractor.java | 6 +- .../web/reactive/function/BodyExtractors.java | 2 +- .../web/reactive/function/BodyInserters.java | 9 +- ...ultClientRequestObservationConvention.java | 5 +- .../client/DefaultClientResponse.java | 8 +- .../client/DefaultClientResponseBuilder.java | 4 +- .../DefaultExchangeStrategiesBuilder.java | 12 +- .../function/client/DefaultWebClient.java | 30 +- .../function/client/ExchangeFunctions.java | 2 +- .../client/support/ClientResponseWrapper.java | 2 +- .../client/support/WebClientAdapter.java | 31 - .../DefaultHandlerStrategiesBuilder.java | 17 +- .../DefaultRenderingResponseBuilder.java | 4 +- .../function/server/DefaultServerRequest.java | 5 +- .../server/PathResourceLookupFunction.java | 6 +- .../function/server/RequestPredicates.java | 20 +- .../function/server/ServerResponse.java | 13 + .../server/support/RouterFunctionMapping.java | 2 +- .../handler/AbstractHandlerMapping.java | 4 +- .../handler/AbstractUrlHandlerMapping.java | 4 +- .../handler/SimpleUrlHandlerMapping.java | 6 +- .../resource/EncodedResourceResolver.java | 6 +- .../reactive/resource/ResourceWebHandler.java | 2 +- .../resource/WebJarsResourceResolver.java | 2 +- .../method/AbstractHandlerMethodMapping.java | 7 +- .../result/method/InvocableHandlerMethod.java | 23 +- .../RequestMappingInfoHandlerMapping.java | 21 +- .../method/SyncInvocableHandlerMethod.java | 2 +- ...AbstractMessageReaderArgumentResolver.java | 13 +- .../annotation/ControllerMethodResolver.java | 43 +- .../ModelAttributeMethodArgumentResolver.java | 4 +- .../method/annotation/ModelInitializer.java | 2 +- .../RequestMappingHandlerAdapter.java | 32 +- .../RequestMappingHandlerMapping.java | 6 +- .../ResponseEntityExceptionHandler.java | 27 +- .../ResponseEntityResultHandler.java | 8 +- ...rverWebExchangeMethodArgumentResolver.java | 6 +- .../web/reactive/result/view/BindStatus.java | 14 +- .../reactive/result/view/RedirectView.java | 7 - .../result/view/UrlBasedViewResolver.java | 6 +- .../view/ViewResolutionResultHandler.java | 4 +- .../web/reactive/socket/HandshakeInfo.java | 6 +- .../AbstractListenerWebSocketSession.java | 2 +- .../adapter/AbstractWebSocketSession.java | 15 +- .../StandardWebSocketHandlerAdapter.java | 6 +- .../socket/client/JettyWebSocketClient.java | 3 + .../client/StandardWebSocketClient.java | 2 +- .../socket/client/TomcatWebSocketClient.java | 7 +- .../socket/server/RequestUpgradeStrategy.java | 5 +- .../support/HandshakeWebSocketService.java | 116 +- .../support/WebSocketHandlerAdapter.java | 2 +- .../upgrade/JettyRequestUpgradeStrategy.java | 4 +- .../ReactorNetty2RequestUpgradeStrategy.java | 2 +- .../ReactorNettyRequestUpgradeStrategy.java | 4 +- .../StandardWebSocketUpgradeStrategy.java | 199 + .../upgrade/TomcatRequestUpgradeStrategy.java | 162 +- .../UndertowRequestUpgradeStrategy.java | 2 +- .../HeaderContentTypeResolverTests.java | 4 +- .../reactive/config/CorsRegistryTests.java | 6 +- .../DelegatingWebFluxConfigurationTests.java | 2 +- .../config/ResourceHandlerRegistryTests.java | 6 +- .../config/ViewResolverRegistryTests.java | 10 +- .../WebFluxConfigurationSupportTests.java | 14 +- ...tipartRouterFunctionIntegrationTests.java} | 8 +- .../DefaultClientRequestBuilderTests.java | 31 +- .../DefaultClientResponseBuilderTests.java | 4 +- .../client/DefaultWebClientTests.java | 18 +- .../WebClientDataBufferAllocatingTests.java | 4 +- .../WebClientHttpServiceProxyTests.java | 50 +- .../server/DefaultRenderingResponseTests.java | 2 +- .../server/DefaultServerRequestTests.java | 4 +- .../DispatcherHandlerIntegrationTests.java | 6 +- ...lisherHandlerFunctionIntegrationTests.java | 4 +- .../RenderingResponseIntegrationTests.java | 6 +- .../support/RouterFunctionMappingTests.java | 6 +- .../web/reactive/protobuf/Msg.java | 2 +- ...mpleUrlHandlerMappingIntegrationTests.java | 12 +- .../HeadersRequestConditionTests.java | 4 +- .../ParamsRequestConditionTests.java | 4 +- .../PatternsRequestConditionTests.java | 2 +- .../condition/RequestMappingInfoTests.java | 4 +- .../RequestMethodsRequestConditionTests.java | 4 +- ...RequestMappingInfoHandlerMappingTests.java | 21 +- ...bstractRequestMappingIntegrationTests.java | 38 +- .../annotation/ControllerAdviceTests.java | 2 +- .../ControllerInputIntegrationTests.java | 7 +- .../InitBinderBindingContextTests.java | 4 +- .../annotation/ModelInitializerTests.java | 12 +- ...> MultipartWebClientIntegrationTests.java} | 10 +- ...equestBodyMethodArgumentResolverTests.java | 21 +- ...pingExceptionHandlingIntegrationTests.java | 78 +- .../RequestMappingHandlerMappingTests.java | 6 +- .../RequestMappingIntegrationTests.java | 2 +- ...pingMessageConversionIntegrationTests.java | 22 +- ...MappingViewResolutionIntegrationTests.java | 9 +- .../ResponseEntityExceptionHandlerTests.java | 2 +- .../ResponseEntityResultHandlerTests.java | 97 +- .../view/DefaultRenderingBuilderTests.java | 6 +- .../script/KotlinScriptTemplateTests.java | 2 +- ...actReactiveWebSocketIntegrationTests.java} | 18 +- .../socket/WebSocketIntegrationTests.java | 2 +- .../KotlinInvocableHandlerMethodTests.kt | 11 +- .../web/servlet/FrameworkServlet.java | 17 +- .../web/servlet/NoHandlerFoundException.java | 21 +- .../AnnotationDrivenBeanDefinitionParser.java | 2 - .../web/servlet/config/MvcNamespaceUtils.java | 6 +- .../annotation/InterceptorRegistry.java | 2 +- .../WebMvcConfigurationSupport.java | 8 - .../DefaultRenderingResponseBuilder.java | 4 +- .../function/DefaultServerRequest.java | 2 +- .../web/servlet/function/ServerResponse.java | 13 + .../support/RouterFunctionMapping.java | 7 - .../handler/AbstractHandlerMethodMapping.java | 5 +- .../handler/AbstractUrlHandlerMapping.java | 2 +- .../handler/HandlerMappingIntrospector.java | 4 +- .../PathPatternMatchableHandlerMapping.java | 4 +- .../servlet/handler/RequestMatchResult.java | 4 +- .../RequestMappingInfoHandlerMapping.java | 8 +- .../ExceptionHandlerExceptionResolver.java | 7 - .../annotation/MvcUriComponentsBuilder.java | 2 +- .../RequestMappingHandlerAdapter.java | 7 - .../RequestMappingHandlerMapping.java | 6 +- .../ResponseEntityExceptionHandler.java | 27 +- .../annotation/StreamingResponseBody.java | 2 +- .../resource/ResourceHttpRequestHandler.java | 6 +- .../resource/WebJarsResourceResolver.java | 2 +- .../AbstractDispatcherServletInitializer.java | 9 +- .../web/servlet/support/BindStatus.java | 14 +- .../web/servlet/tags/ParamAware.java | 2 +- .../tags/form/AbstractHtmlElementBodyTag.java | 2 +- .../servlet/view/ViewResolverComposite.java | 14 +- .../view/groovy/GroovyMarkupConfigurer.java | 2 +- .../view/json/AbstractJackson2View.java | 4 +- .../view/json/MappingJackson2JsonView.java | 2 - .../view/xml/MappingJackson2XmlView.java | 4 +- .../web/servlet/view/xml/MarshallingView.java | 4 +- .../support/ServletContextSupportTests.java | 10 +- .../web/servlet/DispatcherServletTests.java | 8 + .../web/servlet/FlashMapTests.java | 8 +- .../servlet/SimpleWebApplicationContext.java | 2 +- ...tationDrivenBeanDefinitionParserTests.java | 4 +- .../web/servlet/config/MvcNamespaceTests.java | 50 +- .../config/annotation/CorsRegistryTests.java | 6 +- .../DelegatingWebMvcConfigurationTests.java | 6 +- .../annotation/InterceptorRegistryTests.java | 14 +- .../ResourceHandlerRegistryTests.java | 4 +- .../annotation/ViewResolverRegistryTests.java | 10 +- ...MvcConfigurationSupportExtensionTests.java | 24 +- .../WebMvcConfigurationSupportTests.java | 24 +- .../DefaultRenderingResponseTests.java | 4 +- .../function/DefaultServerRequestTests.java | 4 +- .../ResourceHandlerFunctionTests.java | 6 +- .../support/RouterFunctionMappingTests.java | 2 +- .../handler/HandlerMethodMappingTests.java | 16 +- .../ParameterizableViewControllerTests.java | 4 +- .../mvc/UrlFilenameViewControllerTests.java | 2 +- .../ResponseStatusExceptionResolverTests.java | 8 +- .../HeadersRequestConditionTests.java | 4 +- .../ParamsRequestConditionTests.java | 4 +- .../RequestMethodsRequestConditionTests.java | 4 +- ...RequestMappingInfoHandlerMappingTests.java | 19 +- .../mvc/method/RequestMappingInfoTests.java | 2 +- ...xceptionHandlerExceptionResolverTests.java | 6 +- .../HttpEntityMethodProcessorMockTests.java | 6 +- ...lAndViewMethodReturnValueHandlerTests.java | 2 +- ...thVariableMethodArgumentResolverTests.java | 10 +- ...MappingHandlerAdapterIntegrationTests.java | 46 +- .../RequestMappingHandlerAdapterTests.java | 6 +- .../RequestMappingHandlerMappingTests.java | 6 +- .../RequestPartIntegrationTests.java | 16 +- ...equestPartMethodArgumentResolverTests.java | 20 +- .../ResponseEntityExceptionHandlerTests.java | 8 +- ...nnotationControllerHandlerMethodTests.java | 15 +- .../method/annotation/SseEmitterTests.java | 4 +- ...nfigDispatcherServletInitializerTests.java | 10 +- .../DispatcherServletInitializerTests.java | 6 +- .../web/servlet/tags/AbstractTagTests.java | 2 +- .../web/servlet/tags/UrlTagTests.java | 16 +- .../servlet/tags/form/CheckboxesTagTests.java | 4 +- .../web/servlet/tags/form/InputTagTests.java | 4 +- .../servlet/tags/form/OptionsTagTests.java | 6 +- .../tags/form/RadioButtonsTagTests.java | 8 +- .../web/servlet/tags/form/SelectTagTests.java | 28 +- .../web/servlet/theme/ThemeResolverTests.java | 2 +- .../web/servlet/view/BaseViewTests.java | 16 +- .../web/servlet/view/ViewResolverTests.java | 2 +- .../script/KotlinScriptTemplateTests.java | 2 +- .../adapter/AbstractWebSocketSession.java | 6 +- .../socket/adapter/standard/package-info.java | 2 +- .../client/jetty/JettyWebSocketClient.java | 6 +- .../standard/StandardWebSocketClient.java | 4 +- .../socket/client/standard/package-info.java | 2 +- .../WebSocketHandlerDecoratorFactory.java | 4 +- .../messaging/SessionDisconnectEvent.java | 2 +- .../SubProtocolWebSocketHandler.java | 4 +- .../socket/server/RequestUpgradeStrategy.java | 3 +- .../jetty/JettyRequestUpgradeStrategy.java | 4 +- .../AbstractTyrusRequestUpgradeStrategy.java | 8 +- .../standard/ServerEndpointExporter.java | 2 +- .../standard/ServerEndpointRegistration.java | 4 +- .../StandardWebSocketUpgradeStrategy.java | 21 +- .../TomcatRequestUpgradeStrategy.java | 54 +- .../UndertowRequestUpgradeStrategy.java | 64 +- .../WebSphereRequestUpgradeStrategy.java | 47 +- .../support/AbstractHandshakeHandler.java | 84 +- .../sockjs/client/JettyXhrTransport.java | 7 +- .../socket/sockjs/client/SockJsClient.java | 10 +- .../frame/Jackson2SockJsMessageCodec.java | 4 +- .../sockjs/support/AbstractSockJsService.java | 3 +- .../sockjs/transport/TransportType.java | 15 +- .../handler/HtmlFileTransportHandler.java | 9 +- .../handler/SockJsWebSocketHandler.java | 4 +- .../AbstractWebSocketIntegrationTests.java | 16 +- .../web/socket/WebSocketExtensionTests.java | 4 +- .../web/socket/WebSocketHandshakeTests.java | 4 +- .../ConvertingEncoderDecoderSupportTests.java | 2 +- .../StandardWebSocketSessionTests.java | 16 +- .../WebSocketConnectionManagerTests.java | 9 +- .../StandardWebSocketClientTests.java | 61 +- ...essageBrokerBeanDefinitionParserTests.java | 10 +- .../WebMvcStompEndpointRegistryTests.java | 10 +- .../WebSocketHandlerRegistrationTests.java | 26 +- ...currentWebSocketSessionDecoratorTests.java | 4 +- .../DefaultSimpUserRegistryTests.java | 12 +- .../StompSubProtocolHandlerTests.java | 28 +- .../SubProtocolWebSocketHandlerTests.java | 2 +- .../HttpSessionHandshakeInterceptorTests.java | 10 +- .../OriginHandshakeInterceptorTests.java | 2 +- .../AbstractSockJsIntegrationTests.java | 4 +- .../client/ClientSockJsSessionTests.java | 38 +- .../client/DefaultTransportRequestTests.java | 17 +- .../client/JettySockJsIntegrationTests.java | 4 +- .../client/RestTemplateXhrTransportTests.java | 8 +- .../sockjs/client/SockJsClientTests.java | 24 +- .../sockjs/client/SockJsUrlInfoTests.java | 22 +- .../sockjs/client/XhrTransportTests.java | 24 +- .../sockjs/support/SockJsServiceTests.java | 8 +- .../handler/DefaultSockJsServiceTests.java | 6 +- .../transport/session/SockJsSessionTests.java | 6 +- src/checkstyle/checkstyle-suppressions.xml | 3 + update_copyright_headers.sh | 21 + 1375 files changed, 28573 insertions(+), 25668 deletions(-) create mode 100644 framework-docs/src/docs/asciidoc/attributes.adoc create mode 100644 framework-docs/src/docs/asciidoc/integration/cache.adoc create mode 100644 framework-docs/src/docs/asciidoc/integration/email.adoc create mode 100644 framework-docs/src/docs/asciidoc/integration/jms.adoc create mode 100644 framework-docs/src/docs/asciidoc/integration/jmx.adoc create mode 100644 framework-docs/src/docs/asciidoc/integration/observability.adoc create mode 100644 framework-docs/src/docs/asciidoc/integration/rest-clients.adoc create mode 100644 framework-docs/src/docs/asciidoc/integration/scheduling.adoc create mode 100644 framework-docs/src/docs/asciidoc/page-layout.adoc create mode 100644 framework-docs/src/docs/asciidoc/testing/integration-testing.adoc create mode 100644 framework-docs/src/docs/asciidoc/testing/spring-mvc-test-client.adoc create mode 100644 framework-docs/src/docs/asciidoc/testing/spring-mvc-test-framework.adoc create mode 100644 framework-docs/src/docs/asciidoc/testing/testcontext-framework.adoc create mode 100644 framework-docs/src/docs/asciidoc/testing/testing-annotations.adoc create mode 100644 framework-docs/src/docs/asciidoc/testing/testing-appendix.adoc create mode 100644 framework-docs/src/docs/asciidoc/testing/testing-introduction.adoc create mode 100644 framework-docs/src/docs/asciidoc/testing/testing-resources.adoc create mode 100644 framework-docs/src/docs/asciidoc/testing/testing-support-jdbc.adoc create mode 100644 framework-docs/src/docs/asciidoc/testing/unit-testing.adoc create mode 100644 framework-docs/src/main/java/org/springframework/docs/core/aot/hints/importruntimehints/SpellCheckService.java create mode 100644 framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflection.java create mode 100644 framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflectionRuntimeHintsTests.java create mode 100644 framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SpellCheckServiceTests.java create mode 100644 framework-docs/src/main/java/org/springframework/docs/core/aot/refresh/AotProcessingSample.java create mode 100644 framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/CustomServerRequestObservationConvention.java create mode 100644 framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/ExtendedServerRequestObservationConvention.java create mode 100644 framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/ServerRequestObservationFilter.java delete mode 100644 spring-aop/src/test/java/org/springframework/aop/support/AbstractRegexpMethodPointcutTests.java create mode 100644 spring-beans/src/test/java/org/springframework/beans/factory/xml/ResourceEntityResolverTests.java create mode 100644 spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java create mode 100644 spring-context/src/test/java/example/indexed/IndexedJakartaNamedComponent.java create mode 100644 spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java create mode 100644 spring-context/src/test/java/example/scannable/JakartaNamedComponent.java create mode 100644 spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredGenericTemplate.java create mode 100644 spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/GenericTemplate.java create mode 100644 spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/GenericTemplateConfiguration.java create mode 100644 spring-core/src/main/java/org/springframework/aot/hint/support/ClassHintUtils.java create mode 100644 spring-core/src/main/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHints.java create mode 100644 spring-core/src/test/java/org/springframework/SpringCoreTestSuite.java create mode 100644 spring-core/src/test/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHintsTests.java create mode 100644 spring-expression/src/test/java/org/springframework/expression/spel/SpringExpressionTestSuite.java create mode 100644 spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/SqlServerSequenceMaxValueIncrementer.java delete mode 100644 spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionSubclassFactory.java delete mode 100644 spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateExceptionTranslatorTests.java rename spring-jdbc/src/test/java/org/springframework/jdbc/support/{ => incrementer}/DataFieldMaxValueIncrementerTests.java (90%) create mode 100644 spring-test/src/main/java/org/springframework/test/util/TestSocketUtils.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/aot/DeclarativeRuntimeHintsTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/aot/samples/hints/DeclarativeRuntimeHintsSpringJupiterTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/util/TestSocketUtilsTests.java rename spring-web/src/main/java/org/springframework/http/{ => server}/observation/DefaultServerRequestObservationConvention.java (95%) rename spring-web/src/main/java/org/springframework/http/{ => server}/observation/ServerHttpObservationDocumentation.java (95%) rename spring-web/src/main/java/org/springframework/http/{ => server}/observation/ServerRequestObservationContext.java (83%) rename spring-web/src/main/java/org/springframework/http/{ => server}/observation/ServerRequestObservationConvention.java (88%) rename spring-web/src/main/java/org/springframework/http/{ => server}/observation/package-info.java (67%) rename spring-web/src/main/java/org/springframework/http/{observation/reactive => server/reactive/observation}/DefaultServerRequestObservationConvention.java (92%) rename spring-web/src/main/java/org/springframework/http/{observation/reactive => server/reactive/observation}/ServerHttpObservationDocumentation.java (95%) rename spring-web/src/main/java/org/springframework/http/{observation/reactive => server/reactive/observation}/ServerRequestObservationContext.java (72%) rename spring-web/src/main/java/org/springframework/http/{observation/reactive => server/reactive/observation}/ServerRequestObservationConvention.java (88%) rename spring-web/src/main/java/org/springframework/http/{observation/reactive => server/reactive/observation}/package-info.java (63%) create mode 100644 spring-web/src/main/java/org/springframework/web/DefaultErrorResponseBuilder.java create mode 100644 spring-web/src/test/java/org/springframework/http/ProblemDetailTests.java rename spring-web/src/test/java/org/springframework/http/{ => server}/observation/DefaultServerRequestObservationConventionTests.java (98%) rename spring-web/src/test/java/org/springframework/http/server/reactive/{MultipartIntegrationTests.java => MultipartHttpHandlerIntegrationTests.java} (87%) rename spring-web/src/test/java/org/springframework/http/{observation/reactive => server/reactive/observation}/DefaultServerRequestObservationConventionTests.java (87%) rename spring-web/src/{test/java/org/springframework => testFixtures/java/org/springframework/web/testfixture}/http/MockHttpInputMessage.java (66%) rename spring-web/src/{test/java/org/springframework => testFixtures/java/org/springframework/web/testfixture}/http/MockHttpOutputMessage.java (55%) create mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/DispatchExceptionHandler.java create mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/StandardWebSocketUpgradeStrategy.java rename spring-webflux/src/test/java/org/springframework/web/reactive/function/{MultipartIntegrationTests.java => MultipartRouterFunctionIntegrationTests.java} (97%) rename spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/{MultipartIntegrationTests.java => MultipartWebClientIntegrationTests.java} (96%) rename spring-webflux/src/test/java/org/springframework/web/reactive/socket/{AbstractWebSocketIntegrationTests.java => AbstractReactiveWebSocketIntegrationTests.java} (94%) create mode 100755 update_copyright_headers.sh diff --git a/.github/workflows/backport-bot.yml b/.github/workflows/backport-bot.yml index 2c3d36fd7c49..153c63247652 100644 --- a/.github/workflows/backport-bot.yml +++ b/.github/workflows/backport-bot.yml @@ -3,6 +3,8 @@ name: Backport Bot on: issues: types: [labeled] + pull_request: + types: [labeled] push: branches: - '*.x' @@ -13,6 +15,7 @@ jobs: permissions: contents: read issues: write + pull-requests: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/build.gradle b/build.gradle index 03c0c306c8d7..3cb59c035212 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ plugins { id 'com.github.ben-manes.versions' version '0.42.0' id 'com.github.johnrengelman.shadow' version '7.1.2' apply false id 'de.undercouch.download' version '5.1.0' - id 'me.champeau.jmh' version '0.6.6' apply false + id 'me.champeau.jmh' version '0.6.8' apply false } ext { @@ -21,6 +21,13 @@ ext { configure(allprojects) { project -> repositories { mavenCentral() + maven { + url "https://repo.spring.io/milestone" + content { + // Netty 5 optional support + includeGroup 'io.projectreactor.netty' + } + } maven { url "https://repo.spring.io/libs-spring-framework-build" } if (version.contains('-')) { maven { url "https://repo.spring.io/milestone" } @@ -71,7 +78,7 @@ configure([rootProject] + javaProjects) { project -> } checkstyle { - toolVersion = "10.3.4" + toolVersion = "10.5.0" configDirectory.set(rootProject.file("src/checkstyle")) } @@ -117,7 +124,7 @@ configure([rootProject] + javaProjects) { project -> "https://fasterxml.github.io/jackson-core/javadoc/2.10/", "https://fasterxml.github.io/jackson-databind/javadoc/2.10/", "https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.10/", - "https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/", + "https://hc.apache.org/httpcomponents-client-5.2.x/current/httpclient5/apidocs/", "https://projectreactor.io/docs/test/release/api/", "https://junit.org/junit4/javadoc/4.13.2/", // TODO Uncomment link to JUnit 5 docs once we have sorted out diff --git a/buildSrc/src/main/java/org/springframework/build/KotlinConventions.java b/buildSrc/src/main/java/org/springframework/build/KotlinConventions.java index f0ef7f3d59c7..438501d228f4 100644 --- a/buildSrc/src/main/java/org/springframework/build/KotlinConventions.java +++ b/buildSrc/src/main/java/org/springframework/build/KotlinConventions.java @@ -38,6 +38,7 @@ private void configure(KotlinCompile compile) { kotlinOptions.setApiVersion("1.7"); kotlinOptions.setLanguageVersion("1.7"); kotlinOptions.setJvmTarget("17"); + kotlinOptions.setJavaParameters(true); kotlinOptions.setAllWarningsAsErrors(true); List freeCompilerArgs = new ArrayList<>(compile.getKotlinOptions().getFreeCompilerArgs()); freeCompilerArgs.addAll(List.of("-Xsuppress-version-warnings", "-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn")); diff --git a/framework-docs/framework-docs.gradle b/framework-docs/framework-docs.gradle index 72acc52ba093..3688da199e0c 100644 --- a/framework-docs/framework-docs.gradle +++ b/framework-docs/framework-docs.gradle @@ -10,6 +10,15 @@ configurations { asciidoctorExtensions } +dependencies { + api(project(":spring-context")) + api(project(":spring-web")) + api("jakarta.servlet:jakarta.servlet-api") + + implementation(project(":spring-core-test")) + implementation("org.assertj:assertj-core") +} + jar { enabled = false } @@ -80,8 +89,6 @@ rootProject.tasks.dokkaHtmlMultiModule.configure { } asciidoctorj { - def docRoot = 'https://docs.spring.io' - def docsSpringFramework = "${docRoot}/spring-framework/docs/${project.version}" version = '2.4.3' fatalWarnings ".*" options doctype: 'book', eruby: 'erubis' @@ -92,11 +99,7 @@ asciidoctorj { revnumber: project.version, sectanchors: '', sectnums: '', - 'spring-version': project.version, - 'spring-framework-main-code': 'https://github.com/spring-projects/spring-framework/tree/main', - 'doc-root': docRoot, - 'docs-spring-framework': docsSpringFramework, - 'api-spring-framework': "${docsSpringFramework}/javadoc-api/org/springframework" + 'spring-version': project.version ]) } @@ -110,18 +113,23 @@ asciidoctor { sources { include '*.adoc' } - outputDir "$buildDir/docs/ref-docs/html5" - outputOptions { - backends "spring-html" - } - logDocuments = true resources { from(sourceDir) { include 'images/*.png' } } + outputDir "$buildDir/docs/ref-docs/html5" + outputOptions { + backends "spring-html" + } + forkOptions { + jvmArgs += ["--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED"] + } + logDocuments = true } +asciidoctor.mustRunAfter "check" + /** * Generate the Spring Framework Reference documentation from "src/docs/asciidoc" * in "build/docs/ref-docs/pdf". @@ -241,7 +249,6 @@ task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) { distZip.mustRunAfter moduleProjects.check - publishing { publications { mavenJava(MavenPublication) { @@ -250,4 +257,4 @@ publishing { artifact distZip } } -} \ No newline at end of file +} diff --git a/framework-docs/src/docs/asciidoc/appendix.adoc b/framework-docs/src/docs/asciidoc/appendix.adoc index 8f11dd941a81..ab1f7a86be96 100644 --- a/framework-docs/src/docs/asciidoc/appendix.adoc +++ b/framework-docs/src/docs/asciidoc/appendix.adoc @@ -1,9 +1,7 @@ [[appendix]] = Appendix -:toc: left -:toclevels: 4 -:tabsize: 4 -:docinfo1: +include::attributes.adoc[] +include::page-layout.adoc[] This part of the reference documentation covers topics that apply to multiple modules within the core Spring Framework. diff --git a/framework-docs/src/docs/asciidoc/attributes.adoc b/framework-docs/src/docs/asciidoc/attributes.adoc new file mode 100644 index 000000000000..321eaf5cd34a --- /dev/null +++ b/framework-docs/src/docs/asciidoc/attributes.adoc @@ -0,0 +1,18 @@ +:chomp: default headers packages +:docs-site: https://docs.spring.io +// Spring Framework +:docs-spring-framework: {docs-site}/spring-framework/docs/{spring-version} +:api-spring-framework: {docs-spring-framework}/javadoc-api/org/springframework +:docs-java: {docdir}/../../main/java/org/springframework/docs +:docs-kotlin: {docdir}/../../main/kotlin/org/springframework/docs +:docs-resources: {docdir}/../../main/resources +:spring-framework-main-code: https://github.com/spring-projects/spring-framework/tree/main +// Spring portfolio Links +:docs-spring-boot: {docs-site}/spring-boot/docs/current/reference +:docs-spring-gemfire: {docs-site}/spring-gemfire/docs/current/reference +:docs-spring-security: {docs-site}/spring-security/reference +// Third-party Links +:docs-graalvm: https://www.graalvm.org/22.3/reference-manual +:gh-rsocket: https://github.com/rsocket +:gh-rsocket-extensions: {gh-rsocket}/rsocket/blob/master/Extensions +:gh-rsocket-java: {gh-rsocket}/rsocket-java diff --git a/framework-docs/src/docs/asciidoc/core.adoc b/framework-docs/src/docs/asciidoc/core.adoc index 1801504c0edf..35b85b16c333 100644 --- a/framework-docs/src/docs/asciidoc/core.adoc +++ b/framework-docs/src/docs/asciidoc/core.adoc @@ -1,9 +1,7 @@ [[spring-core]] = Core Technologies -:toc: left -:toclevels: 4 -:tabsize: 4 -:docinfo1: +include::attributes.adoc[] +include::page-layout.adoc[] This part of the reference documentation covers all the technologies that are absolutely integral to the Spring Framework. diff --git a/framework-docs/src/docs/asciidoc/core/core-aop-api.adoc b/framework-docs/src/docs/asciidoc/core/core-aop-api.adoc index 26e91ea39b18..e627c4a7d97b 100644 --- a/framework-docs/src/docs/asciidoc/core/core-aop-api.adoc +++ b/framework-docs/src/docs/asciidoc/core/core-aop-api.adoc @@ -23,7 +23,7 @@ Spring's pointcut model enables pointcut reuse independent of advice types. You target different advice with the same pointcut. The `org.springframework.aop.Pointcut` interface is the central interface, used to -target advices to particular classes and methods. The complete interface follows: +target advice to particular classes and methods. The complete interface follows: [source,java,indent=0,subs="verbatim,quotes"] ---- @@ -843,7 +843,7 @@ created by the implementation of the `getObject()` method in the `ProxyFactoryBe method creates an AOP proxy that wraps a target object. One of the most important benefits of using a `ProxyFactoryBean` or another IoC-aware -class to create AOP proxies is that advices and pointcuts can also be +class to create AOP proxies is that advice and pointcuts can also be managed by IoC. This is a powerful feature, enabling certain approaches that are hard to achieve with other AOP frameworks. For example, an advice may itself reference application objects (besides the target, which should be available in any AOP @@ -901,7 +901,7 @@ to be applied. You can find an example of using this feature in <` element. Again, the following example specifies a `ReflectiveLoadTimeWeaver`: diff --git a/framework-docs/src/docs/asciidoc/core/core-aot.adoc b/framework-docs/src/docs/asciidoc/core/core-aot.adoc index 9903a6438d36..4621cd30670e 100644 --- a/framework-docs/src/docs/asciidoc/core/core-aot.adoc +++ b/framework-docs/src/docs/asciidoc/core/core-aot.adoc @@ -1,9 +1,11 @@ -[[aot]] +[[core.aot]] = Ahead of Time Optimizations This chapter covers Spring's Ahead of Time (AOT) optimizations. -[[aot-introduction]] +For AOT support specific to integration tests, see <>. + +[[core.aot.introduction]] == Introduction to Ahead of Time Optimizations Spring's support for AOT optimizations is meant to inspect an `ApplicationContext` at build time and apply decisions and discovery logic that usually happens at runtime. @@ -13,7 +15,7 @@ Applying such optimizations early implies the following restrictions: * The classpath is fixed and fully defined at build time. * The beans defined in your application cannot change at runtime, meaning: -** `@Profile`, in particular profile-specific configuration need to be chosen at build time. +** `@Profile`, in particular profile-specific configuration needs to be chosen at build time. ** Environment properties that impact the presence of a bean (`@Conditional`) are only considered at build time. When these restrictions are in place, it becomes possible to perform ahead-of-time processing at build time and generate additional assets. @@ -21,117 +23,107 @@ A Spring AOT processed application typically generates: * Java source code * Bytecode (usually for dynamic proxies) -* {api-spring-framework}/aot/hint/RuntimeHints.html[`RuntimeHints`] for the use of reflection, resource loading, serialization, and JDK proxy. +* {api-spring-framework}/aot/hint/RuntimeHints.html[`RuntimeHints`] for the use of reflection, resource loading, serialization, and JDK proxies. -NOTE: At the moment, AOT is focused on allowing Spring Applications to be deployed as native images using GraalVM. -We intend to offer more JVM-based use cases in future generations. +NOTE: At the moment, AOT is focused on allowing Spring applications to be deployed as native images using GraalVM. +We intend to support more JVM-based use cases in future generations. -[[aot-basics]] +[[core.aot.basics]] == AOT engine overview -The entry point of the AOT engine for processing an `ApplicationContext` arrangement is `ApplicationContextAotGenerator`. It takes care of the following steps, based on `GenericApplicationContext` that represents the application to optimize and a {api-spring-framework}/aot/generate/GenerationContext.html[`GenerationContext`]: + +The entry point of the AOT engine for processing an `ApplicationContext` arrangement is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {api-spring-framework}/aot/generate/GenerationContext.html[`GenerationContext`]: * Refresh an `ApplicationContext` for AOT processing. Contrary to a traditional refresh, this version only creates bean definitions, not bean instances. * Invoke the available `BeanFactoryInitializationAotProcessor` implementations and apply their contributions against the `GenerationContext`. -For instance, a core implementation iterates over all candidate bean definitions and generate the necessary code to restore the state of the `BeanFactory`. +For instance, a core implementation iterates over all candidate bean definitions and generates the necessary code to restore the state of the `BeanFactory`. -Once this process completes, the `GenerationContext` has been updated with the generated code, resources, and classes that are necessary for the application to run. -The `RuntimeHints` instance can also be used to generate the relevant GraalVM configuration files. +Once this process completes, the `GenerationContext` will have been updated with the generated code, resources, and classes that are necessary for the application to run. +The `RuntimeHints` instance can also be used to generate the relevant GraalVM native image configuration files. -`ApplicationContextAotGenerator#processAheadOfTime` returns the class name of the `ApplicationContextInitializer` entry point that permits to start the context with AOT optimizations. +`ApplicationContextAotGenerator#processAheadOfTime` returns the class name of the `ApplicationContextInitializer` entry point that allows the context to be started with AOT optimizations. -Those steps are covered in more details in the sections below. +Those steps are covered in greater detail in the sections below. -[[aot-refresh]] +[[core.aot.refresh]] == Refresh for AOT Processing -Refresh for AOT processing is supported on any `GenericApplicationContext` implementations. + +Refresh for AOT processing is supported on all `GenericApplicationContext` implementations. An application context is created with any number of entry points, usually in the form of `@Configuration`-annotated classes. -Let's take a basic example: +Let's look at a basic example: -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- -@Configuration(proxyBeanMethods=false) -@ComponentScan -@Import({DataSourceConfiguration.class, ContainerConfiguration.class}) -public class MyApplication { - -} ----- +include::code:AotProcessingSample[tag=myapplication] Starting this application with the regular runtime involves a number of steps including classpath scanning, configuration class parsing, bean instantiation, and lifecycle callback handling. -Refresh for AOT processing is only applying a subset of what is happening with a <>. -It can be triggered as follows: +Refresh for AOT processing only applies a subset of what happens with a <>. +AOT processing can be triggered as follows: -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- -GenericApplicationContext applicationContext = new AnnotatedConfigApplicationContext(); -context.register(MyApplication.class); -context.refreshForAotProcessing(); ----- +include::code:AotProcessingSample[tag=aotcontext] In this mode, <> are invoked as usual. This includes configuration class parsing, import selectors, classpath scanning, etc. -Such step makes sure that the `BeanRegistry` contains the relevant bean definitions for the application. +Such steps make sure that the `BeanRegistry` contains the relevant bean definitions for the application. If bean definitions are guarded by conditions (such as `@Profile`), these are discarded at this stage. -Because this mode does not actually create bean instances, `BeanPostProcessor` are not invoked, except for specific variants that are relevant for AOT processing. +Because this mode does not actually create bean instances, `BeanPostProcessor` implementations are not invoked, except for specific variants that are relevant for AOT processing. These are: -* `MergedBeanDefinitionPostProcessor` implementations post-process bean definitions to extract additional settings, such as init and destroy methods. +* `MergedBeanDefinitionPostProcessor` implementations post-process bean definitions to extract additional settings, such as `init` and `destroy` methods. * `SmartInstantiationAwareBeanPostProcessor` implementations determine a more precise bean type if necessary. -This makes sure to create any proxy that is required at runtime. +This makes sure to create any proxy that will be required at runtime. -One this part completes, the `BeanFactory` contains the bean definitions that are necessary for the application to run. It does not trigger bean instantiations but allow the AOT engine to inspect the beans that would be created at runtime. +One this part completes, the `BeanFactory` contains the bean definitions that are necessary for the application to run. It does not trigger bean instantiation but allows the AOT engine to inspect the beans that will be created at runtime. -[[aot-bean-factory-initialization-contributions]] +[[core.aot.bean-factory-initialization-contributions]] == Bean Factory Initialization AOT Contributions + Components that want to participate in this step can implement the {api-spring-framework}/beans/factory/aot/BeanFactoryInitializationAotProcessor.html[`BeanFactoryInitializationAotProcessor`] interface. Each implementation can return an AOT contribution, based on the state of the bean factory. -An AOT contribution is a component that contributes generated code that reproduce a particular behavior. -It can also contribute `RuntimeHints` to indicate the need for reflection, resource loading, serialization, or JDK proxy. +An AOT contribution is a component that contributes generated code that reproduces a particular behavior. +It can also contribute `RuntimeHints` to indicate the need for reflection, resource loading, serialization, or JDK proxies. -`BeanFactoryInitializationAotProcessor` implementation should be registered in `META-INF/aot/spring.factories` with a key matching the fully qualified name of the interface. +A `BeanFactoryInitializationAotProcessor` implementation can be registered in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the interface. -It can also be implemented on a bean directly. +A `BeanFactoryInitializationAotProcessor` can also be implemented directly by a bean. In this mode, the bean provides an AOT contribution equivalent to the feature it provides with a regular runtime. -As such, such a bean is automatically excluded from the AOT-optimized context. +Consequently, such a bean is automatically excluded from the AOT-optimized context. [NOTE] ==== -Using this interface on bean will cause the bean and **all** of its dependencies to be initialized during AOT processing. -We generally recommend that this interface is only used with infrastructure beans such as `BeanFactoryPostProcessor` which have limited dependencies and are already initialized early in the bean factory lifecycle. -If such a bean is registered using a factory `@Bean` method, make sure to make it `static` so that its enclosing `@Configuration` class does not have to be initialized. +If a bean implements the `BeanFactoryInitializationAotProcessor` interface, the bean and **all** of its dependencies will be initialized during AOT processing. +We generally recommend that this interface is only implemented by infrastructure beans such as `BeanFactoryPostProcessor` which have limited dependencies and are already initialized early in the bean factory lifecycle. +If such a bean is registered using an `@Bean` factory method, ensure the method is `static` so that its enclosing `@Configuration` class does not have to be initialized. ==== -[[aot-bean-registration-contributions]] +[[core.aot.bean-registration-contributions]] === Bean Registration AOT Contributions -A core `BeanFactoryInitializationAotProcessor` implementation is about collecting the necessary contributions for each candidate `BeanDefinition`. + +A core `BeanFactoryInitializationAotProcessor` implementation is responsible for collecting the necessary contributions for each candidate `BeanDefinition`. It does so using a dedicated `BeanRegistrationAotProcessor`. This interface is used as follows: -* On a `BeanPostProcessor` bean, to replace its runtime behavior. -For instance <> is implementing this interface to generate code that injects members annotated with `@Autowired`. -* On a type registered in `META-INF/aot/spring.factories` with a key matching the fully qualified name of the interface. -Typically used whe the bean definition needs to be tuned for specific features of the core framework. +* Implemented by a `BeanPostProcessor` bean, to replace its runtime behavior. +For instance <> implements this interface to generate code that injects members annotated with `@Autowired`. +* Implemented by a type registered in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the interface. +Typically used when the bean definition needs to be tuned for specific features of the core framework. [NOTE] ==== -Using this interface on bean will cause the bean and **all** of its dependencies to be initialized during AOT processing. -We generally recommend that this interface is only used with infrastructure beans such as `BeanPostProcessor` which have limited dependencies and are already initialized early in the bean factory lifecycle. -If such a bean is registered using a factory `@Bean` method, make sure to make it `static` so that its enclosing `@Configuration` class does not have to be initialized. +If a bean implements the `BeanRegistrationAotProcessor` interface, the bean and **all** of its dependencies will be initialized during AOT processing. +We generally recommend that this interface is only implemented by infrastructure beans such as `BeanFactoryPostProcessor` which have limited dependencies and are already initialized early in the bean factory lifecycle. +If such a bean is registered using an `@Bean` factory method, ensure the method is `static` so that its enclosing `@Configuration` class does not have to be initialized. ==== -If no `BeanRegistrationAotProcessor` handles a particular registered bean, the default implementation processes it. -This should be the default behavior, as tuning the generated code for a bean definition should be restricted to corner cases. +If no `BeanRegistrationAotProcessor` handles a particular registered bean, a default implementation processes it. +This is the default behavior, since tuning the generated code for a bean definition should be restricted to corner cases. Taking our previous example, let's assume that `DataSourceConfiguration` is as follows: -[source,java,indent=0] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java ---- @Configuration(proxyBeanMethods = false) public class DataSourceConfiguration { @@ -144,48 +136,172 @@ Taking our previous example, let's assume that `DataSourceConfiguration` is as f } ---- -As there isn't any particular condition on this class, `dataSourceConfiguration` and `dataSource` are identified as candidates. -The AOT engine would convert the configuration class above to code like this: +Since there isn't any particular condition on this class, `dataSourceConfiguration` and `dataSource` are identified as candidates. +The AOT engine will convert the configuration class above to code similar to the following: -[source,java,indent=0] +[source,java,indent=0,role="primary"] +.Java ---- - /** - * Bean definitions for {@link DataSourceConfiguration} - */ - public class DataSourceConfiguration__BeanDefinitions { - /** - * Get the bean definition for 'dataSourceConfiguration' - */ - public static BeanDefinition getDataSourceConfigurationBeanDefinition() { - Class beanType = DataSourceConfiguration.class; - RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType); - beanDefinition.setInstanceSupplier(DataSourceConfiguration::new); - return beanDefinition; - } - - /** - * Get the bean instance supplier for 'dataSource'. - */ - private static BeanInstanceSupplier getDataSourceInstanceSupplier() { - return BeanInstanceSupplier.forFactoryMethod(DataSourceConfiguration.class, "dataSource") - .withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(DataSourceConfiguration.class).dataSource()); - } - - /** - * Get the bean definition for 'dataSource' - */ - public static BeanDefinition getDataSourceBeanDefinition() { - Class beanType = SimpleDataSource.class; - RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType); - beanDefinition.setInstanceSupplier(getDataSourceInstanceSupplier()); - return beanDefinition; - } - } + /** + * Bean definitions for {@link DataSourceConfiguration} + */ + public class DataSourceConfiguration__BeanDefinitions { + /** + * Get the bean definition for 'dataSourceConfiguration' + */ + public static BeanDefinition getDataSourceConfigurationBeanDefinition() { + Class beanType = DataSourceConfiguration.class; + RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType); + beanDefinition.setInstanceSupplier(DataSourceConfiguration::new); + return beanDefinition; + } + + /** + * Get the bean instance supplier for 'dataSource'. + */ + private static BeanInstanceSupplier getDataSourceInstanceSupplier() { + return BeanInstanceSupplier.forFactoryMethod(DataSourceConfiguration.class, "dataSource") + .withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(DataSourceConfiguration.class).dataSource()); + } + + /** + * Get the bean definition for 'dataSource' + */ + public static BeanDefinition getDataSourceBeanDefinition() { + Class beanType = SimpleDataSource.class; + RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType); + beanDefinition.setInstanceSupplier(getDataSourceInstanceSupplier()); + return beanDefinition; + } + } ---- NOTE: The exact code generated may differ depending on the exact nature of your bean definitions. -The generated code above create equivalent bean definitions to the `@Configuration` class, but in a direct way and without the use of reflection if at all possible. -There is a bean definition for "`dataSourceConfiguration`" bean and one for "`dataSourceBean`". +The generated code above creates bean definitions equivalent to the `@Configuration` class, but in a direct way and without the use of reflection if at all possible. +There is a bean definition for `dataSourceConfiguration` and one for `dataSourceBean`. When a `datasource` instance is required, a `BeanInstanceSupplier` is called. This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean. + + +[[core.aot.hints]] +== Runtime Hints + +Running an application as a native image requires additional information compared to a regular JVM runtime. +For instance, GraalVM needs to know ahead of time if a component uses reflection. +Similarly, classpath resources are not shipped in a native image unless specified explicitly. +Consequently, if the application needs to load a resource, it must be referenced from the corresponding GraalVM native image configuration file. + +The {api-spring-framework}/aot/hint/RuntimeHints.html[`RuntimeHints`] API collects the need for reflection, resource loading, serialization, and JDK proxies at runtime. +The following example makes sure that `config/app.properties` can be loaded from the classpath at runtime within a native image: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + runtimeHints.resources().registerPattern("config/app.properties"); +---- + +A number of contracts are handled automatically during AOT processing. +For instance, the return type of a `@Controller` method is inspected, and relevant reflection hints are added if Spring detects that the type should be serialized (typically to JSON). + +For cases that the core container cannot infer, you can register such hints programmatically. +A number of convenient annotations are also provided for common use cases. + + +[[core.aot.hints.import-runtime-hints]] +=== `@ImportRuntimeHints` + +`RuntimeHintsRegistrar` implementations allow you to get a callback to the `RuntimeHints` instance managed by the AOT engine. +Implementations of this interface can be registered using `@ImportRuntimeHints` on any Spring bean or `@Bean` factory method. +`RuntimeHintsRegistrar` implementations are detected and invoked at build time. + +include::code:SpellCheckService[] + +If at all possible, `@ImportRuntimeHints` should be used as close as possible to the component that requires the hints. +This way, if the component is not contributed to the `BeanFactory`, the hints won't be contributed either. + +It is also possible to register an implementation statically by adding an entry in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the `RuntimeHintsRegistrar` interface. + + +[[core.aot.hints.reflective]] +=== `@Reflective` + +{api-spring-framework}/aot/hint/annotation/Reflective.html[`@Reflective`] provides an idiomatic way to flag the need for reflection on an annotated element. +For instance, `@EventListener` is meta-annotated with `@Reflective` since the underlying implementation invokes the annotated method using reflection. + +By default, only Spring beans are considered and an invocation hint is registered for the annotated element. +This can be tuned by specifying a custom `ReflectiveProcessor` implementation via the +`@Reflective` annotation. + +Library authors can reuse this annotation for their own purposes. +If components other than Spring beans need to be processed, a `BeanFactoryInitializationAotProcessor` can detect the relevant types and use `ReflectiveRuntimeHintsRegistrar` to process them. + + +[[core.aot.hints.register-reflection-for-binding]] +=== `@RegisterReflectionForBinding` + +{api-spring-framework}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`] is a specialization of `@Reflective` that registers the need for serializing arbitrary types. +A typical use case is the use of DTOs that the container cannot infer, such as using a web client within a method body. + +`@RegisterReflectionForBinding` can be applied to any Spring bean at the class level, but it can also be applied directly to a method, field, or constructor to better indicate where the hints are actually required. +The following example registers `Account` for serialization. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Component + public class OrderService { + + @RegisterReflectionForBinding(Account.class) + public void process(Order order) { + // ... + } + + } +---- + +[[core.aot.hints.testing]] +=== Testing Runtime Hints + +Spring Core also ships `RuntimeHintsPredicates`, a utility for checking that existing hints match a particular use case. +This can be used in your own tests to validate that a `RuntimeHintsRegistrar` contains the expected results. +We can write a test for our `SpellCheckService` and ensure that we will be able to load a dictionary at runtime: + +include::code:SpellCheckServiceTests[tag=hintspredicates] + +With `RuntimeHintsPredicates`, we can check for reflection, resource, serialization, or proxy generation hints. +This approach works well for unit tests but implies that the runtime behavior of a component is well known. + +You can learn more about the global runtime behavior of an application by running its test suite (or the app itself) with the {docs-graalvm}/native-image/metadata/AutomaticMetadataCollection/[GraalVM tracing agent]. +This agent will record all relevant calls requiring GraalVM hints at runtime and write them out as JSON configuration files. + +For more targeted discovery and testing, Spring Framework ships a dedicated module with core AOT testing utilities, `"org.springframework:spring-core-test"`. +This module contains the RuntimeHints Agent, a Java agent that records all method invocations that are related to runtime hints and helps you to assert that a given `RuntimeHints` instance covers all recorded invocations. +Let's consider a piece of infrastructure for which we'd like to test the hints we're contributing during the AOT processing phase. + +include::code:SampleReflection[] + +We can then write a unit test (no native compilation required) that checks our contributed hints: + +include::code:SampleReflectionRuntimeHintsTests[] + +If you forgot to contribute a hint, the test will fail and provide some details about the invocation: + +[source,txt,indent=0,subs="verbatim,quotes"] +---- +org.springframework.docs.core.aot.hints.testing.SampleReflection performReflection +INFO: Spring version:6.0.0-SNAPSHOT + +Missing <"ReflectionHints"> for invocation +with arguments ["org.springframework.core.SpringVersion", + false, + jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7]. +Stacktrace: +<"org.springframework.util.ClassUtils#forName, Line 284 +io.spring.runtimehintstesting.SampleReflection#performReflection, Line 19 +io.spring.runtimehintstesting.SampleReflectionRuntimeHintsTests#lambda$shouldRegisterReflectionHints$0, Line 25 +---- + +There are various ways to configure this Java agent in your build, so please refer to the documentation of your build tool and test execution plugin. +The agent itself can be configured to instrument specific packages (by default, only `org.springframework` is instrumented). +You'll find more details in the {spring-framework-main-code}/buildSrc/README.md[Spring Framework `buildSrc` README] file. diff --git a/framework-docs/src/docs/asciidoc/core/core-appendix.adoc b/framework-docs/src/docs/asciidoc/core/core-appendix.adoc index 9ecc84772896..488765bd0283 100644 --- a/framework-docs/src/docs/asciidoc/core/core-appendix.adoc +++ b/framework-docs/src/docs/asciidoc/core/core-appendix.adoc @@ -1069,7 +1069,7 @@ The following listing shows the `Component` class: private String name; private List components = new ArrayList (); - // mmm, there is no setter method for the 'components' + // there is no setter method for the 'components' public void addComponent(Component component) { this.components.add(component); } @@ -1099,7 +1099,7 @@ The following listing shows the `Component` class: var name: String? = null private val components = ArrayList() - // mmm, there is no setter method for the 'components' + // there is no setter method for the 'components' fun addComponent(component: Component) { this.components.add(component) } diff --git a/framework-docs/src/docs/asciidoc/core/core-beans.adoc b/framework-docs/src/docs/asciidoc/core/core-beans.adoc index 544817bce341..af3dab8a6c8f 100644 --- a/framework-docs/src/docs/asciidoc/core/core-beans.adoc +++ b/framework-docs/src/docs/asciidoc/core/core-beans.adoc @@ -107,16 +107,14 @@ configuration metadata is actually written. These days, many developers choose For information about using other forms of metadata with the Spring container, see: -* <>: Spring 2.5 introduced - support for annotation-based configuration metadata. -* <>: Starting with Spring 3.0, many features - provided by the Spring JavaConfig project became part of the core Spring Framework. - Thus, you can define beans external to your application classes by using Java rather - than XML files. To use these new features, see the - https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html[`@Configuration`], - https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html[`@Bean`], - https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Import.html[`@Import`], - and https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/DependsOn.html[`@DependsOn`] annotations. +* <>: define beans using + annotation-based configuration metadata. +* <>: define beans external to your application + classes by using Java rather than XML files. To use these features, see the + {api-spring-framework}/context/annotation/Configuration.html[`@Configuration`], + {api-spring-framework}/context/annotation/Bean.html[`@Bean`], + {api-spring-framework}/context/annotation/Import.html[`@Import`], + and {api-spring-framework}/context/annotation/DependsOn.html[`@DependsOn`] annotations. Spring configuration consists of at least one and typically more than one bean definition that the container must manage. XML-based configuration metadata configures these @@ -124,14 +122,12 @@ beans as `` elements inside a top-level `` element. Java configuration typically uses `@Bean`-annotated methods within a `@Configuration` class. These bean definitions correspond to the actual objects that make up your application. -Typically, you define service layer objects, data access objects (DAOs), presentation -objects such as Struts `Action` instances, infrastructure objects such as Hibernate -`SessionFactories`, JMS `Queues`, and so forth. Typically, one does not configure -fine-grained domain objects in the container, because it is usually the responsibility -of DAOs and business logic to create and load domain objects. However, you can use -Spring's integration with AspectJ to configure objects that have been created outside -the control of an IoC container. See <>. +Typically, you define service layer objects, persistence layer objects such as +repositories or data access objects (DAOs), presentation objects such as Web controllers, +infrastructure objects such as a JPA `EntityManagerFactory`, JMS queues, and so forth. +Typically, one does not configure fine-grained domain objects in the container, because +it is usually the responsibility of repositories and business logic to create and load +domain objects. The following example shows the basic structure of XML-based configuration metadata: @@ -157,12 +153,11 @@ The following example shows the basic structure of XML-based configuration metad ---- <1> The `id` attribute is a string that identifies the individual bean definition. - <2> The `class` attribute defines the type of the bean and uses the fully qualified -classname. +class name. -The value of the `id` attribute refers to collaborating objects. The XML for -referring to collaborating objects is not shown in this example. See +The value of the `id` attribute can be used to refer to collaborating objects. The XML +for referring to collaborating objects is not shown in this example. See <> for more information. @@ -610,7 +605,7 @@ creating a namespace), yet they refer to the same bean. .Java-configuration **** -If you use Javaconfiguration, the `@Bean` annotation can be used to provide aliases. +If you use Java Configuration, the `@Bean` annotation can be used to provide aliases. See <> for details. **** @@ -2839,7 +2834,7 @@ processed by the Spring `DispatcherServlet`, no special setup is necessary. `DispatcherServlet` already exposes all relevant state. If you use a Servlet web container, with requests processed outside of Spring's -`DispatcherServlet` (for example, when using JSF or Struts), you need to register the +`DispatcherServlet` (for example, when using JSF), you need to register the `org.springframework.web.context.request.RequestContextListener` `ServletRequestListener`. This can be done programmatically by using the `WebApplicationInitializer` interface. Alternatively, add the following declaration to your web application's `web.xml` file: @@ -3986,7 +3981,7 @@ dependency type. The following table summarizes the most important `Aware` inter | <> | `MessageSourceAware` -| Configured strategy for resolving messages (with support for parametrization and +| Configured strategy for resolving messages (with support for parameterization and internationalization). | <> @@ -5025,7 +5020,7 @@ through Java 8's `java.util.Optional`, as the following example shows: As of Spring Framework 5.0, you can also use a `@Nullable` annotation (of any kind in any package -- for example, `javax.annotation.Nullable` from JSR-305) or just leverage -Kotlin builtin null-safety support: +Kotlin built-in null-safety support: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -5205,6 +5200,7 @@ with specific arguments, narrowing the set of type matches so that a specific be chosen for each argument. In the simplest case, this can be a plain descriptive value, as shown in the following example: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5229,18 +5225,20 @@ shown in the following example: // ... } ---- +-- You can also specify the `@Qualifier` annotation on individual constructor arguments or method parameters, as shown in the following example: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- public class MovieRecommender { - private MovieCatalog movieCatalog; + private final MovieCatalog movieCatalog; - private CustomerPreferenceDao customerPreferenceDao; + private final CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(@Qualifier("main") MovieCatalog movieCatalog, @@ -5271,9 +5269,11 @@ method parameters, as shown in the following example: // ... } ---- +-- The following example shows corresponding bean definitions. +-- [source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -5307,6 +5307,7 @@ The following example shows corresponding bean definitions. is qualified with the same value. <2> The bean with the `action` qualifier value is wired with the constructor argument that is qualified with the same value. +-- For a fallback match, the bean name is considered a default qualifier value. Thus, you can define the bean with an `id` of `main` instead of the nested qualifier element, leading @@ -5384,6 +5385,7 @@ constructor or a multi-argument method. You can create your own custom qualifier annotations. To do so, define an annotation and provide the `@Qualifier` annotation within your definition, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5403,10 +5405,12 @@ provide the `@Qualifier` annotation within your definition, as the following exa @Qualifier annotation class Genre(val value: String) ---- +-- Then you can provide the custom qualifier on autowired fields and parameters, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5445,6 +5449,7 @@ following example shows: // ... } ---- +-- Next, you can provide the information for the candidate bean definitions. You can add `` tags as sub-elements of the `` tag and then specify the `type` and @@ -5453,6 +5458,7 @@ fully-qualified class name of the annotation. Alternately, as a convenience if n conflicting names exists, you can use the short class name. The following example demonstrates both approaches: +-- [source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -5480,6 +5486,7 @@ demonstrates both approaches: ---- +-- In <>, you can see an annotation-based alternative to providing the qualifier metadata in XML. Specifically, see <>. @@ -5490,6 +5497,7 @@ several different types of dependencies. For example, you may provide an offline catalog that can be searched when no Internet connection is available. First, define the simple annotation, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5497,7 +5505,6 @@ the simple annotation, as the following example shows: @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Offline { - } ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -5508,10 +5515,12 @@ the simple annotation, as the following example shows: @Qualifier annotation class Offline ---- +-- Then add the annotation to the field or property to be autowired, as shown in the following example: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5539,9 +5548,11 @@ class MovieRecommender { } ---- <1> This line adds the `@Offline` annotation. +-- Now the bean definition only needs a qualifier `type`, as shown in the following example: +-- [source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -5550,6 +5561,7 @@ Now the bean definition only needs a qualifier `type`, as shown in the following ---- <1> This element specifies the qualifier. +-- You can also define custom qualifier annotations that accept named attributes in @@ -5558,6 +5570,7 @@ then specified on a field or parameter to be autowired, a bean definition must m all such attribute values to be considered an autowire candidate. As an example, consider the following annotation definition: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5579,9 +5592,11 @@ consider the following annotation definition: @Qualifier annotation class MovieQualifier(val genre: String, val format: Format) ---- +-- In this case `Format` is an enum, defined as follows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5596,10 +5611,12 @@ In this case `Format` is an enum, defined as follows: VHS, DVD, BLURAY } ---- +-- The fields to be autowired are annotated with the custom qualifier and include values for both attributes: `genre` and `format`, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5648,6 +5665,7 @@ for both attributes: `genre` and `format`, as the following example shows: // ... } ---- +-- Finally, the bean definitions should contain matching qualifier values. This example also demonstrates that you can use bean meta attributes instead of the @@ -5656,6 +5674,7 @@ precedence, but the autowiring mechanism falls back on the values provided withi `` tags if no such qualifier is present, as in the last two bean definitions in the following example: +-- [source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -5699,6 +5718,7 @@ the following example: ---- +-- @@ -5830,6 +5850,7 @@ endpoints. Spring supports this pattern for Spring-managed objects as well. the bean name to be injected. In other words, it follows by-name semantics, as demonstrated in the following example: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5855,6 +5876,7 @@ class SimpleMovieLister { } ---- <1> This line injects a `@Resource`. +-- If no name is explicitly specified, the default name is derived from the field name or @@ -5862,6 +5884,7 @@ setter method. In case of a field, it takes the field name. In case of a setter it takes the bean property name. The following example is going to have the bean named `movieFinder` injected into its setter method: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5885,6 +5908,7 @@ named `movieFinder` injected into its setter method: } ---- +-- NOTE: The name provided with the annotation is resolved as a bean name by the `ApplicationContext` of which the `CommonAnnotationBeanPostProcessor` is aware. @@ -5903,6 +5927,7 @@ Thus, in the following example, the `customerPreferenceDao` field first looks fo named "customerPreferenceDao" and then falls back to a primary type match for the type `CustomerPreferenceDao`: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5940,6 +5965,7 @@ named "customerPreferenceDao" and then falls back to a primary type match for th ---- <1> The `context` field is injected based on the known resolvable dependency type: `ApplicationContext`. +-- [[beans-value-annotations]] === Using `@Value` @@ -6298,7 +6324,7 @@ the `@RestController` annotation from Spring MVC is composed of `@Controller` an In addition, composed annotations can optionally redeclare attributes from meta-annotations to allow customization. This can be particularly useful when you want to only expose a subset of the meta-annotation's attributes. For example, Spring's -`@SessionScope` annotation hardcodes the scope name to `session` but still allows +`@SessionScope` annotation hard codes the scope name to `session` but still allows customization of the `proxyMode`. The following listing shows the definition of the `SessionScope` annotation: @@ -7515,7 +7541,7 @@ container. It includes the following topics: [[beans-java-basic-concepts]] === Basic Concepts: `@Bean` and `@Configuration` -The central artifacts in Spring's new Java-configuration support are +The central artifacts in Spring's Java configuration support are `@Configuration`-annotated classes and `@Bean`-annotated methods. The `@Bean` annotation is used to indicate that a method instantiates, configures, and @@ -8099,7 +8125,7 @@ class AppConfig { By default, beans defined with Java configuration that have a public `close` or `shutdown` method are automatically enlisted with a destruction callback. If you have a public `close` or `shutdown` method and you do not wish for it to be called when the container -shuts down, you can add `@Bean(destroyMethod="")` to your bean definition to disable the +shuts down, you can add `@Bean(destroyMethod = "")` to your bean definition to disable the default `(inferred)` mode. You may want to do that by default for a resource that you acquire with JNDI, as its @@ -8112,7 +8138,7 @@ The following example shows how to prevent an automatic destruction callback for [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- - @Bean(destroyMethod="") + @Bean(destroyMethod = "") public DataSource dataSource() throws NamingException { return (DataSource) jndiTemplate.lookup("MyDS"); } @@ -9456,7 +9482,7 @@ now looks like the following listing: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- - @Bean(destroyMethod="") + @Bean(destroyMethod = "") public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); @@ -9495,6 +9521,7 @@ annotation lets you indicate that a component is eligible for registration when one or more specified profiles are active. Using our preceding example, we can rewrite the `dataSource` configuration as follows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -9529,7 +9556,9 @@ can rewrite the `dataSource` configuration as follows: } } ---- +-- +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -9537,13 +9566,15 @@ can rewrite the `dataSource` configuration as follows: @Profile("production") public class JndiDataConfig { - @Bean(destroyMethod="") + @Bean(destroyMethod = "") // <1> public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } } ---- +<1> `@Bean(destroyMethod = "")` disables default destroy method inference. + [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- @@ -9551,13 +9582,15 @@ can rewrite the `dataSource` configuration as follows: @Profile("production") class JndiDataConfig { - @Bean(destroyMethod = "") + @Bean(destroyMethod = "") // <1> fun dataSource(): DataSource { val ctx = InitialContext() return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource } } ---- +<1> `@Bean(destroyMethod = "")` disables default destroy method inference. +-- NOTE: As mentioned earlier, with `@Bean` methods, you typically choose to use programmatic JNDI lookups, by using either Spring's `JndiTemplate`/`JndiLocatorDelegate` helpers or the @@ -9569,9 +9602,9 @@ profile expression. A profile expression allows for more complicated profile log expressed (for example, `production & us-east`). The following operators are supported in profile expressions: -* `!`: A logical "`not`" of the profile -* `&`: A logical "`and`" of the profiles -* `|`: A logical "`or`" of the profiles +* `!`: A logical `NOT` of the profile +* `&`: A logical `AND` of the profiles +* `|`: A logical `OR` of the profiles NOTE: You cannot mix the `&` and `|` operators without using parentheses. For example, `production & us-east | eu-central` is not a valid expression. It must be expressed as @@ -9582,6 +9615,7 @@ of creating a custom composed annotation. The following example defines a custom `@Production` annotation that you can use as a drop-in replacement for `@Profile("production")`: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -9599,6 +9633,7 @@ of creating a custom composed annotation. The following example defines a custom @Profile("production") annotation class Production ---- +-- TIP: If a `@Configuration` class is marked with `@Profile`, all of the `@Bean` methods and `@Import` annotations associated with that class are bypassed unless one or more of @@ -9613,6 +9648,7 @@ active. For example, given `@Profile({"p1", "!p2"})`, registration will occur if of a configuration class (for example, for alternative variants of a particular bean), as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -9664,6 +9700,7 @@ the following example shows: ---- <1> The `standaloneDataSource` method is available only in the `development` profile. <2> The `jndiDataSource` method is available only in the `production` profile. +-- [NOTE] ==== @@ -9834,7 +9871,7 @@ activates multiple profiles: Declaratively, `spring.profiles.active` may accept a comma-separated list of profile names, as the following example shows: -[literal,subs="verbatim,quotes"] +[literal,indent=0,subs="verbatim,quotes"] ---- -Dspring.profiles.active="profile1,profile2" ---- @@ -10230,13 +10267,13 @@ handled in the JDK-standard way of resolving messages through `ResourceBundle` o purposes of the example, assume the contents of two of the above resource bundle files are as follows: -[literal,subs="verbatim,quotes"] +[source,properties,indent=0,subs="verbatim,quotes"] ---- # in format.properties message=Alligators rock! ---- -[literal,subs="verbatim,quotes"] +[source,properties,indent=0,subs="verbatim,quotes"] ---- # in exceptions.properties argument.required=The {0} argument is required. diff --git a/framework-docs/src/docs/asciidoc/core/core-expressions.adoc b/framework-docs/src/docs/asciidoc/core/core-expressions.adoc index 5ee6f2f71722..d813cd2b3736 100644 --- a/framework-docs/src/docs/asciidoc/core/core-expressions.adoc +++ b/framework-docs/src/docs/asciidoc/core/core-expressions.adoc @@ -744,25 +744,42 @@ topics: [[expressions-ref-literal]] === Literal Expressions -The types of literal expressions supported are strings, numeric values (int, real, hex), -boolean, and null. Strings are delimited by single quotation marks. To put a single quotation mark itself -in a string, use two single quotation mark characters. +SpEL supports the following types of literal expressions. -The following listing shows simple usage of literals. Typically, they are not used -in isolation like this but, rather, as part of a more complex expression -- for example, -using a literal on one side of a logical comparison operator. +- strings +- numeric values: integer (`int` or `long`), hexadecimal (`int` or `long`), real (`float` + or `double`) +- boolean values: `true` or `false` +- null + +Strings can delimited by single quotation marks (`'`) or double quotation marks (`"`). To +include a single quotation mark within a string literal enclosed in single quotation +marks, use two adjacent single quotation mark characters. Similarly, to include a double +quotation mark within a string literal enclosed in double quotation marks, use two +adjacent double quotation mark characters. + +Numbers support the use of the negative sign, exponential notation, and decimal points. +By default, real numbers are parsed by using `Double.parseDouble()`. + +The following listing shows simple usage of literals. Typically, they are not used in +isolation like this but, rather, as part of a more complex expression -- for example, +using a literal on one side of a logical comparison operator or as an argument to a +method. [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- ExpressionParser parser = new SpelExpressionParser(); - // evals to "Hello World" + // evaluates to "Hello World" String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); + // evaluates to "Tony's Pizza" + String pizzaParlor = (String) parser.parseExpression("'Tony''s Pizza'").getValue(); + double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue(); - // evals to 2147483647 + // evaluates to 2147483647 int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue(); boolean trueValue = (Boolean) parser.parseExpression("true").getValue(); @@ -774,12 +791,15 @@ using a literal on one side of a logical comparison operator. ---- val parser = SpelExpressionParser() - // evals to "Hello World" + // evaluates to "Hello World" val helloWorld = parser.parseExpression("'Hello World'").value as String + // evaluates to "Tony's Pizza" + val pizzaParlor = parser.parseExpression("'Tony''s Pizza'").value as String + val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double - // evals to 2147483647 + // evaluates to 2147483647 val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int val trueValue = parser.parseExpression("true").value as Boolean @@ -787,9 +807,6 @@ using a literal on one side of a logical comparison operator. val nullValue = parser.parseExpression("null").value ---- -Numbers support the use of the negative sign, exponential notation, and decimal points. -By default, real numbers are parsed by using `Double.parseDouble()`. - [[expressions-properties-arrays]] @@ -804,7 +821,7 @@ Pupin's city of birth, we use the following expressions: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- - // evals to 1856 + // evaluates to 1856 int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context); String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context); @@ -812,7 +829,7 @@ Pupin's city of birth, we use the following expressions: [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - // evals to 1856 + // evaluates to 1856 val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String diff --git a/framework-docs/src/docs/asciidoc/core/core-validation.adoc b/framework-docs/src/docs/asciidoc/core/core-validation.adoc index 7afadfa8a97d..e4f8e1c7e762 100644 --- a/framework-docs/src/docs/asciidoc/core/core-validation.adoc +++ b/framework-docs/src/docs/asciidoc/core/core-validation.adoc @@ -1634,7 +1634,7 @@ If you prefer XML-based configuration, you can use a xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans - https://www.springframework.org/schema/beans/spring-beans.xsd> + https://www.springframework.org/schema/beans/spring-beans.xsd"> diff --git a/framework-docs/src/docs/asciidoc/data-access.adoc b/framework-docs/src/docs/asciidoc/data-access.adoc index b9a2453022c4..49f0f2ce55ea 100644 --- a/framework-docs/src/docs/asciidoc/data-access.adoc +++ b/framework-docs/src/docs/asciidoc/data-access.adoc @@ -1,9 +1,7 @@ [[spring-data-tier]] = Data Access -:toc: left -:toclevels: 4 -:tabsize: 4 -:docinfo1: +include::attributes.adoc[] +include::page-layout.adoc[] This part of the reference documentation is concerned with data access and the interaction between the data access layer and the business or service layer. @@ -131,7 +129,7 @@ Typically, you need an application server's JTA capability only if your applicat to handle transactions across multiple resources, which is not a requirement for many applications. Many high-end applications use a single, highly scalable database (such as Oracle RAC) instead. Stand-alone transaction managers (such as -https://www.atomikos.com/[Atomikos Transactions] and https://jotm.ow2.org/[JOTM]) +https://www.atomikos.com/[Atomikos Transactions]) are other options. Of course, you may need other application server capabilities, such as Java Message Service (JMS) and Jakarta EE Connector Architecture (JCA). @@ -1092,8 +1090,8 @@ application-specific `Exception` type by supplying an _exception pattern_ via th ---- - - + + ---- @@ -1107,8 +1105,8 @@ unhandled `InstrumentNotFoundException`: ---- - - + + ---- @@ -1123,7 +1121,7 @@ attendant transaction: ---- - + ---- @@ -3482,6 +3480,7 @@ configure a `DataSource` in your Spring configuration file and then dependency-i that shared `DataSource` bean into your DAO classes. The `JdbcTemplate` is created in the setter for the `DataSource`. This leads to DAOs that resemble the following: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -3506,6 +3505,7 @@ the setter for the `DataSource`. This leads to DAOs that resemble the following: // JDBC-backed implementations of the methods on the CorporateEventDao follow... } ---- +-- The following example shows the corresponding XML configuration: @@ -3542,6 +3542,7 @@ support for dependency injection. In this case, you can annotate the class with (which makes it a candidate for component-scanning) and annotate the `DataSource` setter method with `@Autowired`. The following example shows how to do so: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -3576,6 +3577,7 @@ method with `@Autowired`. The following example shows how to do so: <1> Annotate the class with `@Repository`. <2> Constructor injection of the `DataSource`. <3> Create a new `JdbcTemplate` with the `DataSource`. +-- The following example shows the corresponding XML configuration: @@ -4186,7 +4188,7 @@ To configure a `DriverManagerDataSource`: . Obtain a connection with `DriverManagerDataSource` as you typically obtain a JDBC connection. -. Specify the fully qualified classname of the JDBC driver so that the `DriverManager` +. Specify the fully qualified class name of the JDBC driver so that the `DriverManager` can load the driver class. . Provide a URL that varies between JDBC drivers. (See the documentation for your driver for the correct value.) @@ -4344,7 +4346,7 @@ javadoc for more details. ==== Using `DataSourceTransactionManager` The `DataSourceTransactionManager` class is a `PlatformTransactionManager` -implementation for single JDBC datasources. It binds a JDBC connection from the +implementation for single JDBC data sources. It binds a JDBC connection from the specified data source to the currently executing thread, potentially allowing for one thread connection per data source. @@ -5486,8 +5488,7 @@ example shows such a method: .Java ---- public List searchForActors(int age, String namePattern) { - List actors = actorSearchMappingQuery.execute(age, namePattern); - return actors; + return actorSearchMappingQuery.execute(age, namePattern); } ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -6824,7 +6825,7 @@ The following query uses a bind variable: [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - val first = client.sql("SELECT id, name FROM person WHERE WHERE first_name = :fn") + val first = client.sql("SELECT id, name FROM person WHERE first_name = :fn") .bind("fn", "Joe") .fetch().awaitSingle() ---- @@ -6849,7 +6850,7 @@ Without specifying further mapping details, queries return tabular results as `Map` whose keys are case-insensitive column names that map to their column value. You can take control over result mapping by supplying a `Function` that gets -called for each `Row` so it can can return arbitrary values (singular values, +called for each `Row` so it can return arbitrary values (singular values, collections and maps, and objects). The following example extracts the `name` column and emits its value: @@ -6965,7 +6966,7 @@ Consider the following query: SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50)) ---- -The preceding query can be parametrized and run as follows: +The preceding query can be parameterized and run as follows: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -7080,6 +7081,7 @@ in your Spring configuration file and then dependency-inject that shared `ConnectionFactory` bean into your DAO classes. The `DatabaseClient` is created in the setter for the `ConnectionFactory`. This leads to DAOs that resemble the following: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -7104,12 +7106,14 @@ the setter for the `ConnectionFactory`. This leads to DAOs that resemble the fol // R2DBC-backed implementations of the methods on the CorporateEventDao follow... } ---- +-- An alternative to explicit configuration is to use component-scanning and annotation support for dependency injection. In this case, you can annotate the class with `@Component` (which makes it a candidate for component-scanning) and annotate the `ConnectionFactory` setter method with `@Autowired`. The following example shows how to do so: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -7144,6 +7148,7 @@ method with `@Autowired`. The following example shows how to do so: <1> Annotate the class with `@Component`. <2> Constructor injection of the `ConnectionFactory`. <3> Create a new `DatabaseClient` with the `ConnectionFactory`. +-- Regardless of which of the above template initialization styles you choose to use (or not), it is seldom necessary to create a new instance of a `DatabaseClient` class each @@ -7207,7 +7212,7 @@ responsibility of the administrator who sets up the `ConnectionFactory`. You most likely fill both roles as you develop and test code, but you do not necessarily have to know how the production data source is configured. -When you use Spring's R2DBC layer, you can can configure your own with a +When you use Spring's R2DBC layer, you can configure your own with a connection pool implementation provided by a third party. A popular implementation is R2DBC Pool (`r2dbc-pool`). Implementations in the Spring distribution are meant only for testing purposes and do not provide pooling. @@ -7281,7 +7286,7 @@ javadoc for more details. ==== Using `R2dbcTransactionManager` The `R2dbcTransactionManager` class is a `ReactiveTransactionManager` implementation for -single R2DBC datasources. It binds an R2DBC connection from the specified connection factory +single R2DBC data sources. It binds an R2DBC connection from the specified connection factory to the subscriber `Context`, potentially allowing for one subscriber connection for each connection factory. @@ -8631,7 +8636,7 @@ given `javax.xml.transform.Result`. The result is a tagging interface that basic represents an XML output abstraction. Concrete implementations wrap various XML representations, as the following table indicates: -[[oxm-marshller-tbl]] +[[oxm-marshaller-tbl]] |=== | Result implementation| Wraps XML representation @@ -8674,7 +8679,7 @@ This interface also has one method, which reads from the given with `Result`, `Source` is a tagging interface that has three concrete implementations. Each wraps a different XML representation, as the following table indicates: -[[oxm-unmarshller-tbl]] +[[oxm-unmarshaller-tbl]] |=== | Source implementation| Wraps XML representation @@ -8868,9 +8873,10 @@ preamble of the XML configuration file. The following example shows how to do so - xsi:schemaLocation="http://www.springframework.org/schema/beans - https://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/oxm https://www.springframework.org/schema/oxm/spring-oxm.xsd"> <2> + xsi:schemaLocation="http://www.springframework.org/schema/beans + https://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/oxm + https://www.springframework.org/schema/oxm/spring-oxm.xsd"> <2> ---- <1> Reference the `oxm` schema. <2> Specify the `oxm` schema location. diff --git a/framework-docs/src/docs/asciidoc/data-access/data-access-appendix.adoc b/framework-docs/src/docs/asciidoc/data-access/data-access-appendix.adoc index f876e464bf9c..449c86095d88 100644 --- a/framework-docs/src/docs/asciidoc/data-access/data-access-appendix.adoc +++ b/framework-docs/src/docs/asciidoc/data-access/data-access-appendix.adoc @@ -38,12 +38,15 @@ are available to you: + xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" - http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd <2> - http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> + http://www.springframework.org/schema/beans + https://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/tx + https://www.springframework.org/schema/tx/spring-tx.xsd <2> + http://www.springframework.org/schema/aop + https://www.springframework.org/schema/aop/spring-aop.xsd"> @@ -79,8 +82,10 @@ the correct schema so that the elements in the `jdbc` namespace are available to xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" <1> xsi:schemaLocation=" - http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> <2> + http://www.springframework.org/schema/beans + https://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/jdbc + https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> <2> diff --git a/framework-docs/src/docs/asciidoc/index.adoc b/framework-docs/src/docs/asciidoc/index.adoc index 15e7db515548..c512f77d7aa1 100644 --- a/framework-docs/src/docs/asciidoc/index.adoc +++ b/framework-docs/src/docs/asciidoc/index.adoc @@ -1,9 +1,10 @@ :noheader: = Spring Framework Documentation +include::attributes.adoc[] [horizontal] -<> :: history, design philosophy, feedback, -getting started. +<> :: History, Design Philosophy, Feedback, +Getting Started. <> :: IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP, AOT. <> :: Mock Objects, TestContext Framework, @@ -15,13 +16,13 @@ STOMP Messaging. <> :: Spring WebFlux, WebClient, WebSocket, RSocket. <> :: REST Clients, JMS, JCA, JMX, -Email, Tasks, Scheduling, Caching. +Email, Tasks, Scheduling, Caching, Observability. <> :: Kotlin, Groovy, Dynamic Languages. <> :: Spring properties. -https://github.com/spring-projects/spring-framework/wiki[*Wiki*] :: What's New, -Upgrade Notes, Supported Versions, and other cross-version information. +https://github.com/spring-projects/spring-framework/wiki[Wiki] :: What's New, +Upgrade Notes, Supported Versions, additional cross-version information. -NOTE: This documentation is available in {docs-spring-framework}/reference/html/index.html[HTML] and {docs-spring-framework}/reference/pdf/spring-framework.pdf[PDF] formats. +NOTE: This documentation is also available in {docs-spring-framework}/reference/pdf/spring-framework.pdf[PDF] format. Rod Johnson, Juergen Hoeller, Keith Donald, Colin Sampaleanu, Rob Harrop, Thomas Risberg, Alef Arendsen, Darren Davison, Dmitriy Kopylenko, Mark Pollack, Thierry Templier, Erwin diff --git a/framework-docs/src/docs/asciidoc/integration.adoc b/framework-docs/src/docs/asciidoc/integration.adoc index 63b327218849..2dd9aeb1e193 100644 --- a/framework-docs/src/docs/asciidoc/integration.adoc +++ b/framework-docs/src/docs/asciidoc/integration.adoc @@ -1,5736 +1,23 @@ [[spring-integration]] = Integration -:doc-spring-amqp: {doc-root}/spring-amqp/docs/current/reference -:doc-spring-gemfire: {doc-root}/spring-gemfire/docs/current/reference -:toc: left -:toclevels: 4 -:tabsize: 4 -:docinfo1: +include::attributes.adoc[] +include::page-layout.adoc[] This part of the reference documentation covers Spring Framework's integration with a number of technologies. +include::integration/rest-clients.adoc[leveloffset=+1] -[[rest-client-access]] -== REST Clients +include::integration/jms.adoc[leveloffset=+1] -The Spring Framework provides the following choices for making calls to REST endpoints: +include::integration/jmx.adoc[leveloffset=+1] -* <> - non-blocking, reactive client w fluent API. -* <> - synchronous client with template method API. -* <> - annotated interface with generated, dynamic proxy implementation. - - -[[rest-webclient]] -=== `WebClient` - -`WebClient` is a non-blocking, reactive client to perform HTTP requests. It was -introduced in 5.0 and offers an alternative to the `RestTemplate`, with support for -synchronous, asynchronous, and streaming scenarios. - -`WebClient` supports the following: - -* Non-blocking I/O. -* Reactive Streams back pressure. -* High concurrency with fewer hardware resources. -* Functional-style, fluent API that takes advantage of Java 8 lambdas. -* Synchronous and asynchronous interactions. -* Streaming up to or streaming down from a server. - -See <> for more details. - - - - -[[rest-resttemplate]] -=== `RestTemplate` - -The `RestTemplate` provides a higher level API over HTTP client libraries. It makes it -easy to invoke REST endpoints in a single line. It exposes the following groups of -overloaded methods: - -NOTE: `RestTemplate` is in maintenance mode, with only requests for minor -changes and bugs to be accepted. Please, consider using the -<> instead. - -[[rest-overview-of-resttemplate-methods-tbl]] -.RestTemplate methods -[cols="1,3"] -|=== -| Method group | Description - -| `getForObject` -| Retrieves a representation via GET. - -| `getForEntity` -| Retrieves a `ResponseEntity` (that is, status, headers, and body) by using GET. - -| `headForHeaders` -| Retrieves all headers for a resource by using HEAD. - -| `postForLocation` -| Creates a new resource by using POST and returns the `Location` header from the response. - -| `postForObject` -| Creates a new resource by using POST and returns the representation from the response. - -| `postForEntity` -| Creates a new resource by using POST and returns the representation from the response. - -| `put` -| Creates or updates a resource by using PUT. - -| `patchForObject` -| Updates a resource by using PATCH and returns the representation from the response. -Note that the JDK `HttpURLConnection` does not support `PATCH`, but Apache -HttpComponents and others do. - -| `delete` -| Deletes the resources at the specified URI by using DELETE. - -| `optionsForAllow` -| Retrieves allowed HTTP methods for a resource by using ALLOW. - -| `exchange` -| More generalized (and less opinionated) version of the preceding methods that provides extra -flexibility when needed. It accepts a `RequestEntity` (including HTTP method, URL, headers, -and body as input) and returns a `ResponseEntity`. - -These methods allow the use of `ParameterizedTypeReference` instead of `Class` to specify -a response type with generics. - -| `execute` -| The most generalized way to perform a request, with full control over request -preparation and response extraction through callback interfaces. - -|=== - -[[rest-resttemplate-create]] -==== Initialization - -The default constructor uses `java.net.HttpURLConnection` to perform requests. You can -switch to a different HTTP library with an implementation of `ClientHttpRequestFactory`. -There is built-in support for the following: - -* Apache HttpComponents -* Netty -* OkHttp - -For example, to switch to Apache HttpComponents, you can use the following: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); ----- - -Each `ClientHttpRequestFactory` exposes configuration options specific to the underlying -HTTP client library -- for example, for credentials, connection pooling, and other details. - -TIP: Note that the `java.net` implementation for HTTP requests can raise an exception when -accessing the status of a response that represents an error (such as 401). If this is an -issue, switch to another HTTP client library. - -[[rest-resttemplate-uri]] -===== URIs - -Many of the `RestTemplate` methods accept a URI template and URI template variables, -either as a `String` variable argument, or as `Map`. - -The following example uses a `String` variable argument: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - String result = restTemplate.getForObject( - "https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21"); ----- - -The following example uses a `Map`: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - Map vars = Collections.singletonMap("hotel", "42"); - - String result = restTemplate.getForObject( - "https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars); ----- - -Keep in mind URI templates are automatically encoded, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - restTemplate.getForObject("https://example.com/hotel list", String.class); - - // Results in request to "https://example.com/hotel%20list" ----- - -You can use the `uriTemplateHandler` property of `RestTemplate` to customize how URIs -are encoded. Alternatively, you can prepare a `java.net.URI` and pass it into one of -the `RestTemplate` methods that accepts a `URI`. - -For more details on working with and encoding URIs, see <>. - -[[rest-template-headers]] -===== Headers - -You can use the `exchange()` methods to specify request headers, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - String uriTemplate = "https://example.com/hotels/{hotel}"; - URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42); - - RequestEntity requestEntity = RequestEntity.get(uri) - .header("MyRequestHeader", "MyValue") - .build(); - - ResponseEntity response = template.exchange(requestEntity, String.class); - - String responseHeader = response.getHeaders().getFirst("MyResponseHeader"); - String body = response.getBody(); ----- - -You can obtain response headers through many `RestTemplate` method variants that return -`ResponseEntity`. - -[[rest-template-body]] -==== Body - -Objects passed into and returned from `RestTemplate` methods are converted to and from raw -content with the help of an `HttpMessageConverter`. - -On a POST, an input object is serialized to the request body, as the following example shows: - ----- -URI location = template.postForLocation("https://example.com/people", person); ----- - -You need not explicitly set the Content-Type header of the request. In most cases, -you can find a compatible message converter based on the source `Object` type, and the chosen -message converter sets the content type accordingly. If necessary, you can use the -`exchange` methods to explicitly provide the `Content-Type` request header, and that, in -turn, influences what message converter is selected. - -On a GET, the body of the response is deserialized to an output `Object`, as the following example shows: - ----- -Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42); ----- - -The `Accept` header of the request does not need to be explicitly set. In most cases, -a compatible message converter can be found based on the expected response type, which -then helps to populate the `Accept` header. If necessary, you can use the `exchange` -methods to provide the `Accept` header explicitly. - -By default, `RestTemplate` registers all built-in -<>, depending on classpath checks that help -to determine what optional conversion libraries are present. You can also set the message -converters to use explicitly. - -[[rest-message-conversion]] -==== Message Conversion -[.small]#<># - -The `spring-web` module contains the `HttpMessageConverter` contract for reading and -writing the body of HTTP requests and responses through `InputStream` and `OutputStream`. -`HttpMessageConverter` instances are used on the client side (for example, in the `RestTemplate`) and -on the server side (for example, in Spring MVC REST controllers). - -Concrete implementations for the main media (MIME) types are provided in the framework -and are, by default, registered with the `RestTemplate` on the client side and with -`RequestMappingHandlerAdapter` on the server side (see -<>). - -The implementations of `HttpMessageConverter` are described in the following sections. -For all converters, a default media type is used, but you can override it by setting the -`supportedMediaTypes` bean property. The following table describes each implementation: - -[[rest-message-converters-tbl]] -.HttpMessageConverter Implementations -[cols="1,3"] -|=== -| MessageConverter | Description - -| `StringHttpMessageConverter` -| An `HttpMessageConverter` implementation that can read and write `String` instances from the HTTP -request and response. By default, this converter supports all text media types -(`text/{asterisk}`) and writes with a `Content-Type` of `text/plain`. - -| `FormHttpMessageConverter` -| An `HttpMessageConverter` implementation that can read and write form data from the HTTP -request and response. By default, this converter reads and writes the -`application/x-www-form-urlencoded` media type. Form data is read from and written into a -`MultiValueMap`. The converter can also write (but not read) multipart -data read from a `MultiValueMap`. By default, `multipart/form-data` is -supported. As of Spring Framework 5.2, additional multipart subtypes can be supported for -writing form data. Consult the javadoc for `FormHttpMessageConverter` for further details. - -| `ByteArrayHttpMessageConverter` -| An `HttpMessageConverter` implementation that can read and write byte arrays from the -HTTP request and response. By default, this converter supports all media types (`{asterisk}/{asterisk}`) -and writes with a `Content-Type` of `application/octet-stream`. You can override this -by setting the `supportedMediaTypes` property and overriding `getContentType(byte[])`. - -| `MarshallingHttpMessageConverter` -| An `HttpMessageConverter` implementation that can read and write XML by using Spring's -`Marshaller` and `Unmarshaller` abstractions from the `org.springframework.oxm` package. -This converter requires a `Marshaller` and `Unmarshaller` before it can be used. You can inject these -through constructor or bean properties. By default, this converter supports -`text/xml` and `application/xml`. - -| `MappingJackson2HttpMessageConverter` -| An `HttpMessageConverter` implementation that can read and write JSON by using Jackson's -`ObjectMapper`. You can customize JSON mapping as needed through the use of Jackson's -provided annotations. When you need further control (for cases where custom JSON -serializers/deserializers need to be provided for specific types), you can inject a custom `ObjectMapper` -through the `ObjectMapper` property. By default, this -converter supports `application/json`. - -| `MappingJackson2XmlHttpMessageConverter` -| An `HttpMessageConverter` implementation that can read and write XML by using -https://github.com/FasterXML/jackson-dataformat-xml[Jackson XML] extension's -`XmlMapper`. You can customize XML mapping as needed through the use of JAXB -or Jackson's provided annotations. When you need further control (for cases where custom XML -serializers/deserializers need to be provided for specific types), you can inject a custom `XmlMapper` -through the `ObjectMapper` property. By default, this -converter supports `application/xml`. - -| `SourceHttpMessageConverter` -| An `HttpMessageConverter` implementation that can read and write -`javax.xml.transform.Source` from the HTTP request and response. Only `DOMSource`, -`SAXSource`, and `StreamSource` are supported. By default, this converter supports -`text/xml` and `application/xml`. - -| `BufferedImageHttpMessageConverter` -| An `HttpMessageConverter` implementation that can read and write -`java.awt.image.BufferedImage` from the HTTP request and response. This converter reads -and writes the media type supported by the Java I/O API. - -|=== - -[[rest-template-jsonview]] -==== Jackson JSON Views - -You can specify a https://www.baeldung.com/jackson-json-view-annotation[Jackson JSON View] -to serialize only a subset of the object properties, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23")); - value.setSerializationView(User.WithoutPasswordView.class); - - RequestEntity requestEntity = - RequestEntity.post(new URI("https://example.com/user")).body(value); - - ResponseEntity response = template.exchange(requestEntity, String.class); ----- - -[[rest-template-multipart]] -==== Multipart - -To send multipart data, you need to provide a `MultiValueMap` whose values -may be an `Object` for part content, a `Resource` for a file part, or an `HttpEntity` for -part content with headers. For example: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - MultiValueMap parts = new LinkedMultiValueMap<>(); - - parts.add("fieldPart", "fieldValue"); - parts.add("filePart", new FileSystemResource("...logo.png")); - parts.add("jsonPart", new Person("Jason")); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_XML); - parts.add("xmlPart", new HttpEntity<>(myBean, headers)); ----- - -In most cases, you do not have to specify the `Content-Type` for each part. The content -type is determined automatically based on the `HttpMessageConverter` chosen to serialize -it or, in the case of a `Resource` based on the file extension. If necessary, you can -explicitly provide the `MediaType` with an `HttpEntity` wrapper. - -Once the `MultiValueMap` is ready, you can pass it to the `RestTemplate`, as show below: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - MultiValueMap parts = ...; - template.postForObject("https://example.com/upload", parts, Void.class); ----- - -If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set -to `multipart/form-data` by the `FormHttpMessageConverter`. If the `MultiValueMap` has -`String` values the `Content-Type` is defaulted to `application/x-www-form-urlencoded`. -If necessary the `Content-Type` may also be set explicitly. - - -[[rest-http-interface]] -=== HTTP Interface - -The Spring Framework lets you define an HTTP service as a Java interface with annotated -methods for HTTP exchanges. You can then generate a proxy that implements this interface -and performs the exchanges. This helps to simplify HTTP remote access which often -involves a facade that wraps the details of using the underlying HTTP client. - -One, declare an interface with `@HttpExchange` methods: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - interface RepositoryService { - - @GetExchange("/repos/{owner}/{repo}") - Repository getRepository(@PathVariable String owner, @PathVariable String repo); - - // more HTTP exchange methods... - - } ----- - -Two, create a proxy that will perform the declared HTTP exchanges: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build(); - HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build(); - - RepositoryService service = factory.createClient(RepositoryService.class); ----- - -`@HttpExchange` is supported at the type level where it applies to all methods: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json") - interface RepositoryService { - - @GetExchange - Repository getRepository(@PathVariable String owner, @PathVariable String repo); - - @PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - void updateRepository(@PathVariable String owner, @PathVariable String repo, - @RequestParam String name, @RequestParam String description, @RequestParam String homepage); - - } ----- - - -[[rest-http-interface-method-parameters]] -==== Method Parameters - -Annotated, HTTP exchange methods support flexible method signatures with the following -method parameters: - -[cols="1,2", options="header"] -|=== -| Method argument | Description - -| `URI` -| Dynamically set the URL for the request, overriding the annotation's `url` attribute. - -| `HttpMethod` -| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute - -| `@RequestHeader` -| Add a request header or mutliple headers. The argument may be a `Map` or - `MultiValueMap` with multiple headers, a `Collection` of values, or an - individual value. Type conversion is supported for non-String values. - -| `@PathVariable` -| Add a variable for expand a placeholder in the request URL. The argument may be a - `Map` with multiple variables, or an individual value. Type conversion - is supported for non-String values. - -| `@RequestBody` -| Provide the body of the request either as an Object to be serialized, or a - Reactive Streams `Publisher` such as `Mono`, `Flux`, or any other async type supported - through the configured `ReactiveAdapterRegistry`. - -| `@RequestParam` -| Add a request parameter or mutliple parameters. The argument may be a `Map` - or `MultiValueMap` with multiple parameters, a `Collection` of values, or - an individual value. Type conversion is supported for non-String values. - - When `"content-type"` is set to `"application/x-www-form-urlencoded"`, request - parameters are encoded in the request body. Otherwise, they are added as URL query - parameters. - -| `@RequestPart` -| Add a request part, which may be a String (form field), `Resource` (file part), - Object (entity to be encoded, e.g. as JSON), `HttpEntity` (part content and headers), - a Spring `Part`, or Reactive Streams `Publisher` of any of the above. - -| `@CookieValue` -| Add a cookie or mutliple cookies. The argument may be a `Map` or - `MultiValueMap` with multiple cookies, a `Collection` of values, or an - individual value. Type conversion is supported for non-String values. - -|=== - - -[[rest-http-interface-return-values]] -==== Return Values - -Annotated, HTTP exchange methods support the following return values: - -[cols="1,2", options="header"] -|=== -| Method return value | Description - -| `void`, `Mono` -| Perform the given request, and release the response content, if any. - -| `HttpHeaders`, `Mono` -| Perform the given request, release the response content, if any, and return the - response headers. - -| ``, `Mono` -| Perform the given request and decode the response content to the declared return type. - -| ``, `Flux` -| Perform the given request and decode the response content to a stream of the declared - element type. - -| `ResponseEntity`, `Mono>` -| Perform the given request, and release the response content, if any, and return a - `ResponseEntity` with the status and headers. - -| `ResponseEntity`, `Mono>` -| Perform the given request, decode the response content to the declared return type, and - return a `ResponseEntity` with the status, headers, and the decoded body. - -| `Mono>` -| Perform the given request, decode the response content to a stream of the declared - element type, and return a `ResponseEntity` with the status, headers, and the decoded - response body stream. - -|=== - -TIP: You can also use any other async or reactive types registered in the -`ReactiveAdapterRegistry`. - - -[[rest-http-interface-exceptions]] -==== Exception Handling - -By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status -codes. To customize this, you can register a response status handler that applies to all -responses performed through the client: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - WebClient webClient = WebClient.builder() - .defaultStatusHandler(HttpStatusCode::isError, resp -> ...) - .build(); - - HttpServiceProxyFactory proxyFactory = - WebClientAdapter.createHttpServiceProxyFactory(webClient); ----- - -For more details and options, such as suppressing error status codes, see the Javadoc of -`defaultStatusHandler` in `WebClient.Builder`. - - - - -[[jms]] -== JMS (Java Message Service) - -Spring provides a JMS integration framework that simplifies the use of the JMS API in much -the same way as Spring's integration does for the JDBC API. - -JMS can be roughly divided into two areas of functionality, namely the production and -consumption of messages. The `JmsTemplate` class is used for message production and -synchronous message reception. For asynchronous reception similar to Jakarta EE's -message-driven bean style, Spring provides a number of message-listener containers that -you can use to create Message-Driven POJOs (MDPs). Spring also provides a declarative way -to create message listeners. - -The `org.springframework.jms.core` package provides the core functionality for using -JMS. It contains JMS template classes that simplify the use of the JMS by handling the -creation and release of resources, much like the `JdbcTemplate` does for JDBC. The -design principle common to Spring template classes is to provide helper methods to -perform common operations and, for more sophisticated usage, delegate the essence of the -processing task to user-implemented callback interfaces. The JMS template follows the -same design. The classes offer various convenience methods for sending messages, -consuming messages synchronously, and exposing the JMS session and message producer to -the user. - -The `org.springframework.jms.support` package provides `JMSException` translation -functionality. The translation converts the checked `JMSException` hierarchy to a -mirrored hierarchy of unchecked exceptions. If any provider-specific subclasses -of the checked `jakarta.jms.JMSException` exist, this exception is wrapped in the -unchecked `UncategorizedJmsException`. - -The `org.springframework.jms.support.converter` package provides a `MessageConverter` -abstraction to convert between Java objects and JMS messages. - -The `org.springframework.jms.support.destination` package provides various strategies -for managing JMS destinations, such as providing a service locator for destinations -stored in JNDI. - -The `org.springframework.jms.annotation` package provides the necessary infrastructure -to support annotation-driven listener endpoints by using `@JmsListener`. - -The `org.springframework.jms.config` package provides the parser implementation for the -`jms` namespace as well as the java config support to configure listener containers and -create listener endpoints. - -Finally, the `org.springframework.jms.connection` package provides an implementation of -the `ConnectionFactory` suitable for use in standalone applications. It also contains an -implementation of Spring's `PlatformTransactionManager` for JMS (the cunningly named -`JmsTransactionManager`). This allows for seamless integration of JMS as a transactional -resource into Spring's transaction management mechanisms. - -[NOTE] -==== -As of Spring Framework 5, Spring's JMS package fully supports JMS 2.0 and requires the -JMS 2.0 API to be present at runtime. We recommend the use of a JMS 2.0 compatible provider. - -If you happen to use an older message broker in your system, you may try upgrading to a -JMS 2.0 compatible driver for your existing broker generation. Alternatively, you may also -try to run against a JMS 1.1 based driver, simply putting the JMS 2.0 API jar on the -classpath but only using JMS 1.1 compatible API against your driver. Spring's JMS support -adheres to JMS 1.1 conventions by default, so with corresponding configuration it does -support such a scenario. However, please consider this for transition scenarios only. -==== - - - -[[jms-using]] -=== Using Spring JMS - -This section describes how to use Spring's JMS components. - - -[[jms-jmstemplate]] -==== Using `JmsTemplate` - -The `JmsTemplate` class is the central class in the JMS core package. It simplifies the -use of JMS, since it handles the creation and release of resources when sending or -synchronously receiving messages. - -Code that uses the `JmsTemplate` needs only to implement callback interfaces that give them -a clearly defined high-level contract. The `MessageCreator` callback interface creates a -message when given a `Session` provided by the calling code in `JmsTemplate`. To -allow for more complex usage of the JMS API, `SessionCallback` provides the -JMS session, and `ProducerCallback` exposes a `Session` and -`MessageProducer` pair. - -The JMS API exposes two types of send methods, one that takes delivery mode, priority, -and time-to-live as Quality of Service (QOS) parameters and one that takes no QOS -parameters and uses default values. Since `JmsTemplate` has many send methods, -setting the QOS parameters have been exposed as bean properties to -avoid duplication in the number of send methods. Similarly, the timeout value for -synchronous receive calls is set by using the `setReceiveTimeout` property. - -Some JMS providers allow the setting of default QOS values administratively through the -configuration of the `ConnectionFactory`. This has the effect that a call to a -`MessageProducer` instance's `send` method (`send(Destination destination, Message message)`) -uses different QOS default values than those specified in the JMS specification. In order -to provide consistent management of QOS values, the `JmsTemplate` must, therefore, be -specifically enabled to use its own QOS values by setting the boolean property -`isExplicitQosEnabled` to `true`. - -For convenience, `JmsTemplate` also exposes a basic request-reply operation that allows -for sending a message and waiting for a reply on a temporary queue that is created as part of -the operation. - -IMPORTANT: Instances of the `JmsTemplate` class are thread-safe, once configured. This is -important, because it means that you can configure a single instance of a `JmsTemplate` -and then safely inject this shared reference into multiple collaborators. To be -clear, the `JmsTemplate` is stateful, in that it maintains a reference to a -`ConnectionFactory`, but this state is not conversational state. - -As of Spring Framework 4.1, `JmsMessagingTemplate` is built on top of `JmsTemplate` -and provides an integration with the messaging abstraction -- that is, -`org.springframework.messaging.Message`. This lets you create the message to -send in a generic manner. - - -[[jms-connections]] -==== Connections - -The `JmsTemplate` requires a reference to a `ConnectionFactory`. The `ConnectionFactory` -is part of the JMS specification and serves as the entry point for working with JMS. It -is used by the client application as a factory to create connections with the JMS -provider and encapsulates various configuration parameters, many of which are -vendor-specific, such as SSL configuration options. - -When using JMS inside an EJB, the vendor provides implementations of the JMS interfaces -so that they can participate in declarative transaction management and perform pooling -of connections and sessions. In order to use this implementation, Jakarta EE containers -typically require that you declare a JMS connection factory as a `resource-ref` inside -the EJB or servlet deployment descriptors. To ensure the use of these features with the -`JmsTemplate` inside an EJB, the client application should ensure that it references the -managed implementation of the `ConnectionFactory`. - -[[jms-caching-resources]] -===== Caching Messaging Resources - -The standard API involves creating many intermediate objects. To send a message, the -following 'API' walk is performed: - -[literal] -[subs="verbatim,quotes"] ----- -ConnectionFactory->Connection->Session->MessageProducer->send ----- - -Between the `ConnectionFactory` and the `Send` operation, three intermediate -objects are created and destroyed. To optimize the resource usage and increase -performance, Spring provides two implementations of `ConnectionFactory`. - -[[jms-connection-factory]] -===== Using `SingleConnectionFactory` - -Spring provides an implementation of the `ConnectionFactory` interface, -`SingleConnectionFactory`, that returns the same `Connection` on all -`createConnection()` calls and ignores calls to `close()`. This is useful for testing and -standalone environments so that the same connection can be used for multiple -`JmsTemplate` calls that may span any number of transactions. `SingleConnectionFactory` -takes a reference to a standard `ConnectionFactory` that would typically come from JNDI. - -[[jdbc-connection-factory-caching]] -===== Using `CachingConnectionFactory` - -The `CachingConnectionFactory` extends the functionality of `SingleConnectionFactory` -and adds the caching of `Session`, `MessageProducer`, and `MessageConsumer` instances. -The initial cache size is set to `1`. You can use the `sessionCacheSize` property to -increase the number of cached sessions. Note that the number of actual cached sessions -is more than that number, as sessions are cached based on their acknowledgment mode, -so there can be up to four cached session instances (one for each acknowledgment mode) -when `sessionCacheSize` is set to one. `MessageProducer` and `MessageConsumer` instances -are cached within their owning session and also take into account the unique properties -of the producers and consumers when caching. MessageProducers are cached based on their -destination. MessageConsumers are cached based on a key composed of the destination, selector, -noLocal delivery flag, and the durable subscription name (if creating durable consumers). - -[NOTE] -==== -MessageProducers and MessageConsumers for temporary queues and topics -(TemporaryQueue/TemporaryTopic) will never be cached. Unfortunately, WebLogic JMS happens -to implement the temporary queue/topic interfaces on its regular destination implementation, -mis-indicating that none of its destinations can be cached. Please use a different connection -pool/cache on WebLogic, or customize `CachingConnectionFactory` for WebLogic purposes. -==== - - -[[jms-destinations]] -==== Destination Management - -Destinations, as `ConnectionFactory` instances, are JMS administered objects that you can store -and retrieve in JNDI. When configuring a Spring application context, you can use the -JNDI `JndiObjectFactoryBean` factory class or `` to perform dependency -injection on your object's references to JMS destinations. However, this strategy -is often cumbersome if there are a large number of destinations in the application or if there -are advanced destination management features unique to the JMS provider. Examples of -such advanced destination management include the creation of dynamic destinations or -support for a hierarchical namespace of destinations. The `JmsTemplate` delegates the -resolution of a destination name to a JMS destination object that implements the -`DestinationResolver` interface. `DynamicDestinationResolver` is the default -implementation used by `JmsTemplate` and accommodates resolving dynamic destinations. A -`JndiDestinationResolver` is also provided to act as a service locator for -destinations contained in JNDI and optionally falls back to the behavior contained in -`DynamicDestinationResolver`. - -Quite often, the destinations used in a JMS application are only known at runtime and, -therefore, cannot be administratively created when the application is deployed. This is -often because there is shared application logic between interacting system components -that create destinations at runtime according to a well-known naming convention. Even -though the creation of dynamic destinations is not part of the JMS specification, most -vendors have provided this functionality. Dynamic destinations are created with a user-defined name, -which differentiates them from temporary destinations, and are often -not registered in JNDI. The API used to create dynamic destinations varies from provider -to provider since the properties associated with the destination are vendor-specific. -However, a simple implementation choice that is sometimes made by vendors is to -disregard the warnings in the JMS specification and to use the method `TopicSession` -`createTopic(String topicName)` or the `QueueSession` `createQueue(String -queueName)` method to create a new destination with default destination properties. Depending -on the vendor implementation, `DynamicDestinationResolver` can then also create a -physical destination instead of only resolving one. - -The boolean property `pubSubDomain` is used to configure the `JmsTemplate` with -knowledge of what JMS domain is being used. By default, the value of this property is -false, indicating that the point-to-point domain, `Queues`, is to be used. This property -(used by `JmsTemplate`) determines the behavior of dynamic destination resolution through -implementations of the `DestinationResolver` interface. - -You can also configure the `JmsTemplate` with a default destination through the -property `defaultDestination`. The default destination is with send and receive -operations that do not refer to a specific destination. - - -[[jms-mdp]] -==== Message Listener Containers - -One of the most common uses of JMS messages in the EJB world is to drive message-driven -beans (MDBs). Spring offers a solution to create message-driven POJOs (MDPs) in a way -that does not tie a user to an EJB container. (See <> for detailed -coverage of Spring's MDP support.) Since Spring Framework 4.1, endpoint methods can be -annotated with `@JmsListener` -- see <> for more details. - -A message listener container is used to receive messages from a JMS message queue and -drive the `MessageListener` that is injected into it. The listener container is -responsible for all threading of message reception and dispatches into the listener for -processing. A message listener container is the intermediary between an MDP and a -messaging provider and takes care of registering to receive messages, participating in -transactions, resource acquisition and release, exception conversion, and so on. This -lets you write the (possibly complex) business logic -associated with receiving a message (and possibly respond to it), and delegates -boilerplate JMS infrastructure concerns to the framework. - -There are two standard JMS message listener containers packaged with Spring, each with -its specialized feature set. - -* <> -* <> - -[[jms-mdp-simple]] -===== Using `SimpleMessageListenerContainer` - -This message listener container is the simpler of the two standard flavors. It creates -a fixed number of JMS sessions and consumers at startup, registers the listener by using -the standard JMS `MessageConsumer.setMessageListener()` method, and leaves it up the JMS -provider to perform listener callbacks. This variant does not allow for dynamic adaption -to runtime demands or for participation in externally managed transactions. -Compatibility-wise, it stays very close to the spirit of the standalone JMS -specification, but is generally not compatible with Jakarta EE's JMS restrictions. - -NOTE: While `SimpleMessageListenerContainer` does not allow for participation in externally -managed transactions, it does support native JMS transactions. To enable this feature, -you can switch the `sessionTransacted` flag to `true` or, in the XML namespace, set the -`acknowledge` attribute to `transacted`. Exceptions thrown from your listener then lead -to a rollback, with the message getting redelivered. Alternatively, consider using -`CLIENT_ACKNOWLEDGE` mode, which provides redelivery in case of an exception as well but -does not use transacted `Session` instances and, therefore, does not include any other -`Session` operations (such as sending response messages) in the transaction protocol. - -IMPORTANT: The default `AUTO_ACKNOWLEDGE` mode does not provide proper reliability guarantees. -Messages can get lost when listener execution fails (since the provider automatically -acknowledges each message after listener invocation, with no exceptions to be propagated to -the provider) or when the listener container shuts down (you can configure this by setting -the `acceptMessagesWhileStopping` flag). Make sure to use transacted sessions in case of -reliability needs (for example, for reliable queue handling and durable topic subscriptions). - -[[jms-mdp-default]] -===== Using `DefaultMessageListenerContainer` - -This message listener container is used in most cases. In contrast to -`SimpleMessageListenerContainer`, this container variant allows for dynamic adaptation -to runtime demands and is able to participate in externally managed transactions. -Each received message is registered with an XA transaction when configured with a -`JtaTransactionManager`. As a result, processing may take advantage of XA transaction -semantics. This listener container strikes a good balance between low requirements on -the JMS provider, advanced functionality (such as participation in externally managed -transactions), and compatibility with Jakarta EE environments. - -You can customize the cache level of the container. Note that, when no caching is enabled, -a new connection and a new session is created for each message reception. Combining this -with a non-durable subscription with high loads may lead to message loss. Make sure to -use a proper cache level in such a case. - -This container also has recoverable capabilities when the broker goes down. By default, -a simple `BackOff` implementation retries every five seconds. You can specify -a custom `BackOff` implementation for more fine-grained recovery options. See -{api-spring-framework}/util/backoff/ExponentialBackOff.html[`ExponentialBackOff`] for an example. - -NOTE: Like its sibling (<>), -`DefaultMessageListenerContainer` supports native JMS transactions and allows for -customizing the acknowledgment mode. If feasible for your scenario, This is strongly -recommended over externally managed transactions -- that is, if you can live with -occasional duplicate messages in case of the JVM dying. Custom duplicate message -detection steps in your business logic can cover such situations -- for example, -in the form of a business entity existence check or a protocol table check. -Any such arrangements are significantly more efficient than the alternative: -wrapping your entire processing with an XA transaction (through configuring your -`DefaultMessageListenerContainer` with an `JtaTransactionManager`) to cover the -reception of the JMS message as well as the execution of the business logic in your -message listener (including database operations, etc.). - -IMPORTANT: The default `AUTO_ACKNOWLEDGE` mode does not provide proper reliability guarantees. -Messages can get lost when listener execution fails (since the provider automatically -acknowledges each message after listener invocation, with no exceptions to be propagated to -the provider) or when the listener container shuts down (you can configure this by setting -the `acceptMessagesWhileStopping` flag). Make sure to use transacted sessions in case of -reliability needs (for example, for reliable queue handling and durable topic subscriptions). - - -[[jms-tx]] -==== Transaction Management - -Spring provides a `JmsTransactionManager` that manages transactions for a single JMS -`ConnectionFactory`. This lets JMS applications leverage the managed-transaction -features of Spring, as described in -<>. -The `JmsTransactionManager` performs local resource transactions, binding a JMS -Connection/Session pair from the specified `ConnectionFactory` to the thread. -`JmsTemplate` automatically detects such transactional resources and operates -on them accordingly. - -In a Jakarta EE environment, the `ConnectionFactory` pools Connection and Session instances, -so those resources are efficiently reused across transactions. In a standalone environment, -using Spring's `SingleConnectionFactory` result in a shared JMS `Connection`, with -each transaction having its own independent `Session`. Alternatively, consider the use -of a provider-specific pooling adapter, such as ActiveMQ's `PooledConnectionFactory` -class. - -You can also use `JmsTemplate` with the `JtaTransactionManager` and an XA-capable JMS -`ConnectionFactory` to perform distributed transactions. Note that this requires the -use of a JTA transaction manager as well as a properly XA-configured ConnectionFactory. -(Check your Jakarta EE server's or JMS provider's documentation.) - -Reusing code across a managed and unmanaged transactional environment can be confusing -when using the JMS API to create a `Session` from a `Connection`. This is because the -JMS API has only one factory method to create a `Session`, and it requires values for the -transaction and acknowledgment modes. In a managed environment, setting these values is -the responsibility of the environment's transactional infrastructure, so these values -are ignored by the vendor's wrapper to the JMS Connection. When you use the `JmsTemplate` -in an unmanaged environment, you can specify these values through the use of the -properties `sessionTransacted` and `sessionAcknowledgeMode`. When you use a -`PlatformTransactionManager` with `JmsTemplate`, the template is always given a -transactional JMS `Session`. - - - -[[jms-sending]] -=== Sending a Message - -The `JmsTemplate` contains many convenience methods to send a message. Send -methods specify the destination by using a `jakarta.jms.Destination` object, and others -specify the destination by using a `String` in a JNDI lookup. The `send` method -that takes no destination argument uses the default destination. - -The following example uses the `MessageCreator` callback to create a text message from the -supplied `Session` object: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - import jakarta.jms.ConnectionFactory; - import jakarta.jms.JMSException; - import jakarta.jms.Message; - import jakarta.jms.Queue; - import jakarta.jms.Session; - - import org.springframework.jms.core.MessageCreator; - import org.springframework.jms.core.JmsTemplate; - - public class JmsQueueSender { - - private JmsTemplate jmsTemplate; - private Queue queue; - - public void setConnectionFactory(ConnectionFactory cf) { - this.jmsTemplate = new JmsTemplate(cf); - } - - public void setQueue(Queue queue) { - this.queue = queue; - } - - public void simpleSend() { - this.jmsTemplate.send(this.queue, new MessageCreator() { - public Message createMessage(Session session) throws JMSException { - return session.createTextMessage("hello queue world"); - } - }); - } - } ----- - -In the preceding example, the `JmsTemplate` is constructed by passing a reference to a -`ConnectionFactory`. As an alternative, a zero-argument constructor and -`connectionFactory` is provided and can be used for constructing the instance in -JavaBean style (using a `BeanFactory` or plain Java code). Alternatively, consider -deriving from Spring's `JmsGatewaySupport` convenience base class, which provides -pre-built bean properties for JMS configuration. - -The `send(String destinationName, MessageCreator creator)` method lets you send a -message by using the string name of the destination. If these names are registered in JNDI, -you should set the `destinationResolver` property of the template to an instance of -`JndiDestinationResolver`. - -If you created the `JmsTemplate` and specified a default destination, the -`send(MessageCreator c)` sends a message to that destination. - - -[[jms-msg-conversion]] -==== Using Message Converters - -To facilitate the sending of domain model objects, the `JmsTemplate` has -various send methods that take a Java object as an argument for a message's data -content. The overloaded methods `convertAndSend()` and `receiveAndConvert()` methods in -`JmsTemplate` delegate the conversion process to an instance of the `MessageConverter` -interface. This interface defines a simple contract to convert between Java objects and -JMS messages. The default implementation (`SimpleMessageConverter`) supports conversion -between `String` and `TextMessage`, `byte[]` and `BytesMessage`, and `java.util.Map` -and `MapMessage`. By using the converter, you and your application code can focus on the -business object that is being sent or received through JMS and not be concerned with the -details of how it is represented as a JMS message. - -The sandbox currently includes a `MapMessageConverter`, which uses reflection to convert -between a JavaBean and a `MapMessage`. Other popular implementation choices you might -implement yourself are converters that use an existing XML marshalling package (such as -JAXB or XStream) to create a `TextMessage` that represents the object. - -To accommodate the setting of a message's properties, headers, and body that can not be -generically encapsulated inside a converter class, the `MessagePostProcessor` interface -gives you access to the message after it has been converted but before it is sent. The -following example shows how to modify a message header and a property after a -`java.util.Map` is converted to a message: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public void sendWithConversion() { - Map map = new HashMap(); - map.put("Name", "Mark"); - map.put("Age", new Integer(47)); - jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() { - public Message postProcessMessage(Message message) throws JMSException { - message.setIntProperty("AccountID", 1234); - message.setJMSCorrelationID("123-00001"); - return message; - } - }); - } ----- - -This results in a message of the following form: - -[literal] -[subs="verbatim,quotes"] ----- -MapMessage={ - Header={ - ... standard headers ... - CorrelationID={123-00001} - } - Properties={ - AccountID={Integer:1234} - } - Fields={ - Name={String:Mark} - Age={Integer:47} - } -} ----- - - -[[jms-callbacks]] -==== Using `SessionCallback` and `ProducerCallback` - -While the send operations cover many common usage scenarios, you might sometimes -want to perform multiple operations on a JMS `Session` or `MessageProducer`. The -`SessionCallback` and `ProducerCallback` expose the JMS `Session` and `Session` / -`MessageProducer` pair, respectively. The `execute()` methods on `JmsTemplate` run -these callback methods. - - - -[[jms-receiving]] -=== Receiving a Message - -This describes how to receive messages with JMS in Spring. - - -[[jms-receiving-sync]] -==== Synchronous Reception - -While JMS is typically associated with asynchronous processing, you can -consume messages synchronously. The overloaded `receive(..)` methods provide this -functionality. During a synchronous receive, the calling thread blocks until a message -becomes available. This can be a dangerous operation, since the calling thread can -potentially be blocked indefinitely. The `receiveTimeout` property specifies how long -the receiver should wait before giving up waiting for a message. - - -[[jms-receiving-async]] -==== Asynchronous reception: Message-Driven POJOs - -NOTE: Spring also supports annotated-listener endpoints through the use of the `@JmsListener` -annotation and provides an open infrastructure to register endpoints programmatically. -This is, by far, the most convenient way to setup an asynchronous receiver. -See <> for more details. - -In a fashion similar to a Message-Driven Bean (MDB) in the EJB world, the Message-Driven -POJO (MDP) acts as a receiver for JMS messages. The one restriction (but see -<>) on an MDP is that it must implement -the `jakarta.jms.MessageListener` interface. Note that, if your POJO receives messages -on multiple threads, it is important to ensure that your implementation is thread-safe. - -The following example shows a simple implementation of an MDP: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - import jakarta.jms.JMSException; - import jakarta.jms.Message; - import jakarta.jms.MessageListener; - import jakarta.jms.TextMessage; - - public class ExampleListener implements MessageListener { - - public void onMessage(Message message) { - if (message instanceof TextMessage textMessage) { - try { - System.out.println(textMessage.getText()); - } - catch (JMSException ex) { - throw new RuntimeException(ex); - } - } - else { - throw new IllegalArgumentException("Message must be of type TextMessage"); - } - } - } ----- - -Once you have implemented your `MessageListener`, it is time to create a message listener -container. - -The following example shows how to define and configure one of the message listener -containers that ships with Spring (in this case, `DefaultMessageListenerContainer`): - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - ----- - -See the Spring javadoc of the various message listener containers (all of which implement -{api-spring-framework}/jms/listener/MessageListenerContainer.html[MessageListenerContainer]) -for a full description of the features supported by each implementation. - - -[[jms-receiving-async-session-aware-message-listener]] -==== Using the `SessionAwareMessageListener` Interface - -The `SessionAwareMessageListener` interface is a Spring-specific interface that provides -a similar contract to the JMS `MessageListener` interface but also gives the message-handling -method access to the JMS `Session` from which the `Message` was received. -The following listing shows the definition of the `SessionAwareMessageListener` interface: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - package org.springframework.jms.listener; - - public interface SessionAwareMessageListener { - - void onMessage(Message message, Session session) throws JMSException; - } ----- - -You can choose to have your MDPs implement this interface (in preference to the standard -JMS `MessageListener` interface) if you want your MDPs to be able to respond to any -received messages (by using the `Session` supplied in the `onMessage(Message, Session)` -method). All of the message listener container implementations that ship with Spring -have support for MDPs that implement either the `MessageListener` or -`SessionAwareMessageListener` interface. Classes that implement the -`SessionAwareMessageListener` come with the caveat that they are then tied to Spring -through the interface. The choice of whether or not to use it is left entirely up to you -as an application developer or architect. - -Note that the `onMessage(..)` method of the `SessionAwareMessageListener` -interface throws `JMSException`. In contrast to the standard JMS `MessageListener` -interface, when using the `SessionAwareMessageListener` interface, it is the -responsibility of the client code to handle any thrown exceptions. - - -[[jms-receiving-async-message-listener-adapter]] -==== Using `MessageListenerAdapter` - -The `MessageListenerAdapter` class is the final component in Spring's asynchronous -messaging support. In a nutshell, it lets you expose almost any class as an MDP -(though there are some constraints). - -Consider the following interface definition: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public interface MessageDelegate { - - void handleMessage(String message); - - void handleMessage(Map message); - - void handleMessage(byte[] message); - - void handleMessage(Serializable message); - } ----- - -Notice that, although the interface extends neither the `MessageListener` nor the -`SessionAwareMessageListener` interface, you can still use it as an MDP by using the -`MessageListenerAdapter` class. Notice also how the various message handling methods are -strongly typed according to the contents of the various `Message` types that they can -receive and handle. - -Now consider the following implementation of the `MessageDelegate` interface: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public class DefaultMessageDelegate implements MessageDelegate { - // implementation elided for clarity... - } ----- - -In particular, note how the preceding implementation of the `MessageDelegate` interface (the -`DefaultMessageDelegate` class) has no JMS dependencies at all. It truly is a -POJO that we can make into an MDP through the following configuration: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - ----- - -The next example shows another MDP that can handle only receiving JMS -`TextMessage` messages. Notice how the message handling method is actually called -`receive` (the name of the message handling method in a `MessageListenerAdapter` -defaults to `handleMessage`), but it is configurable (as you can see later in this section). Notice -also how the `receive(..)` method is strongly typed to receive and respond only to JMS -`TextMessage` messages. -The following listing shows the definition of the `TextMessageDelegate` interface: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public interface TextMessageDelegate { - - void receive(TextMessage message); - } ----- - -The following listing shows a class that implements the `TextMessageDelegate` interface: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public class DefaultTextMessageDelegate implements TextMessageDelegate { - // implementation elided for clarity... - } ----- - -The configuration of the attendant `MessageListenerAdapter` would then be as follows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - ----- - -Note that, if the `messageListener` receives a JMS `Message` of a type -other than `TextMessage`, an `IllegalStateException` is thrown (and subsequently -swallowed). Another of the capabilities of the `MessageListenerAdapter` class is the -ability to automatically send back a response `Message` if a handler method returns a -non-void value. Consider the following interface and class: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public interface ResponsiveTextMessageDelegate { - - // notice the return type... - String receive(TextMessage message); - } ----- - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate { - // implementation elided for clarity... - } ----- - -If you use the `DefaultResponsiveTextMessageDelegate` in conjunction with a -`MessageListenerAdapter`, any non-null value that is returned from the execution of -the `'receive(..)'` method is (in the default configuration) converted into a -`TextMessage`. The resulting `TextMessage` is then sent to the `Destination` (if -one exists) defined in the JMS `Reply-To` property of the original `Message` or the -default `Destination` set on the `MessageListenerAdapter` (if one has been configured). -If no `Destination` is found, an `InvalidDestinationException` is thrown -(note that this exception is not swallowed and propagates up the -call stack). - - -[[jms-tx-participation]] -==== Processing Messages Within Transactions - -Invoking a message listener within a transaction requires only reconfiguration of the -listener container. - -You can activate local resource transactions through the `sessionTransacted` flag -on the listener container definition. Each message listener invocation then operates -within an active JMS transaction, with message reception rolled back in case of listener -execution failure. Sending a response message (through `SessionAwareMessageListener`) is -part of the same local transaction, but any other resource operations (such as -database access) operate independently. This usually requires duplicate message -detection in the listener implementation, to cover the case where database processing -has committed but message processing failed to commit. - -Consider the following bean definition: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - ----- - -To participate in an externally managed transaction, you need to configure a -transaction manager and use a listener container that supports externally managed -transactions (typically, `DefaultMessageListenerContainer`). - -To configure a message listener container for XA transaction participation, you want -to configure a `JtaTransactionManager` (which, by default, delegates to the Jakarta EE -server's transaction subsystem). Note that the underlying JMS `ConnectionFactory` needs to -be XA-capable and properly registered with your JTA transaction coordinator. (Check your -Jakarta EE server's configuration of JNDI resources.) This lets message reception as well -as (for example) database access be part of the same transaction (with unified commit -semantics, at the expense of XA transaction log overhead). - -The following bean definition creates a transaction manager: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - -Then we need to add it to our earlier container configuration. The container -takes care of the rest. The following example shows how to do so: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - <1> - ----- -<1> Our transaction manager. - - - -[[jms-jca-message-endpoint-manager]] -=== Support for JCA Message Endpoints - -Beginning with version 2.5, Spring also provides support for a JCA-based -`MessageListener` container. The `JmsMessageEndpointManager` tries to -automatically determine the `ActivationSpec` class name from the provider's -`ResourceAdapter` class name. Therefore, it is typically possible to provide -Spring's generic `JmsActivationSpecConfig`, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - ----- - -Alternatively, you can set up a `JmsMessageEndpointManager` with a given -`ActivationSpec` object. The `ActivationSpec` object may also come from a JNDI lookup -(using ``). The following example shows how to do so: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - ----- - -Using Spring's `ResourceAdapterFactoryBean`, you can configure the target `ResourceAdapter` -locally, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - ----- - -The specified `WorkManager` can also point to an environment-specific thread pool -- -typically through a `SimpleTaskWorkManager` instance's `asyncTaskExecutor` property. Consider -defining a shared thread pool for all your `ResourceAdapter` instances if you happen to -use multiple adapters. - -In some environments (such as WebLogic 9 or above), you can instead obtain the entire `ResourceAdapter` object -from JNDI (by using ``). The Spring-based message -listeners can then interact with the server-hosted `ResourceAdapter`, which also use the -server's built-in `WorkManager`. - -See the javadoc for {api-spring-framework}/jms/listener/endpoint/JmsMessageEndpointManager.html[`JmsMessageEndpointManager`], -{api-spring-framework}/jms/listener/endpoint/JmsActivationSpecConfig.html[`JmsActivationSpecConfig`], -and {api-spring-framework}/jca/support/ResourceAdapterFactoryBean.html[`ResourceAdapterFactoryBean`] -for more details. - -Spring also provides a generic JCA message endpoint manager that is not tied to JMS: -`org.springframework.jca.endpoint.GenericMessageEndpointManager`. This component allows -for using any message listener type (such as a JMS `MessageListener`) and any -provider-specific `ActivationSpec` object. See your JCA provider's documentation to -find out about the actual capabilities of your connector, and see the -{api-spring-framework}/jca/endpoint/GenericMessageEndpointManager.html[`GenericMessageEndpointManager`] -javadoc for the Spring-specific configuration details. - -NOTE: JCA-based message endpoint management is very analogous to EJB 2.1 Message-Driven Beans. -It uses the same underlying resource provider contract. As with EJB 2.1 MDBs, you can use any -message listener interface supported by your JCA provider in the Spring context as well. -Spring nevertheless provides explicit "`convenience`" support for JMS, because JMS is the -most common endpoint API used with the JCA endpoint management contract. - - - -[[jms-annotated]] -=== Annotation-driven Listener Endpoints - -The easiest way to receive a message asynchronously is to use the annotated listener -endpoint infrastructure. In a nutshell, it lets you expose a method of a managed -bean as a JMS listener endpoint. The following example shows how to use it: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Component - public class MyService { - - @JmsListener(destination = "myDestination") - public void processOrder(String data) { ... } - } ----- - -The idea of the preceding example is that, whenever a message is available on the -`jakarta.jms.Destination` `myDestination`, the `processOrder` method is invoked -accordingly (in this case, with the content of the JMS message, similar to -what the <> -provides). - -The annotated endpoint infrastructure creates a message listener container -behind the scenes for each annotated method, by using a `JmsListenerContainerFactory`. -Such a container is not registered against the application context but can be easily -located for management purposes by using the `JmsListenerEndpointRegistry` bean. - -TIP: `@JmsListener` is a repeatable annotation on Java 8, so you can associate -several JMS destinations with the same method by adding additional `@JmsListener` -declarations to it. - - -[[jms-annotated-support]] -==== Enable Listener Endpoint Annotations - -To enable support for `@JmsListener` annotations, you can add `@EnableJms` to one of -your `@Configuration` classes, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Configuration - @EnableJms - public class AppConfig { - - @Bean - public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() { - DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); - factory.setConnectionFactory(connectionFactory()); - factory.setDestinationResolver(destinationResolver()); - factory.setSessionTransacted(true); - factory.setConcurrency("3-10"); - return factory; - } - } ----- - -By default, the infrastructure looks for a bean named `jmsListenerContainerFactory` -as the source for the factory to use to create message listener containers. In this -case (and ignoring the JMS infrastructure setup), you can invoke the `processOrder` -method with a core poll size of three threads and a maximum pool size of ten threads. - -You can customize the listener container factory to use for each annotation or you can -configure an explicit default by implementing the `JmsListenerConfigurer` interface. -The default is required only if at least one endpoint is registered without a specific -container factory. See the javadoc of classes that implement -{api-spring-framework}/jms/annotation/JmsListenerConfigurer.html[`JmsListenerConfigurer`] -for details and examples. - -If you prefer <>, you can use the `` -element, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - ----- - - -[[jms-annotated-programmatic-registration]] -==== Programmatic Endpoint Registration - -`JmsListenerEndpoint` provides a model of a JMS endpoint and is responsible for configuring -the container for that model. The infrastructure lets you programmatically configure endpoints -in addition to the ones that are detected by the `JmsListener` annotation. -The following example shows how to do so: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Configuration - @EnableJms - public class AppConfig implements JmsListenerConfigurer { - - @Override - public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) { - SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint(); - endpoint.setId("myJmsEndpoint"); - endpoint.setDestination("anotherQueue"); - endpoint.setMessageListener(message -> { - // processing - }); - registrar.registerEndpoint(endpoint); - } - } ----- - -In the preceding example, we used `SimpleJmsListenerEndpoint`, which provides the actual -`MessageListener` to invoke. However, you could also build your own endpoint variant -to describe a custom invocation mechanism. - -Note that you could skip the use of `@JmsListener` altogether -and programmatically register only your endpoints through `JmsListenerConfigurer`. - - -[[jms-annotated-method-signature]] -==== Annotated Endpoint Method Signature - -So far, we have been injecting a simple `String` in our endpoint, but it can actually -have a very flexible method signature. In the following example, we rewrite it to inject the `Order` with -a custom header: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Component - public class MyService { - - @JmsListener(destination = "myDestination") - public void processOrder(Order order, @Header("order_type") String orderType) { - ... - } - } ----- - -The main elements you can inject in JMS listener endpoints are as follows: - -* The raw `jakarta.jms.Message` or any of its subclasses (provided that it - matches the incoming message type). -* The `jakarta.jms.Session` for optional access to the native JMS API (for example, for sending - a custom reply). -* The `org.springframework.messaging.Message` that represents the incoming JMS message. - Note that this message holds both the custom and the standard headers (as defined - by `JmsHeaders`). -* `@Header`-annotated method arguments to extract a specific header value, including - standard JMS headers. -* A `@Headers`-annotated argument that must also be assignable to `java.util.Map` for - getting access to all headers. -* A non-annotated element that is not one of the supported types (`Message` or - `Session`) is considered to be the payload. You can make that explicit by annotating - the parameter with `@Payload`. You can also turn on validation by adding an extra - `@Valid`. - -The ability to inject Spring's `Message` abstraction is particularly useful to benefit -from all the information stored in the transport-specific message without relying on -transport-specific API. The following example shows how to do so: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @JmsListener(destination = "myDestination") - public void processOrder(Message order) { ... } ----- - -Handling of method arguments is provided by `DefaultMessageHandlerMethodFactory`, which you can -further customize to support additional method arguments. You can customize the conversion and validation -support there as well. - -For instance, if we want to make sure our `Order` is valid before processing it, we can -annotate the payload with `@Valid` and configure the necessary validator, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Configuration - @EnableJms - public class AppConfig implements JmsListenerConfigurer { - - @Override - public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) { - registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory()); - } - - @Bean - public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() { - DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); - factory.setValidator(myValidator()); - return factory; - } - } ----- - - -[[jms-annotated-response]] -==== Response Management - -The existing support in <> -already lets your method have a non-`void` return type. When that is the case, the result of -the invocation is encapsulated in a `jakarta.jms.Message`, sent either in the destination specified -in the `JMSReplyTo` header of the original message or in the default destination configured on -the listener. You can now set that default destination by using the `@SendTo` annotation of the -messaging abstraction. - -Assuming that our `processOrder` method should now return an `OrderStatus`, we can write it -to automatically send a response, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @JmsListener(destination = "myDestination") - @SendTo("status") - public OrderStatus processOrder(Order order) { - // order processing - return status; - } ----- - -TIP: If you have several `@JmsListener`-annotated methods, you can also place the `@SendTo` -annotation at the class level to share a default reply destination. - -If you need to set additional headers in a transport-independent manner, you can return a -`Message` instead, with a method similar to the following: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @JmsListener(destination = "myDestination") - @SendTo("status") - public Message processOrder(Order order) { - // order processing - return MessageBuilder - .withPayload(status) - .setHeader("code", 1234) - .build(); - } ----- - -If you need to compute the response destination at runtime, you can encapsulate your response -in a `JmsResponse` instance that also provides the destination to use at runtime. We can rewrite the previous -example as follows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @JmsListener(destination = "myDestination") - public JmsResponse> processOrder(Order order) { - // order processing - Message response = MessageBuilder - .withPayload(status) - .setHeader("code", 1234) - .build(); - return JmsResponse.forQueue(response, "status"); - } ----- - -Finally, if you need to specify some QoS values for the response such as the priority or -the time to live, you can configure the `JmsListenerContainerFactory` accordingly, -as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Configuration - @EnableJms - public class AppConfig { - - @Bean - public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() { - DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); - factory.setConnectionFactory(connectionFactory()); - QosSettings replyQosSettings = new QosSettings(); - replyQosSettings.setPriority(2); - replyQosSettings.setTimeToLive(10000); - factory.setReplyQosSettings(replyQosSettings); - return factory; - } - } ----- - - - -[[jms-namespace]] -=== JMS Namespace Support - -Spring provides an XML namespace for simplifying JMS configuration. To use the JMS -namespace elements, you need to reference the JMS schema, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - xsi:schemaLocation=" - http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/jms https://www.springframework.org/schema/jms/spring-jms.xsd"> - - - - ----- -<1> Referencing the JMS schema. - - -The namespace consists of three top-level elements: ``, `` -and ``. `` enables the use of <>. `` and `` -define shared listener container configuration and can contain `` child elements. -The following example shows a basic configuration for two listeners: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - ----- - -The preceding example is equivalent to creating two distinct listener container bean -definitions and two distinct `MessageListenerAdapter` bean definitions, as shown -in <>. In addition to the attributes shown -in the preceding example, the `listener` element can contain several optional ones. -The following table describes all of the available attributes: - -[[jms-namespace-listener-tbl]] -.Attributes of the JMS element -[cols="1,6"] -|=== -| Attribute | Description - -| `id` -| A bean name for the hosting listener container. If not specified, a bean name is - automatically generated. - -| `destination` (required) -| The destination name for this listener, resolved through the `DestinationResolver` - strategy. - -| `ref` (required) -| The bean name of the handler object. - -| `method` -| The name of the handler method to invoke. If the `ref` attribute points to a `MessageListener` - or Spring `SessionAwareMessageListener`, you can omit this attribute. - -| `response-destination` -| The name of the default response destination to which to send response messages. This is - applied in case of a request message that does not carry a `JMSReplyTo` field. The - type of this destination is determined by the listener-container's - `response-destination-type` attribute. Note that this applies only to a listener method with a - return value, for which each result object is converted into a response message. - -| `subscription` -| The name of the durable subscription, if any. - -| `selector` -| An optional message selector for this listener. - -| `concurrency` -| The number of concurrent sessions or consumers to start for this listener. This value can either be - a simple number indicating the maximum number (for example, `5`) or a range indicating the lower - as well as the upper limit (for example, `3-5`). Note that a specified minimum is only a hint - and might be ignored at runtime. The default is the value provided by the container. -|=== - -The `` element also accepts several optional attributes. This -allows for customization of the various strategies (for example, `taskExecutor` and -`destinationResolver`) as well as basic JMS settings and resource references. By using -these attributes, you can define highly-customized listener containers while -still benefiting from the convenience of the namespace. - -You can automatically expose such settings as a `JmsListenerContainerFactory` by -specifying the `id` of the bean to expose through the `factory-id` attribute, -as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - ----- - -The following table describes all available attributes. See the class-level javadoc -of the {api-spring-framework}/jms/listener/AbstractMessageListenerContainer.html[`AbstractMessageListenerContainer`] -and its concrete subclasses for more details on the individual properties. The javadoc -also provides a discussion of transaction choices and message redelivery scenarios. - -[[jms-namespace-listener-container-tbl]] -.Attributes of the JMS element -[cols="1,6"] -|=== -| Attribute | Description - -| `container-type` -| The type of this listener container. The available options are `default`, `simple`, - `default102`, or `simple102` (the default option is `default`). - -| `container-class` -| A custom listener container implementation class as a fully qualified class name. - The default is Spring's standard `DefaultMessageListenerContainer` or - `SimpleMessageListenerContainer`, according to the `container-type` attribute. - -| `factory-id` -| Exposes the settings defined by this element as a `JmsListenerContainerFactory` - with the specified `id` so that they can be reused with other endpoints. - -| `connection-factory` -| A reference to the JMS `ConnectionFactory` bean (the default bean name is - `connectionFactory`). - -| `task-executor` -| A reference to the Spring `TaskExecutor` for the JMS listener invokers. - -| `destination-resolver` -| A reference to the `DestinationResolver` strategy for resolving JMS `Destination` instances. - -| `message-converter` -| A reference to the `MessageConverter` strategy for converting JMS Messages to listener - method arguments. The default is a `SimpleMessageConverter`. - -| `error-handler` -| A reference to an `ErrorHandler` strategy for handling any uncaught exceptions that - may occur during the execution of the `MessageListener`. - -| `destination-type` -| The JMS destination type for this listener: `queue`, `topic`, `durableTopic`, `sharedTopic`, - or `sharedDurableTopic`. This potentially enables the `pubSubDomain`, `subscriptionDurable` - and `subscriptionShared` properties of the container. The default is `queue` (which disables - those three properties). - -| `response-destination-type` -| The JMS destination type for responses: `queue` or `topic`. The default is the value of the - `destination-type` attribute. - -| `client-id` -| The JMS client ID for this listener container. You must specify it when you use - durable subscriptions. - -| `cache` -| The cache level for JMS resources: `none`, `connection`, `session`, `consumer`, or - `auto`. By default (`auto`), the cache level is effectively `consumer`, unless - an external transaction manager has been specified -- in which case, the effective - default will be `none` (assuming Jakarta EE-style transaction management, where the given - ConnectionFactory is an XA-aware pool). - -| `acknowledge` -| The native JMS acknowledge mode: `auto`, `client`, `dups-ok`, or `transacted`. A value - of `transacted` activates a locally transacted `Session`. As an alternative, you can specify - the `transaction-manager` attribute, described later in table. The default is `auto`. - -| `transaction-manager` -| A reference to an external `PlatformTransactionManager` (typically an XA-based - transaction coordinator, such as Spring's `JtaTransactionManager`). If not specified, - native acknowledging is used (see the `acknowledge` attribute). - -| `concurrency` -| The number of concurrent sessions or consumers to start for each listener. It can either be - a simple number indicating the maximum number (for example, `5`) or a range indicating the - lower as well as the upper limit (for example, `3-5`). Note that a specified minimum is just a - hint and might be ignored at runtime. The default is `1`. You should keep concurrency limited to `1` in - case of a topic listener or if queue ordering is important. Consider raising it for - general queues. - -| `prefetch` -| The maximum number of messages to load into a single session. Note that raising this - number might lead to starvation of concurrent consumers. - -| `receive-timeout` -| The timeout (in milliseconds) to use for receive calls. The default is `1000` (one - second). `-1` indicates no timeout. - -| `back-off` -| Specifies the `BackOff` instance to use to compute the interval between recovery - attempts. If the `BackOffExecution` implementation returns `BackOffExecution#STOP`, - the listener container does not further try to recover. The `recovery-interval` - value is ignored when this property is set. The default is a `FixedBackOff` with - an interval of 5000 milliseconds (that is, five seconds). - -| `recovery-interval` -| Specifies the interval between recovery attempts, in milliseconds. It offers a convenient - way to create a `FixedBackOff` with the specified interval. For more recovery - options, consider specifying a `BackOff` instance instead. The default is 5000 milliseconds - (that is, five seconds). - -| `phase` -| The lifecycle phase within which this container should start and stop. The lower the - value, the earlier this container starts and the later it stops. The default is - `Integer.MAX_VALUE`, meaning that the container starts as late as possible and stops as - soon as possible. -|=== - -Configuring a JCA-based listener container with the `jms` schema support is very similar, -as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - ----- - -The following table describes the available configuration options for the JCA variant: - -[[jms-namespace-jca-listener-container-tbl]] -.Attributes of the JMS element -[cols="1,6"] -|=== -| Attribute | Description - -| `factory-id` -| Exposes the settings defined by this element as a `JmsListenerContainerFactory` - with the specified `id` so that they can be reused with other endpoints. - -| `resource-adapter` -| A reference to the JCA `ResourceAdapter` bean (the default bean name is - `resourceAdapter`). - -| `activation-spec-factory` -| A reference to the `JmsActivationSpecFactory`. The default is to autodetect the JMS - provider and its `ActivationSpec` class (see {api-spring-framework}/jms/listener/endpoint/DefaultJmsActivationSpecFactory.html[`DefaultJmsActivationSpecFactory`]). - -| `destination-resolver` -| A reference to the `DestinationResolver` strategy for resolving JMS `Destinations`. - -| `message-converter` -| A reference to the `MessageConverter` strategy for converting JMS Messages to listener - method arguments. The default is `SimpleMessageConverter`. - -| `destination-type` -| The JMS destination type for this listener: `queue`, `topic`, `durableTopic`, `sharedTopic`. - or `sharedDurableTopic`. This potentially enables the `pubSubDomain`, `subscriptionDurable`, - and `subscriptionShared` properties of the container. The default is `queue` (which disables - those three properties). - -| `response-destination-type` -| The JMS destination type for responses: `queue` or `topic`. The default is the value of the - `destination-type` attribute. - -| `client-id` -| The JMS client ID for this listener container. It needs to be specified when using - durable subscriptions. - -| `acknowledge` -| The native JMS acknowledge mode: `auto`, `client`, `dups-ok`, or `transacted`. A value - of `transacted` activates a locally transacted `Session`. As an alternative, you can specify - the `transaction-manager` attribute described later. The default is `auto`. - -| `transaction-manager` -| A reference to a Spring `JtaTransactionManager` or a - `jakarta.transaction.TransactionManager` for kicking off an XA transaction for each - incoming message. If not specified, native acknowledging is used (see the - `acknowledge` attribute). - -| `concurrency` -| The number of concurrent sessions or consumers to start for each listener. It can either be - a simple number indicating the maximum number (for example `5`) or a range indicating the - lower as well as the upper limit (for example, `3-5`). Note that a specified minimum is only a - hint and is typically ignored at runtime when you use a JCA listener container. - The default is 1. - -| `prefetch` -| The maximum number of messages to load into a single session. Note that raising this - number might lead to starvation of concurrent consumers. -|=== - - - - -[[jmx]] -== JMX - -The JMX (Java Management Extensions) support in Spring provides features that let you -easily and transparently integrate your Spring application into a JMX infrastructure. - -.JMX? -**** -This chapter is not an introduction to JMX. It does not try to explain why you might want -to use JMX. If you are new to JMX, see <> at the end of this chapter. -**** - -Specifically, Spring's JMX support provides four core features: - -* The automatic registration of any Spring bean as a JMX MBean. -* A flexible mechanism for controlling the management interface of your beans. -* The declarative exposure of MBeans over remote, JSR-160 connectors. -* The simple proxying of both local and remote MBean resources. - -These features are designed to work without coupling your application components to -either Spring or JMX interfaces and classes. Indeed, for the most part, your application -classes need not be aware of either Spring or JMX in order to take advantage of the -Spring JMX features. - - - -[[jmx-exporting]] -=== Exporting Your Beans to JMX - -The core class in Spring's JMX framework is the `MBeanExporter`. This class is -responsible for taking your Spring beans and registering them with a JMX `MBeanServer`. -For example, consider the following class: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - package org.springframework.jmx; - - public class JmxTestBean implements IJmxTestBean { - - private String name; - private int age; - private boolean isSuperman; - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - public void setName(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public int add(int x, int y) { - return x + y; - } - - public void dontExposeMe() { - throw new RuntimeException(); - } - } ----- - -To expose the properties and methods of this bean as attributes and operations of an -MBean, you can configure an instance of the `MBeanExporter` class in your -configuration file and pass in the bean, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - ----- - -The pertinent bean definition from the preceding configuration snippet is the `exporter` -bean. The `beans` property tells the `MBeanExporter` exactly which of your beans must be -exported to the JMX `MBeanServer`. In the default configuration, the key of each entry -in the `beans` `Map` is used as the `ObjectName` for the bean referenced by the -corresponding entry value. You can change this behavior, as described in <>. - -With this configuration, the `testBean` bean is exposed as an MBean under the -`ObjectName` `bean:name=testBean1`. By default, all `public` properties of the bean -are exposed as attributes and all `public` methods (except those inherited from the -`Object` class) are exposed as operations. - -NOTE: `MBeanExporter` is a `Lifecycle` bean (see <>). By default, MBeans are exported as late as possible during -the application lifecycle. You can configure the `phase` at which -the export happens or disable automatic registration by setting the `autoStartup` flag. - - -[[jmx-exporting-mbeanserver]] -==== Creating an MBeanServer - -The configuration shown in the <> assumes that the -application is running in an environment that has one (and only one) `MBeanServer` -already running. In this case, Spring tries to locate the running `MBeanServer` and -register your beans with that server (if any). This behavior is useful when your -application runs inside a container (such as Tomcat or IBM WebSphere) that has its -own `MBeanServer`. - -However, this approach is of no use in a standalone environment or when running inside -a container that does not provide an `MBeanServer`. To address this, you can create an -`MBeanServer` instance declaratively by adding an instance of the -`org.springframework.jmx.support.MBeanServerFactoryBean` class to your configuration. -You can also ensure that a specific `MBeanServer` is used by setting the value of the -`MBeanExporter` instance's `server` property to the `MBeanServer` value returned by an -`MBeanServerFactoryBean`, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - - - - - - - ----- - -In the preceding example, an instance of `MBeanServer` is created by the `MBeanServerFactoryBean` and is -supplied to the `MBeanExporter` through the `server` property. When you supply your own -`MBeanServer` instance, the `MBeanExporter` does not try to locate a running -`MBeanServer` and uses the supplied `MBeanServer` instance. For this to work -correctly, you must have a JMX implementation on your classpath. - - -[[jmx-mbean-server]] -==== Reusing an Existing `MBeanServer` - -If no server is specified, the `MBeanExporter` tries to automatically detect a running -`MBeanServer`. This works in most environments, where only one `MBeanServer` instance is -used. However, when multiple instances exist, the exporter might pick the wrong server. -In such cases, you should use the `MBeanServer` `agentId` to indicate which instance to -be used, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - ... - - ----- - -For platforms or cases where the existing `MBeanServer` has a dynamic (or unknown) -`agentId` that is retrieved through lookup methods, you should use -<>, -as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - ----- - - -[[jmx-exporting-lazy]] -==== Lazily Initialized MBeans - -If you configure a bean with an `MBeanExporter` that is also configured for lazy -initialization, the `MBeanExporter` does not break this contract and avoids -instantiating the bean. Instead, it registers a proxy with the `MBeanServer` and -defers obtaining the bean from the container until the first invocation on the proxy -occurs. - - -[[jmx-exporting-auto]] -==== Automatic Registration of MBeans - -Any beans that are exported through the `MBeanExporter` and are already valid MBeans are -registered as-is with the `MBeanServer` without further intervention from Spring. You can cause MBeans -to be automatically detected by the `MBeanExporter` by setting the `autodetect` -property to `true`, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - ----- - -In the preceding example, the bean called `spring:mbean=true` is already a valid JMX MBean -and is automatically registered by Spring. By default, a bean that is autodetected for JMX -registration has its bean name used as the `ObjectName`. You can override this behavior, -as detailed in <>. - - -[[jmx-exporting-registration-behavior]] -==== Controlling the Registration Behavior - -Consider the scenario where a Spring `MBeanExporter` attempts to register an `MBean` -with an `MBeanServer` by using the `ObjectName` `bean:name=testBean1`. If an `MBean` -instance has already been registered under that same `ObjectName`, the default behavior -is to fail (and throw an `InstanceAlreadyExistsException`). - -You can control exactly what happens when an `MBean` is -registered with an `MBeanServer`. Spring's JMX support allows for three different -registration behaviors to control the registration behavior when the registration -process finds that an `MBean` has already been registered under the same `ObjectName`. -The following table summarizes these registration behaviors: - -[[jmx-registration-behaviors]] -.Registration Behaviors -[cols="1,4"] -|=== -| Registration behavior | Explanation - -| `FAIL_ON_EXISTING` -| This is the default registration behavior. If an `MBean` instance has already been - registered under the same `ObjectName`, the `MBean` that is being registered is not - registered, and an `InstanceAlreadyExistsException` is thrown. The existing - `MBean` is unaffected. - -| `IGNORE_EXISTING` -| If an `MBean` instance has already been registered under the same `ObjectName`, the - `MBean` that is being registered is not registered. The existing `MBean` is - unaffected, and no `Exception` is thrown. This is useful in settings where - multiple applications want to share a common `MBean` in a shared `MBeanServer`. - -| `REPLACE_EXISTING` -| If an `MBean` instance has already been registered under the same `ObjectName`, the - existing `MBean` that was previously registered is unregistered, and the new - `MBean` is registered in its place (the new `MBean` effectively replaces the - previous instance). -|=== - -The values in the preceding table are defined as enums on the `RegistrationPolicy` class. -If you want to change the default registration behavior, you need to set the value of the -`registrationPolicy` property on your `MBeanExporter` definition to one of those -values. - -The following example shows how to change from the default registration -behavior to the `REPLACE_EXISTING` behavior: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - - - - ----- - - - -[[jmx-interface]] -=== Controlling the Management Interface of Your Beans - -In the example in the <>, -you had little control over the management interface of your bean. All of the `public` -properties and methods of each exported bean were exposed as JMX attributes and -operations, respectively. To exercise finer-grained control over exactly which -properties and methods of your exported beans are actually exposed as JMX attributes -and operations, Spring JMX provides a comprehensive and extensible mechanism for -controlling the management interfaces of your beans. - - -[[jmx-interface-assembler]] -==== Using the `MBeanInfoAssembler` Interface - -Behind the scenes, the `MBeanExporter` delegates to an implementation of the -`org.springframework.jmx.export.assembler.MBeanInfoAssembler` interface, which is -responsible for defining the management interface of each bean that is exposed. -The default implementation, -`org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler`, -defines a management interface that exposes all public properties and methods -(as you saw in the examples in the preceding sections). Spring provides two -additional implementations of the `MBeanInfoAssembler` interface that let you -control the generated management interface by using either source-level metadata -or any arbitrary interface. - - -[[jmx-interface-metadata]] -==== Using Source-level Metadata: Java Annotations - -By using the `MetadataMBeanInfoAssembler`, you can define the management interfaces -for your beans by using source-level metadata. The reading of metadata is encapsulated -by the `org.springframework.jmx.export.metadata.JmxAttributeSource` interface. -Spring JMX provides a default implementation that uses Java annotations, namely -`org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource`. -You must configure the `MetadataMBeanInfoAssembler` with an implementation instance of -the `JmxAttributeSource` interface for it to function correctly (there is no default). - -To mark a bean for export to JMX, you should annotate the bean class with the -`ManagedResource` annotation. You must mark each method you wish to expose as an operation -with the `ManagedOperation` annotation and mark each property you wish to expose -with the `ManagedAttribute` annotation. When marking properties, you can omit -either the annotation of the getter or the setter to create a write-only or read-only -attribute, respectively. - -NOTE: A `ManagedResource`-annotated bean must be public, as must the methods exposing -an operation or an attribute. - -The following example shows the annotated version of the `JmxTestBean` class that we -used in <>: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - package org.springframework.jmx; - - import org.springframework.jmx.export.annotation.ManagedResource; - import org.springframework.jmx.export.annotation.ManagedOperation; - import org.springframework.jmx.export.annotation.ManagedAttribute; - - @ManagedResource( - objectName="bean:name=testBean4", - description="My Managed Bean", - log=true, - logFile="jmx.log", - currencyTimeLimit=15, - persistPolicy="OnUpdate", - persistPeriod=200, - persistLocation="foo", - persistName="bar") - public class AnnotationTestBean implements IJmxTestBean { - - private String name; - private int age; - - @ManagedAttribute(description="The Age Attribute", currencyTimeLimit=15) - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - @ManagedAttribute(description="The Name Attribute", - currencyTimeLimit=20, - defaultValue="bar", - persistPolicy="OnUpdate") - public void setName(String name) { - this.name = name; - } - - @ManagedAttribute(defaultValue="foo", persistPeriod=300) - public String getName() { - return name; - } - - @ManagedOperation(description="Add two numbers") - @ManagedOperationParameters({ - @ManagedOperationParameter(name = "x", description = "The first number"), - @ManagedOperationParameter(name = "y", description = "The second number")}) - public int add(int x, int y) { - return x + y; - } - - public void dontExposeMe() { - throw new RuntimeException(); - } - - } ----- - -In the preceding example, you can see that the `JmxTestBean` class is marked with the -`ManagedResource` annotation and that this `ManagedResource` annotation is configured -with a set of properties. These properties can be used to configure various aspects -of the MBean that is generated by the `MBeanExporter` and are explained in greater -detail later in <>. - -Both the `age` and `name` properties are annotated with the `ManagedAttribute` -annotation, but, in the case of the `age` property, only the getter is marked. -This causes both of these properties to be included in the management interface -as attributes, but the `age` attribute is read-only. - -Finally, the `add(int, int)` method is marked with the `ManagedOperation` attribute, -whereas the `dontExposeMe()` method is not. This causes the management interface to -contain only one operation (`add(int, int)`) when you use the `MetadataMBeanInfoAssembler`. - -The following configuration shows how you can configure the `MBeanExporter` to use the -`MetadataMBeanInfoAssembler`: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - - - - - - - - - - - ----- - -In the preceding example, an `MetadataMBeanInfoAssembler` bean has been configured with an -instance of the `AnnotationJmxAttributeSource` class and passed to the `MBeanExporter` -through the assembler property. This is all that is required to take advantage of -metadata-driven management interfaces for your Spring-exposed MBeans. - - -[[jmx-interface-metadata-types]] -==== Source-level Metadata Types - -The following table describes the source-level metadata types that are available for use in Spring JMX: - -[[jmx-metadata-types]] -.Source-level metadata types -|=== -| Purpose| Annotation| Annotation Type - -| Mark all instances of a `Class` as JMX managed resources. -| `@ManagedResource` -| Class - -| Mark a method as a JMX operation. -| `@ManagedOperation` -| Method - -| Mark a getter or setter as one half of a JMX attribute. -| `@ManagedAttribute` -| Method (only getters and setters) - -| Define descriptions for operation parameters. -| `@ManagedOperationParameter` and `@ManagedOperationParameters` -| Method -|=== - -The following table describes the configuration parameters that are available for use on these source-level -metadata types: - -[[jmx-metadata-parameters]] -.Source-level metadata parameters -[cols="1,3,1"] -|=== -| Parameter | Description | Applies to - -| `ObjectName` -| Used by `MetadataNamingStrategy` to determine the `ObjectName` of a managed resource. -| `ManagedResource` - -| `description` -| Sets the friendly description of the resource, attribute or operation. -| `ManagedResource`, `ManagedAttribute`, `ManagedOperation`, or `ManagedOperationParameter` - -| `currencyTimeLimit` -| Sets the value of the `currencyTimeLimit` descriptor field. -| `ManagedResource` or `ManagedAttribute` - -| `defaultValue` -| Sets the value of the `defaultValue` descriptor field. -| `ManagedAttribute` - -| `log` -| Sets the value of the `log` descriptor field. -| `ManagedResource` - -| `logFile` -| Sets the value of the `logFile` descriptor field. -| `ManagedResource` - -| `persistPolicy` -| Sets the value of the `persistPolicy` descriptor field. -| `ManagedResource` - -| `persistPeriod` -| Sets the value of the `persistPeriod` descriptor field. -| `ManagedResource` - -| `persistLocation` -| Sets the value of the `persistLocation` descriptor field. -| `ManagedResource` - -| `persistName` -| Sets the value of the `persistName` descriptor field. -| `ManagedResource` - -| `name` -| Sets the display name of an operation parameter. -| `ManagedOperationParameter` - -| `index` -| Sets the index of an operation parameter. -| `ManagedOperationParameter` -|=== - - -[[jmx-interface-autodetect]] -==== Using the `AutodetectCapableMBeanInfoAssembler` Interface - -To simplify configuration even further, Spring includes the -`AutodetectCapableMBeanInfoAssembler` interface, which extends the `MBeanInfoAssembler` -interface to add support for autodetection of MBean resources. If you configure the -`MBeanExporter` with an instance of `AutodetectCapableMBeanInfoAssembler`, it is -allowed to "`vote`" on the inclusion of beans for exposure to JMX. - -The only implementation of the `AutodetectCapableMBeanInfo` interface is -the `MetadataMBeanInfoAssembler`, which votes to include any bean that is marked -with the `ManagedResource` attribute. The default approach in this case is to use the -bean name as the `ObjectName`, which results in a configuration similar to the following: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - - - - - - - ----- - -Notice that, in the preceding configuration, no beans are passed to the `MBeanExporter`. -However, the `JmxTestBean` is still registered, since it is marked with the `ManagedResource` -attribute and the `MetadataMBeanInfoAssembler` detects this and votes to include it. -The only problem with this approach is that the name of the `JmxTestBean` now has business -meaning. You can address this issue by changing the default behavior for `ObjectName` -creation as defined in <>. - - -[[jmx-interface-java]] -==== Defining Management Interfaces by Using Java Interfaces - -In addition to the `MetadataMBeanInfoAssembler`, Spring also includes the -`InterfaceBasedMBeanInfoAssembler`, which lets you constrain the methods and -properties that are exposed based on the set of methods defined in a collection of -interfaces. - -Although the standard mechanism for exposing MBeans is to use interfaces and a simple -naming scheme, `InterfaceBasedMBeanInfoAssembler` extends this functionality by -removing the need for naming conventions, letting you use more than one interface -and removing the need for your beans to implement the MBean interfaces. - -Consider the following interface, which is used to define a management interface for the -`JmxTestBean` class that we showed earlier: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public interface IJmxTestBean { - - public int add(int x, int y); - - public long myOperation(); - - public int getAge(); - - public void setAge(int age); - - public void setName(String name); - - public String getName(); - - } ----- - -This interface defines the methods and properties that are exposed as operations and -attributes on the JMX MBean. The following code shows how to configure Spring JMX to use -this interface as the definition for the management interface: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - org.springframework.jmx.IJmxTestBean - - - - - - - - - - - ----- - -In the preceding example, the `InterfaceBasedMBeanInfoAssembler` is configured to use the -`IJmxTestBean` interface when constructing the management interface for any bean. It is -important to understand that beans processed by the `InterfaceBasedMBeanInfoAssembler` -are not required to implement the interface used to generate the JMX management -interface. - -In the preceding case, the `IJmxTestBean` interface is used to construct all management -interfaces for all beans. In many cases, this is not the desired behavior, and you may -want to use different interfaces for different beans. In this case, you can pass -`InterfaceBasedMBeanInfoAssembler` a `Properties` instance through the `interfaceMappings` -property, where the key of each entry is the bean name and the value of each entry is a -comma-separated list of interface names to use for that bean. - -If no management interface is specified through either the `managedInterfaces` or -`interfaceMappings` properties, the `InterfaceBasedMBeanInfoAssembler` reflects -on the bean and uses all of the interfaces implemented by that bean to create the -management interface. - - -[[jmx-interface-methodnames]] -==== Using `MethodNameBasedMBeanInfoAssembler` - -`MethodNameBasedMBeanInfoAssembler` lets you specify a list of method names -that are exposed to JMX as attributes and operations. The following code shows a sample -configuration: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - add,myOperation,getName,setName,getAge - - - - ----- - -In the preceding example, you can see that the `add` and `myOperation` methods are exposed as JMX -operations, and `getName()`, `setName(String)`, and `getAge()` are exposed as the -appropriate half of a JMX attribute. In the preceding code, the method mappings apply to -beans that are exposed to JMX. To control method exposure on a bean-by-bean basis, you can use -the `methodMappings` property of `MethodNameMBeanInfoAssembler` to map bean names to -lists of method names. - - - -[[jmx-naming]] -=== Controlling `ObjectName` Instances for Your Beans - -Behind the scenes, the `MBeanExporter` delegates to an implementation of the -`ObjectNamingStrategy` to obtain an `ObjectName` instance for each of the beans it registers. -By default, the default implementation, `KeyNamingStrategy` uses the key of the -`beans` `Map` as the `ObjectName`. In addition, the `KeyNamingStrategy` can map the key -of the `beans` `Map` to an entry in a `Properties` file (or files) to resolve the -`ObjectName`. In addition to the `KeyNamingStrategy`, Spring provides two additional -`ObjectNamingStrategy` implementations: the `IdentityNamingStrategy` (which builds an -`ObjectName` based on the JVM identity of the bean) and the `MetadataNamingStrategy` (which -uses source-level metadata to obtain the `ObjectName`). - - -[[jmx-naming-properties]] -==== Reading `ObjectName` Instances from Properties - -You can configure your own `KeyNamingStrategy` instance and configure it to read -`ObjectName` instances from a `Properties` instance rather than use a bean key. The -`KeyNamingStrategy` tries to locate an entry in the `Properties` with a key -that corresponds to the bean key. If no entry is found or if the `Properties` instance is -`null`, the bean key itself is used. - -The following code shows a sample configuration for the `KeyNamingStrategy`: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - - - - - - - bean:name=testBean1 - - - - names1.properties,names2.properties - - - - ----- - -The preceding example configures an instance of `KeyNamingStrategy` with a `Properties` instance that -is merged from the `Properties` instance defined by the mapping property and the -properties files located in the paths defined by the mappings property. In this -configuration, the `testBean` bean is given an `ObjectName` of `bean:name=testBean1`, -since this is the entry in the `Properties` instance that has a key corresponding to the -bean key. - -If no entry in the `Properties` instance can be found, the bean key name is used as -the `ObjectName`. - - -[[jmx-naming-metadata]] -==== Using `MetadataNamingStrategy` - -`MetadataNamingStrategy` uses the `objectName` property of the `ManagedResource` -attribute on each bean to create the `ObjectName`. The following code shows the -configuration for the `MetadataNamingStrategy`: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - - - - - - - - - - ----- - -If no `objectName` has been provided for the `ManagedResource` attribute, an -`ObjectName` is created with the following -format: _[fully-qualified-package-name]:type=[short-classname],name=[bean-name]_. For -example, the generated `ObjectName` for the following bean would be -`com.example:type=MyClass,name=myBean`: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - - -[[jmx-context-mbeanexport]] -==== Configuring Annotation-based MBean Export - -If you prefer to use <> to define -your management interfaces, a convenience subclass of `MBeanExporter` is available: -`AnnotationMBeanExporter`. When defining an instance of this subclass, you no longer need the -`namingStrategy`, `assembler`, and `attributeSource` configuration, -since it always uses standard Java annotation-based metadata (autodetection is -always enabled as well). In fact, rather than defining an `MBeanExporter` bean, an even -simpler syntax is supported by the `@EnableMBeanExport` `@Configuration` annotation, -as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Configuration - @EnableMBeanExport - public class AppConfig { - - } ----- - -If you prefer XML-based configuration, the `` element serves the -same purpose and is shown in the following listing: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - -If necessary, you can provide a reference to a particular MBean `server`, and the -`defaultDomain` attribute (a property of `AnnotationMBeanExporter`) accepts an alternate -value for the generated MBean `ObjectName` domains. This is used in place of the -fully qualified package name as described in the previous section on -<>, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain") - @Configuration - ContextConfiguration { - - } ----- - -The following example shows the XML equivalent of the preceding annotation-based example: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - -CAUTION: Do not use interface-based AOP proxies in combination with autodetection of JMX -annotations in your bean classes. Interface-based proxies "`hide`" the target class, which -also hides the JMX-managed resource annotations. Hence, you should use target-class proxies in that -case (through setting the 'proxy-target-class' flag on ``, -`` and so on). Otherwise, your JMX beans might be silently ignored at -startup. - - - -[[jmx-jsr160]] -=== Using JSR-160 Connectors - -For remote access, Spring JMX module offers two `FactoryBean` implementations inside the -`org.springframework.jmx.support` package for creating both server- and client-side -connectors. - - -[[jmx-jsr160-server]] -==== Server-side Connectors - -To have Spring JMX create, start, and expose a JSR-160 `JMXConnectorServer`, you can use the -following configuration: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - -By default, `ConnectorServerFactoryBean` creates a `JMXConnectorServer` bound to -`service:jmx:jmxmp://localhost:9875`. The `serverConnector` bean thus exposes the -local `MBeanServer` to clients through the JMXMP protocol on localhost, port 9875. Note -that the JMXMP protocol is marked as optional by the JSR 160 specification. Currently, -the main open-source JMX implementation, MX4J, and the one provided with the JDK -do not support JMXMP. - -To specify another URL and register the `JMXConnectorServer` itself with the -`MBeanServer`, you can use the `serviceUrl` and `ObjectName` properties, respectively, -as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - ----- - -If the `ObjectName` property is set, Spring automatically registers your connector -with the `MBeanServer` under that `ObjectName`. The following example shows the full set of -parameters that you can pass to the `ConnectorServerFactoryBean` when creating a -`JMXConnector`: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - ----- - -Note that, when you use a RMI-based connector, you need the lookup service (`tnameserv` or -`rmiregistry`) to be started in order for the name registration to complete. - - -[[jmx-jsr160-client]] -==== Client-side Connectors - -To create an `MBeanServerConnection` to a remote JSR-160-enabled `MBeanServer`, you can use the -`MBeanServerConnectionFactoryBean`, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - ----- - - -[[jmx-jsr160-protocols]] -==== JMX over Hessian or SOAP - -JSR-160 permits extensions to the way in which communication is done between the client -and the server. The examples shown in the preceding sections use the mandatory RMI-based implementation -required by the JSR-160 specification (IIOP and JRMP) and the (optional) JMXMP. By using -other providers or JMX implementations (such as http://mx4j.sourceforge.net[MX4J]) you -can take advantage of protocols such as SOAP or Hessian over simple HTTP or SSL and others, -as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - ----- - -In the preceding example, we used MX4J 3.0.0. See the official MX4J -documentation for more information. - - - -[[jmx-proxy]] -=== Accessing MBeans through Proxies - -Spring JMX lets you create proxies that re-route calls to MBeans that are registered in a -local or remote `MBeanServer`. These proxies provide you with a standard Java interface, -through which you can interact with your MBeans. The following code shows how to configure a -proxy for an MBean running in a local `MBeanServer`: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - ----- - -In the preceding example, you can see that a proxy is created for the MBean registered under the -`ObjectName` of `bean:name=testBean`. The set of interfaces that the proxy implements -is controlled by the `proxyInterfaces` property, and the rules for mapping methods and -properties on these interfaces to operations and attributes on the MBean are the same -rules used by the `InterfaceBasedMBeanInfoAssembler`. - -The `MBeanProxyFactoryBean` can create a proxy to any MBean that is accessible through an -`MBeanServerConnection`. By default, the local `MBeanServer` is located and used, but -you can override this and provide an `MBeanServerConnection` that points to a remote -`MBeanServer` to cater for proxies that point to remote MBeans: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - ----- - -In the preceding example, we create an `MBeanServerConnection` that points to a remote machine -that uses the `MBeanServerConnectionFactoryBean`. This `MBeanServerConnection` is then -passed to the `MBeanProxyFactoryBean` through the `server` property. The proxy that is -created forwards all invocations to the `MBeanServer` through this -`MBeanServerConnection`. - - - -[[jmx-notifications]] -=== Notifications - -Spring's JMX offering includes comprehensive support for JMX notifications. - - -[[jmx-notifications-listeners]] -==== Registering Listeners for Notifications - -Spring's JMX support makes it easy to register any number of -`NotificationListeners` with any number of MBeans (this includes MBeans exported by -Spring's `MBeanExporter` and MBeans registered through some other mechanism). For -example, consider the scenario where one would like to be informed (through a -`Notification`) each and every time an attribute of a target MBean changes. The following -example writes notifications to the console: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - package com.example; - - import javax.management.AttributeChangeNotification; - import javax.management.Notification; - import javax.management.NotificationFilter; - import javax.management.NotificationListener; - - public class ConsoleLoggingNotificationListener - implements NotificationListener, NotificationFilter { - - public void handleNotification(Notification notification, Object handback) { - System.out.println(notification); - System.out.println(handback); - } - - public boolean isNotificationEnabled(Notification notification) { - return AttributeChangeNotification.class.isAssignableFrom(notification.getClass()); - } - - } ----- - -The following example adds `ConsoleLoggingNotificationListener` (defined in the preceding -example) to `notificationListenerMappings`: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - - - - - - - - - - ----- - -With the preceding configuration in place, every time a JMX `Notification` is broadcast from -the target MBean (`bean:name=testBean1`), the `ConsoleLoggingNotificationListener` bean -that was registered as a listener through the `notificationListenerMappings` property is -notified. The `ConsoleLoggingNotificationListener` bean can then take whatever action -it deems appropriate in response to the `Notification`. - -You can also use straight bean names as the link between exported beans and listeners, -as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - - - - - - - - - - ----- - -If you want to register a single `NotificationListener` instance for all of the beans -that the enclosing `MBeanExporter` exports, you can use the special wildcard (`{asterisk}`) -as the key for an entry in the `notificationListenerMappings` property -map, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - ----- - -If you need to do the inverse (that is, register a number of distinct listeners against -an MBean), you must instead use the `notificationListeners` list property (in -preference to the `notificationListenerMappings` property). This time, instead of -configuring a `NotificationListener` for a single MBean, we configure -`NotificationListenerBean` instances. A `NotificationListenerBean` encapsulates a -`NotificationListener` and the `ObjectName` (or `ObjectNames`) that it is to be -registered against in an `MBeanServer`. The `NotificationListenerBean` also encapsulates -a number of other properties, such as a `NotificationFilter` and an arbitrary handback -object that can be used in advanced JMX notification scenarios. - -The configuration when using `NotificationListenerBean` instances is not wildly -different to what was presented previously, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - - - - bean:name=testBean1 - - - - - - - - - - - - - ----- - -The preceding example is equivalent to the first notification example. Assume, then, that -we want to be given a handback object every time a `Notification` is raised and that -we also want to filter out extraneous `Notifications` by supplying a -`NotificationFilter`. The following example accomplishes these goals: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - - - - bean:name=testBean1 - bean:name=testBean2 - - - - - - - - - - - - - - - - - - - - - - - - - - - ----- - -(For a full discussion of what a handback object is and, -indeed, what a `NotificationFilter` is, see the section of the JMX -specification (1.2) entitled 'The JMX Notification Model'.) - - -[[jmx-notifications-publishing]] -==== Publishing Notifications - -Spring provides support not only for registering to receive `Notifications` but also -for publishing `Notifications`. - -NOTE: This section is really only relevant to Spring-managed beans that have -been exposed as MBeans through an `MBeanExporter`. Any existing user-defined MBeans should -use the standard JMX APIs for notification publication. - -The key interface in Spring's JMX notification publication support is the -`NotificationPublisher` interface (defined in the -`org.springframework.jmx.export.notification` package). Any bean that is going to be -exported as an MBean through an `MBeanExporter` instance can implement the related -`NotificationPublisherAware` interface to gain access to a `NotificationPublisher` -instance. The `NotificationPublisherAware` interface supplies an instance of a -`NotificationPublisher` to the implementing bean through a simple setter method, -which the bean can then use to publish `Notifications`. - -As stated in the javadoc of the -{api-spring-framework}/jmx/export/notification/NotificationPublisher.html[`NotificationPublisher`] -interface, managed beans that publish events through the `NotificationPublisher` -mechanism are not responsible for the state management of notification listeners. -Spring's JMX support takes care of handling all the JMX infrastructure issues. -All you need to do, as an application developer, is implement the -`NotificationPublisherAware` interface and start publishing events by using the -supplied `NotificationPublisher` instance. Note that the `NotificationPublisher` -is set after the managed bean has been registered with an `MBeanServer`. - -Using a `NotificationPublisher` instance is quite straightforward. You create a JMX -`Notification` instance (or an instance of an appropriate `Notification` subclass), -populate the notification with the data pertinent to the event that is to be -published, and invoke the `sendNotification(Notification)` on the -`NotificationPublisher` instance, passing in the `Notification`. - -In the following example, exported instances of the `JmxTestBean` publish a -`NotificationEvent` every time the `add(int, int)` operation is invoked: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - package org.springframework.jmx; - - import org.springframework.jmx.export.notification.NotificationPublisherAware; - import org.springframework.jmx.export.notification.NotificationPublisher; - import javax.management.Notification; - - public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware { - - private String name; - private int age; - private boolean isSuperman; - private NotificationPublisher publisher; - - // other getters and setters omitted for clarity - - public int add(int x, int y) { - int answer = x + y; - this.publisher.sendNotification(new Notification("add", this, 0)); - return answer; - } - - public void dontExposeMe() { - throw new RuntimeException(); - } - - public void setNotificationPublisher(NotificationPublisher notificationPublisher) { - this.publisher = notificationPublisher; - } - - } ----- - -The `NotificationPublisher` interface and the machinery to get it all working is one of -the nicer features of Spring's JMX support. It does, however, come with the price tag of -coupling your classes to both Spring and JMX. As always, the advice here is to be -pragmatic. If you need the functionality offered by the `NotificationPublisher` and -you can accept the coupling to both Spring and JMX, then do so. - - - -[[jmx-resources]] -=== Further Resources - -This section contains links to further resources about JMX: - -* The https://www.oracle.com/technetwork/java/javase/tech/javamanagement-140525.html[JMX -homepage] at Oracle. -* The https://jcp.org/aboutJava/communityprocess/final/jsr003/index3.html[JMX - specification] (JSR-000003). -* The https://jcp.org/aboutJava/communityprocess/final/jsr160/index.html[JMX Remote API - specification] (JSR-000160). -* The http://mx4j.sourceforge.net/[MX4J homepage]. (MX4J is an open-source implementation of - various JMX specs.) - - - - -[[mail]] -== Email - -This section describes how to send email with the Spring Framework. - -.Library dependencies -**** -The following JAR needs to be on the classpath of your application in order to use -the Spring Framework's email library: - -* The https://eclipse-ee4j.github.io/mail/[JavaMail / Jakarta Mail 1.6] library - -This library is freely available on the web -- for example, in Maven Central as -`com.sun.mail:jakarta.mail`. Please make sure to use the latest 1.6.x version -rather than Jakarta Mail 2.0 (which comes with a different package namespace). -**** - -The Spring Framework provides a helpful utility library for sending email that shields -you from the specifics of the underlying mailing system and is responsible for -low-level resource handling on behalf of the client. - -The `org.springframework.mail` package is the root level package for the Spring -Framework's email support. The central interface for sending emails is the `MailSender` -interface. A simple value object that encapsulates the properties of a simple mail such -as `from` and `to` (plus many others) is the `SimpleMailMessage` class. This package -also contains a hierarchy of checked exceptions that provide a higher level of -abstraction over the lower level mail system exceptions, with the root exception being -`MailException`. See the {api-spring-framework}/mail/MailException.html[javadoc] -for more information on the rich mail exception hierarchy. - -The `org.springframework.mail.javamail.JavaMailSender` interface adds specialized -JavaMail features, such as MIME message support to the `MailSender` interface -(from which it inherits). `JavaMailSender` also provides a callback interface called -`org.springframework.mail.javamail.MimeMessagePreparator` for preparing a `MimeMessage`. - - - -[[mail-usage]] -=== Usage - -Assume that we have a business interface called `OrderManager`, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public interface OrderManager { - - void placeOrder(Order order); - - } ----- - -Further assume that we have a requirement stating that an email message with an -order number needs to be generated and sent to a customer who placed the relevant order. - - -[[mail-usage-simple]] -==== Basic `MailSender` and `SimpleMailMessage` Usage - -The following example shows how to use `MailSender` and `SimpleMailMessage` to send an -email when someone places an order: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - import org.springframework.mail.MailException; - import org.springframework.mail.MailSender; - import org.springframework.mail.SimpleMailMessage; - - public class SimpleOrderManager implements OrderManager { - - private MailSender mailSender; - private SimpleMailMessage templateMessage; - - public void setMailSender(MailSender mailSender) { - this.mailSender = mailSender; - } - - public void setTemplateMessage(SimpleMailMessage templateMessage) { - this.templateMessage = templateMessage; - } - - public void placeOrder(Order order) { - - // Do the business calculations... - - // Call the collaborators to persist the order... - - // Create a thread safe "copy" of the template message and customize it - SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage); - msg.setTo(order.getCustomer().getEmailAddress()); - msg.setText( - "Dear " + order.getCustomer().getFirstName() - + order.getCustomer().getLastName() - + ", thank you for placing order. Your order number is " - + order.getOrderNumber()); - try { - this.mailSender.send(msg); - } - catch (MailException ex) { - // simply log it and go on... - System.err.println(ex.getMessage()); - } - } - - } ----- - -The following example shows the bean definitions for the preceding code: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - ----- - - -[[mail-usage-mime]] -==== Using `JavaMailSender` and `MimeMessagePreparator` - -This section describes another implementation of `OrderManager` that uses the `MimeMessagePreparator` -callback interface. In the following example, the `mailSender` property is of type -`JavaMailSender` so that we are able to use the JavaMail `MimeMessage` class: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - import jakarta.mail.Message; - import jakarta.mail.MessagingException; - import jakarta.mail.internet.InternetAddress; - import jakarta.mail.internet.MimeMessage; - - import jakarta.mail.internet.MimeMessage; - import org.springframework.mail.MailException; - import org.springframework.mail.javamail.JavaMailSender; - import org.springframework.mail.javamail.MimeMessagePreparator; - - public class SimpleOrderManager implements OrderManager { - - private JavaMailSender mailSender; - - public void setMailSender(JavaMailSender mailSender) { - this.mailSender = mailSender; - } - - public void placeOrder(final Order order) { - // Do the business calculations... - // Call the collaborators to persist the order... - - MimeMessagePreparator preparator = new MimeMessagePreparator() { - public void prepare(MimeMessage mimeMessage) throws Exception { - mimeMessage.setRecipient(Message.RecipientType.TO, - new InternetAddress(order.getCustomer().getEmailAddress())); - mimeMessage.setFrom(new InternetAddress("mail@mycompany.example")); - mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " + - order.getCustomer().getLastName() + ", thanks for your order. " + - "Your order number is " + order.getOrderNumber() + "."); - } - }; - - try { - this.mailSender.send(preparator); - } - catch (MailException ex) { - // simply log it and go on... - System.err.println(ex.getMessage()); - } - } - - } ----- - -NOTE: The mail code is a crosscutting concern and could well be a candidate for -refactoring into a <>, which could then -be run at appropriate joinpoints on the `OrderManager` target. - -The Spring Framework's mail support ships with the standard JavaMail implementation. -See the relevant javadoc for more information. - - - -[[mail-javamail-mime]] -=== Using the JavaMail `MimeMessageHelper` - -A class that comes in pretty handy when dealing with JavaMail messages is -`org.springframework.mail.javamail.MimeMessageHelper`, which shields you from -having to use the verbose JavaMail API. Using the `MimeMessageHelper`, it is -pretty easy to create a `MimeMessage`, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - // of course you would use DI in any real-world cases - JavaMailSenderImpl sender = new JavaMailSenderImpl(); - sender.setHost("mail.host.com"); - - MimeMessage message = sender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(message); - helper.setTo("test@host.com"); - helper.setText("Thank you for ordering!"); - - sender.send(message); ----- - - -[[mail-javamail-mime-attachments]] -==== Sending Attachments and Inline Resources - -Multipart email messages allow for both attachments and inline resources. Examples of -inline resources include an image or a stylesheet that you want to use in your message but -that you do not want displayed as an attachment. - -[[mail-javamail-mime-attachments-attachment]] -===== Attachments - -The following example shows you how to use the `MimeMessageHelper` to send an email -with a single JPEG image attachment: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - JavaMailSenderImpl sender = new JavaMailSenderImpl(); - sender.setHost("mail.host.com"); - - MimeMessage message = sender.createMimeMessage(); - - // use the true flag to indicate you need a multipart message - MimeMessageHelper helper = new MimeMessageHelper(message, true); - helper.setTo("test@host.com"); - - helper.setText("Check out this image!"); - - // let's attach the infamous windows Sample file (this time copied to c:/) - FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg")); - helper.addAttachment("CoolImage.jpg", file); - - sender.send(message); ----- - -[[mail-javamail-mime-attachments-inline]] -===== Inline Resources - -The following example shows you how to use the `MimeMessageHelper` to send an email -with an inline image: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - JavaMailSenderImpl sender = new JavaMailSenderImpl(); - sender.setHost("mail.host.com"); - - MimeMessage message = sender.createMimeMessage(); - - // use the true flag to indicate you need a multipart message - MimeMessageHelper helper = new MimeMessageHelper(message, true); - helper.setTo("test@host.com"); - - // use the true flag to indicate the text included is HTML - helper.setText("", true); - - // let's include the infamous windows Sample file (this time copied to c:/) - FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg")); - helper.addInline("identifier1234", res); - - sender.send(message); ----- - -WARNING: Inline resources are added to the `MimeMessage` by using the specified `Content-ID` -(`identifier1234` in the above example). The order in which you add the text -and the resource are very important. Be sure to first add the text and then -the resources. If you are doing it the other way around, it does not work. - - -[[mail-templates]] -==== Creating Email Content by Using a Templating Library - -The code in the examples shown in the previous sections explicitly created the content of the email message, -by using methods calls such as `message.setText(..)`. This is fine for simple cases, and it -is okay in the context of the aforementioned examples, where the intent was to show you -the very basics of the API. - -In your typical enterprise application, though, developers often do not create the content -of email messages by using the previously shown approach for a number of reasons: - -* Creating HTML-based email content in Java code is tedious and error prone. -* There is no clear separation between display logic and business logic. -* Changing the display structure of the email content requires writing Java code, - recompiling, redeploying, and so on. - -Typically, the approach taken to address these issues is to use a template library (such -as FreeMarker) to define the display structure of email content. This leaves your code -tasked only with creating the data that is to be rendered in the email template and -sending the email. It is definitely a best practice when the content of your email messages -becomes even moderately complex, and, with the Spring Framework's support classes for -FreeMarker, it becomes quite easy to do. - - - - -[[scheduling]] -== Task Execution and Scheduling - -The Spring Framework provides abstractions for the asynchronous execution and scheduling of -tasks with the `TaskExecutor` and `TaskScheduler` interfaces, respectively. Spring also -features implementations of those interfaces that support thread pools or delegation to -CommonJ within an application server environment. Ultimately, the use of these -implementations behind the common interfaces abstracts away the differences between Java -SE 5, Java SE 6, and Jakarta EE environments. - -Spring also features integration classes to support scheduling with the `Timer` -(part of the JDK since 1.3) and the Quartz Scheduler ( https://www.quartz-scheduler.org/[]). -You can set up both of those schedulers by using a `FactoryBean` with optional references to -`Timer` or `Trigger` instances, respectively. Furthermore, a convenience class for both -the Quartz Scheduler and the `Timer` is available that lets you invoke a method of -an existing target object (analogous to the normal `MethodInvokingFactoryBean` -operation). - - - -[[scheduling-task-executor]] -=== The Spring `TaskExecutor` Abstraction - -Executors are the JDK name for the concept of thread pools. The "`executor`" naming is -due to the fact that there is no guarantee that the underlying implementation is -actually a pool. An executor may be single-threaded or even synchronous. Spring's -abstraction hides implementation details between the Java SE and Jakarta EE environments. - -Spring's `TaskExecutor` interface is identical to the `java.util.concurrent.Executor` -interface. In fact, originally, its primary reason for existence was to abstract away -the need for Java 5 when using thread pools. The interface has a single method -(`execute(Runnable task)`) that accepts a task for execution based on the semantics -and configuration of the thread pool. - -The `TaskExecutor` was originally created to give other Spring components an abstraction -for thread pooling where needed. Components such as the `ApplicationEventMulticaster`, -JMS's `AbstractMessageListenerContainer`, and Quartz integration all use the -`TaskExecutor` abstraction to pool threads. However, if your beans need thread pooling -behavior, you can also use this abstraction for your own needs. - - -[[scheduling-task-executor-types]] -==== `TaskExecutor` Types - -Spring includes a number of pre-built implementations of `TaskExecutor`. -In all likelihood, you should never need to implement your own. -The variants that Spring provides are as follows: - -* `SyncTaskExecutor`: - This implementation does not run invocations asynchronously. Instead, each - invocation takes place in the calling thread. It is primarily used in situations - where multi-threading is not necessary, such as in simple test cases. -* `SimpleAsyncTaskExecutor`: - This implementation does not reuse any threads. Rather, it starts up a new thread - for each invocation. However, it does support a concurrency limit that blocks - any invocations that are over the limit until a slot has been freed up. If you - are looking for true pooling, see `ThreadPoolTaskExecutor`, later in this list. -* `ConcurrentTaskExecutor`: - This implementation is an adapter for a `java.util.concurrent.Executor` instance. - There is an alternative (`ThreadPoolTaskExecutor`) that exposes the `Executor` - configuration parameters as bean properties. There is rarely a need to use - `ConcurrentTaskExecutor` directly. However, if the `ThreadPoolTaskExecutor` is not - flexible enough for your needs, `ConcurrentTaskExecutor` is an alternative. -* `ThreadPoolTaskExecutor`: - This implementation is most commonly used. It exposes bean properties for - configuring a `java.util.concurrent.ThreadPoolExecutor` and wraps it in a `TaskExecutor`. - If you need to adapt to a different kind of `java.util.concurrent.Executor`, we - recommend that you use a `ConcurrentTaskExecutor` instead. -* `DefaultManagedTaskExecutor`: - This implementation uses a JNDI-obtained `ManagedExecutorService` in a JSR-236 - compatible runtime environment (such as a Jakarta EE application server), - replacing a CommonJ WorkManager for that purpose. - - -[[scheduling-task-executor-usage]] -==== Using a `TaskExecutor` - -Spring's `TaskExecutor` implementations are used as simple JavaBeans. In the following example, -we define a bean that uses the `ThreadPoolTaskExecutor` to asynchronously print -out a set of messages: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - import org.springframework.core.task.TaskExecutor; - - public class TaskExecutorExample { - - private class MessagePrinterTask implements Runnable { - - private String message; - - public MessagePrinterTask(String message) { - this.message = message; - } - - public void run() { - System.out.println(message); - } - } - - private TaskExecutor taskExecutor; - - public TaskExecutorExample(TaskExecutor taskExecutor) { - this.taskExecutor = taskExecutor; - } - - public void printMessages() { - for(int i = 0; i < 25; i++) { - taskExecutor.execute(new MessagePrinterTask("Message" + i)); - } - } - } ----- - -As you can see, rather than retrieving a thread from the pool and executing it yourself, -you add your `Runnable` to the queue. Then the `TaskExecutor` uses its internal rules to -decide when the task gets run. - -To configure the rules that the `TaskExecutor` uses, we expose simple bean properties: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - ----- - - - -[[scheduling-task-scheduler]] -=== The Spring `TaskScheduler` Abstraction - -In addition to the `TaskExecutor` abstraction, Spring 3.0 introduced a `TaskScheduler` -with a variety of methods for scheduling tasks to run at some point in the future. -The following listing shows the `TaskScheduler` interface definition: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public interface TaskScheduler { - - ScheduledFuture schedule(Runnable task, Trigger trigger); - - ScheduledFuture schedule(Runnable task, Instant startTime); - - ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period); - - ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period); - - ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay); - - ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay); - ----- - -The simplest method is the one named `schedule` that takes only a `Runnable` and an `Instant`. -That causes the task to run once after the specified time. All of the other methods -are capable of scheduling tasks to run repeatedly. The fixed-rate and fixed-delay -methods are for simple, periodic execution, but the method that accepts a `Trigger` is -much more flexible. - - -[[scheduling-trigger-interface]] -==== `Trigger` Interface - -The `Trigger` interface is essentially inspired by JSR-236 which, as of Spring 3.0, -was not yet officially implemented. The basic idea of the `Trigger` is that execution -times may be determined based on past execution outcomes or even arbitrary conditions. -If these determinations do take into account the outcome of the preceding execution, -that information is available within a `TriggerContext`. The `Trigger` interface itself -is quite simple, as the following listing shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public interface Trigger { - - Date nextExecutionTime(TriggerContext triggerContext); - } ----- - -The `TriggerContext` is the most important part. It encapsulates all of -the relevant data and is open for extension in the future, if necessary. The -`TriggerContext` is an interface (a `SimpleTriggerContext` implementation is used by -default). The following listing shows the available methods for `Trigger` implementations. - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public interface TriggerContext { - - Date lastScheduledExecutionTime(); - - Date lastActualExecutionTime(); - - Date lastCompletionTime(); - } ----- - - -[[scheduling-trigger-implementations]] -==== `Trigger` Implementations - -Spring provides two implementations of the `Trigger` interface. The most interesting one -is the `CronTrigger`. It enables the scheduling of tasks based on -<>. -For example, the following task is scheduled to run 15 minutes past each hour but only -during the 9-to-5 "`business hours`" on weekdays: - -[source,java,indent=0] -[subs="verbatim"] ----- - scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI")); ----- - -The other implementation is a `PeriodicTrigger` that accepts a fixed -period, an optional initial delay value, and a boolean to indicate whether the period -should be interpreted as a fixed-rate or a fixed-delay. Since the `TaskScheduler` -interface already defines methods for scheduling tasks at a fixed rate or with a -fixed delay, those methods should be used directly whenever possible. The value of the -`PeriodicTrigger` implementation is that you can use it within components that rely on -the `Trigger` abstraction. For example, it may be convenient to allow periodic triggers, -cron-based triggers, and even custom trigger implementations to be used interchangeably. -Such a component could take advantage of dependency injection so that you can configure such `Triggers` -externally and, therefore, easily modify or extend them. - - -[[scheduling-task-scheduler-implementations]] -==== `TaskScheduler` implementations - -As with Spring's `TaskExecutor` abstraction, the primary benefit of the `TaskScheduler` -arrangement is that an application's scheduling needs are decoupled from the deployment -environment. This abstraction level is particularly relevant when deploying to an -application server environment where threads should not be created directly by the -application itself. For such scenarios, Spring provides a `TimerManagerTaskScheduler` -that delegates to a CommonJ `TimerManager` on WebLogic or WebSphere as well as a more recent -`DefaultManagedTaskScheduler` that delegates to a JSR-236 `ManagedScheduledExecutorService` -in a Jakarta EE environment. Both are typically configured with a JNDI lookup. - -Whenever external thread management is not a requirement, a simpler alternative is -a local `ScheduledExecutorService` setup within the application, which can be adapted -through Spring's `ConcurrentTaskScheduler`. As a convenience, Spring also provides a -`ThreadPoolTaskScheduler`, which internally delegates to a `ScheduledExecutorService` -to provide common bean-style configuration along the lines of `ThreadPoolTaskExecutor`. -These variants work perfectly fine for locally embedded thread pool setups in lenient -application server environments, as well -- in particular on Tomcat and Jetty. - - - -[[scheduling-annotation-support]] -=== Annotation Support for Scheduling and Asynchronous Execution - -Spring provides annotation support for both task scheduling and asynchronous method -execution. - - -[[scheduling-enable-annotation-support]] -==== Enable Scheduling Annotations - -To enable support for `@Scheduled` and `@Async` annotations, you can add `@EnableScheduling` and -`@EnableAsync` to one of your `@Configuration` classes, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Configuration - @EnableAsync - @EnableScheduling - public class AppConfig { - } ----- - -You can pick and choose the relevant annotations for your application. For example, -if you need only support for `@Scheduled`, you can omit `@EnableAsync`. For more -fine-grained control, you can additionally implement the `SchedulingConfigurer` -interface, the `AsyncConfigurer` interface, or both. See the -{api-spring-framework}/scheduling/annotation/SchedulingConfigurer.html[`SchedulingConfigurer`] -and {api-spring-framework}/scheduling/annotation/AsyncConfigurer.html[`AsyncConfigurer`] -javadoc for full details. - -If you prefer XML configuration, you can use the `` element, -as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - ----- - -Note that, with the preceding XML, an executor reference is provided for handling those -tasks that correspond to methods with the `@Async` annotation, and the scheduler -reference is provided for managing those methods annotated with `@Scheduled`. - -NOTE: The default advice mode for processing `@Async` annotations is `proxy` which allows -for interception of calls through the proxy only. Local calls within the same class -cannot get intercepted that way. For a more advanced mode of interception, consider -switching to `aspectj` mode in combination with compile-time or load-time weaving. - - -[[scheduling-annotation-support-scheduled]] -==== The `@Scheduled` annotation - -You can add the `@Scheduled` annotation to a method, along with trigger metadata. For -example, the following method is invoked every five seconds (5000 milliseconds) with a -fixed delay, meaning that the period is measured from the completion time of each -preceding invocation. - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Scheduled(fixedDelay = 5000) - public void doSomething() { - // something that should run periodically - } ----- - -[NOTE] -==== -By default, milliseconds will be used as the time unit for fixed delay, fixed rate, and -initial delay values. If you would like to use a different time unit such as seconds or -minutes, you can configure this via the `timeUnit` attribute in `@Scheduled`. - -For example, the previous example can also be written as follows. - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) - public void doSomething() { - // something that should run periodically - } ----- -==== - -If you need a fixed-rate execution, you can use the `fixedRate` attribute within the -annotation. The following method is invoked every five seconds (measured between the -successive start times of each invocation). - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS) - public void doSomething() { - // something that should run periodically - } ----- - -For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the -amount of time to wait before the first execution of the method, as the following -`fixedRate` example shows. - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Scheduled(initialDelay = 1000, fixedRate = 5000) - public void doSomething() { - // something that should run periodically - } ----- - -If simple periodic scheduling is not expressive enough, you can provide a -<>. -The following example runs only on weekdays: - -[source,java,indent=0] -[subs="verbatim"] ----- - @Scheduled(cron="*/5 * * * * MON-FRI") - public void doSomething() { - // something that should run on weekdays only - } ----- - -TIP: You can also use the `zone` attribute to specify the time zone in which the cron -expression is resolved. - -Notice that the methods to be scheduled must have void returns and must not accept any -arguments. If the method needs to interact with other objects from the application -context, those would typically have been provided through dependency injection. - -[NOTE] -==== -As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope. - -Make sure that you are not initializing multiple instances of the same `@Scheduled` -annotation class at runtime, unless you do want to schedule callbacks to each such -instance. Related to this, make sure that you do not use `@Configurable` on bean -classes that are annotated with `@Scheduled` and registered as regular Spring beans -with the container. Otherwise, you would get double initialization (once through the -container and once through the `@Configurable` aspect), with the consequence of each -`@Scheduled` method being invoked twice. -==== - - -[[scheduling-annotation-support-async]] -==== The `@Async` annotation - -You can provide the `@Async` annotation on a method so that invocation of that method -occurs asynchronously. In other words, the caller returns immediately upon -invocation, while the actual execution of the method occurs in a task that has been -submitted to a Spring `TaskExecutor`. In the simplest case, you can apply the annotation -to a method that returns `void`, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Async - void doSomething() { - // this will be run asynchronously - } ----- - -Unlike the methods annotated with the `@Scheduled` annotation, these methods can expect -arguments, because they are invoked in the "`normal`" way by callers at runtime rather -than from a scheduled task being managed by the container. For example, the following code is -a legitimate application of the `@Async` annotation: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Async - void doSomething(String s) { - // this will be run asynchronously - } ----- - -Even methods that return a value can be invoked asynchronously. However, such methods -are required to have a `Future`-typed return value. This still provides the benefit of -asynchronous execution so that the caller can perform other tasks prior to calling -`get()` on that `Future`. The following example shows how to use `@Async` on a method -that returns a value: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Async - Future returnSomething(int i) { - // this will be run asynchronously - } ----- - -TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return type -but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of Spring -4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with the -asynchronous task and for immediate composition with further processing steps. - -You can not use `@Async` in conjunction with lifecycle callbacks such as -`@PostConstruct`. To asynchronously initialize Spring beans, you currently have to use -a separate initializing Spring bean that then invokes the `@Async` annotated method on the -target, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public class SampleBeanImpl implements SampleBean { - - @Async - void doSomething() { - // ... - } - - } - - public class SampleBeanInitializer { - - private final SampleBean bean; - - public SampleBeanInitializer(SampleBean bean) { - this.bean = bean; - } - - @PostConstruct - public void initialize() { - bean.doSomething(); - } - - } ----- - -NOTE: There is no direct XML equivalent for `@Async`, since such methods should be designed -for asynchronous execution in the first place, not externally re-declared to be asynchronous. -However, you can manually set up Spring's `AsyncExecutionInterceptor` with Spring AOP, -in combination with a custom pointcut. - - -[[scheduling-annotation-support-qualification]] -==== Executor Qualification with `@Async` - -By default, when specifying `@Async` on a method, the executor that is used is the -one <>, -i.e. the "`annotation-driven`" element if you are using XML or your `AsyncConfigurer` -implementation, if any. However, you can use the `value` attribute of the `@Async` -annotation when you need to indicate that an executor other than the default should be -used when executing a given method. The following example shows how to do so: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Async("otherExecutor") - void doSomething(String s) { - // this will be run asynchronously by "otherExecutor" - } ----- - -In this case, `"otherExecutor"` can be the name of any `Executor` bean in the Spring -container, or it may be the name of a qualifier associated with any `Executor` (for example, as -specified with the `` element or Spring's `@Qualifier` annotation). - - -[[scheduling-annotation-support-exception]] -==== Exception Management with `@Async` - -When an `@Async` method has a `Future`-typed return value, it is easy to manage -an exception that was thrown during the method execution, as this exception is -thrown when calling `get` on the `Future` result. With a `void` return type, -however, the exception is uncaught and cannot be transmitted. You can provide an -`AsyncUncaughtExceptionHandler` to handle such exceptions. The following example shows -how to do so: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { - - @Override - public void handleUncaughtException(Throwable ex, Method method, Object... params) { - // handle exception - } - } ----- - -By default, the exception is merely logged. You can define a custom `AsyncUncaughtExceptionHandler` -by using `AsyncConfigurer` or the `` XML element. - - - -[[scheduling-task-namespace]] -=== The `task` Namespace - -As of version 3.0, Spring includes an XML namespace for configuring `TaskExecutor` and -`TaskScheduler` instances. It also provides a convenient way to configure tasks to be -scheduled with a trigger. - - -[[scheduling-task-namespace-scheduler]] -==== The 'scheduler' Element - -The following element creates a `ThreadPoolTaskScheduler` instance with the -specified thread pool size: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - -The value provided for the `id` attribute is used as the prefix for thread names -within the pool. The `scheduler` element is relatively straightforward. If you do not -provide a `pool-size` attribute, the default thread pool has only a single thread. -There are no other configuration options for the scheduler. - - -[[scheduling-task-namespace-executor]] -==== The `executor` Element - -The following creates a `ThreadPoolTaskExecutor` instance: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - -As with the scheduler shown in the <>, -the value provided for the `id` attribute is used as the prefix for thread names within -the pool. As far as the pool size is concerned, the `executor` element supports more -configuration options than the `scheduler` element. For one thing, the thread pool for -a `ThreadPoolTaskExecutor` is itself more configurable. Rather than only a single size, -an executor's thread pool can have different values for the core and the max size. -If you provide a single value, the executor has a fixed-size thread pool (the core and -max sizes are the same). However, the `executor` element's `pool-size` attribute also -accepts a range in the form of `min-max`. The following example sets a minimum value of -`5` and a maximum value of `25`: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - -In the preceding configuration, a `queue-capacity` value has also been provided. -The configuration of the thread pool should also be considered in light of the -executor's queue capacity. For the full description of the relationship between pool -size and queue capacity, see the documentation for -https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.html[`ThreadPoolExecutor`]. -The main idea is that, when a task is submitted, the executor first tries to use a -free thread if the number of active threads is currently less than the core size. -If the core size has been reached, the task is added to the queue, as long as its -capacity has not yet been reached. Only then, if the queue's capacity has been -reached, does the executor create a new thread beyond the core size. If the max size -has also been reached, then the executor rejects the task. - -By default, the queue is unbounded, but this is rarely the desired configuration, -because it can lead to `OutOfMemoryErrors` if enough tasks are added to that queue while -all pool threads are busy. Furthermore, if the queue is unbounded, the max size has -no effect at all. Since the executor always tries the queue before creating a new -thread beyond the core size, a queue must have a finite capacity for the thread pool to -grow beyond the core size (this is why a fixed-size pool is the only sensible case -when using an unbounded queue). - -Consider the case, as mentioned above, when a task is rejected. By default, when a -task is rejected, a thread pool executor throws a `TaskRejectedException`. However, -the rejection policy is actually configurable. The exception is thrown when using -the default rejection policy, which is the `AbortPolicy` implementation. -For applications where some tasks can be skipped under heavy load, you can instead -configure either `DiscardPolicy` or `DiscardOldestPolicy`. Another option that works -well for applications that need to throttle the submitted tasks under heavy load is -the `CallerRunsPolicy`. Instead of throwing an exception or discarding tasks, -that policy forces the thread that is calling the submit method to run the task itself. -The idea is that such a caller is busy while running that task and not able to submit -other tasks immediately. Therefore, it provides a simple way to throttle the incoming -load while maintaining the limits of the thread pool and queue. Typically, this allows -the executor to "`catch up`" on the tasks it is handling and thereby frees up some -capacity on the queue, in the pool, or both. You can choose any of these options from an -enumeration of values available for the `rejection-policy` attribute on the `executor` -element. - -The following example shows an `executor` element with a number of attributes to specify -various behaviors: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - -Finally, the `keep-alive` setting determines the time limit (in seconds) for which threads -may remain idle before being stopped. If there are more than the core number of threads -currently in the pool, after waiting this amount of time without processing a task, excess -threads get stopped. A time value of zero causes excess threads to stop -immediately after executing a task without remaining follow-up work in the task queue. -The following example sets the `keep-alive` value to two minutes: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - - -[[scheduling-task-namespace-scheduled-tasks]] -==== The 'scheduled-tasks' Element - -The most powerful feature of Spring's task namespace is the support for configuring -tasks to be scheduled within a Spring Application Context. This follows an approach -similar to other "`method-invokers`" in Spring, such as that provided by the JMS namespace -for configuring message-driven POJOs. Basically, a `ref` attribute can point to any -Spring-managed object, and the `method` attribute provides the name of a method to be -invoked on that object. The following listing shows a simple example: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - ----- - -The scheduler is referenced by the outer element, and each individual -task includes the configuration of its trigger metadata. In the preceding example, that -metadata defines a periodic trigger with a fixed delay indicating the number of -milliseconds to wait after each task execution has completed. Another option is -`fixed-rate`, indicating how often the method should be run regardless of how long -any previous execution takes. Additionally, for both `fixed-delay` and `fixed-rate` tasks, you can specify an -'initial-delay' parameter, indicating the number of milliseconds to wait -before the first execution of the method. For more control, you can instead provide a `cron` attribute -to provide a <>. -The following example shows these other options: - -[source,xml,indent=0] -[subs="verbatim"] ----- - - - - - - - ----- - - - -[[scheduling-cron-expression]] -=== Cron Expressions - -All Spring cron expressions have to conform to the same format, whether you are using them in -<>, -<>, -or someplace else. -A well-formed cron expression, such as `* * * * * *`, consists of six space-separated time and date -fields, each with its own range of valid values: - - -.... - ┌───────────── second (0-59) - │ ┌───────────── minute (0 - 59) - │ │ ┌───────────── hour (0 - 23) - │ │ │ ┌───────────── day of the month (1 - 31) - │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC) - │ │ │ │ │ ┌───────────── day of the week (0 - 7) - │ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN) - │ │ │ │ │ │ - * * * * * * -.... - -There are some rules that apply: - -* A field may be an asterisk (`*`), which always stands for "`first-last`". -For the day-of-the-month or day-of-the-week fields, a question mark (`?`) may be used instead of an -asterisk. -* Commas (`,`) are used to separate items of a list. -* Two numbers separated with a hyphen (`-`) express a range of numbers. -The specified range is inclusive. -* Following a range (or `*`) with `/` specifies the interval of the number's value through the range. -* English names can also be used for the day-of-month and day-of-week fields. -Use the first three letters of the particular day or month (case does not matter). -* The day-of-month and day-of-week fields can contain a `L` character, which has a different meaning -** In the day-of-month field, `L` stands for _the last day of the month_. -If followed by a negative offset (that is, `L-n`), it means _``n``th-to-last day of the month_. -** In the day-of-week field, `L` stands for _the last day of the week_. -If prefixed by a number or three-letter name (`dL` or `DDDL`), it means _the last day of week (`d` -or `DDD`) in the month_. -* The day-of-month field can be `nW`, which stands for _the nearest weekday to day of the month ``n``_. -If `n` falls on Saturday, this yields the Friday before it. -If `n` falls on Sunday, this yields the Monday after, which also happens if `n` is `1` and falls on -a Saturday (that is: `1W` stands for _the first weekday of the month_). -* If the day-of-month field is `LW`, it means _the last weekday of the month_. -* The day-of-week field can be `d#n` (or `DDD#n`), which stands for _the ``n``th day of week `d` -(or ``DDD``) in the month_. - -Here are some examples: - -|=== -| Cron Expression | Meaning - -|`0 0 * * * *` | top of every hour of every day -|`*/10 * * * * *` | every ten seconds -| `0 0 8-10 * * *` | 8, 9 and 10 o'clock of every day -| `0 0 6,19 * * *` | 6:00 AM and 7:00 PM every day -| `0 0/30 8-10 * * *` | 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day -| `0 0 9-17 * * MON-FRI`| on the hour nine-to-five weekdays -| `0 0 0 25 DEC ?` | every Christmas Day at midnight -| `0 0 0 L * *` | last day of the month at midnight -| `0 0 0 L-3 * *` | third-to-last day of the month at midnight -| `0 0 0 * * 5L` | last Friday of the month at midnight -| `0 0 0 * * THUL` | last Thursday of the month at midnight -| `0 0 0 1W * *` | first weekday of the month at midnight -| `0 0 0 LW * *` | last weekday of the month at midnight -| `0 0 0 ? * 5#2` | the second Friday in the month at midnight -| `0 0 0 ? * MON#1` | the first Monday in the month at midnight -|=== - -==== Macros - -Expressions such as `0 0 * * * *` are hard for humans to parse and are, therefore, hard to fix in case of bugs. -To improve readability, Spring supports the following macros, which represent commonly used sequences. -You can use these macros instead of the six-digit value, thus: `@Scheduled(cron = "@hourly")`. - -|=== -|Macro | Meaning - -| `@yearly` (or `@annually`) | once a year (`0 0 0 1 1 *`) -| `@monthly` | once a month (`0 0 0 1 * *`) -| `@weekly` | once a week (`0 0 0 * * 0`) -| `@daily` (or `@midnight`) | once a day (`0 0 0 * * *`), or -| `@hourly` | once an hour, (`0 0 * * * *`) -|=== - - - -[[scheduling-quartz]] -=== Using the Quartz Scheduler - -Quartz uses `Trigger`, `Job`, and `JobDetail` objects to realize scheduling of all kinds -of jobs. For the basic concepts behind Quartz, see -https://www.quartz-scheduler.org/[]. For convenience purposes, Spring offers a couple of -classes that simplify using Quartz within Spring-based applications. - - -[[scheduling-quartz-jobdetail]] -==== Using the `JobDetailFactoryBean` - -Quartz `JobDetail` objects contain all the information needed to run a job. Spring provides a -`JobDetailFactoryBean`, which provides bean-style properties for XML configuration purposes. -Consider the following example: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - ----- - -The job detail configuration has all the information it needs to run the job (`ExampleJob`). -The timeout is specified in the job data map. The job data map is available through the -`JobExecutionContext` (passed to you at execution time), but the `JobDetail` also gets -its properties from the job data mapped to properties of the job instance. So, in the following example, -the `ExampleJob` contains a bean property named `timeout`, and the `JobDetail` -has it applied automatically: - -[source,java,indent=0] -[subs="verbatim"] ----- - package example; - - public class ExampleJob extends QuartzJobBean { - - private int timeout; - - /** - * Setter called after the ExampleJob is instantiated - * with the value from the JobDetailFactoryBean (5) - */ - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException { - // do the actual work - } - } ----- - -All additional properties from the job data map are available to you as well. - -NOTE: By using the `name` and `group` properties, you can modify the name and the group -of the job, respectively. By default, the name of the job matches the bean name -of the `JobDetailFactoryBean` (`exampleJob` in the preceding example above). - - -[[scheduling-quartz-method-invoking-job]] -==== Using the `MethodInvokingJobDetailFactoryBean` - -Often you merely need to invoke a method on a specific object. By using the -`MethodInvokingJobDetailFactoryBean`, you can do exactly this, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - ----- - -The preceding example results in the `doIt` method being called on the -`exampleBusinessObject` method, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - public class ExampleBusinessObject { - - // properties and collaborators - - public void doIt() { - // do the actual work - } - } ----- - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - -By using the `MethodInvokingJobDetailFactoryBean`, you need not create one-line jobs -that merely invoke a method. You need only create the actual business object and -wire up the detail object. - -By default, Quartz Jobs are stateless, resulting in the possibility of jobs interfering -with each other. If you specify two triggers for the same `JobDetail`, it is -possible that, before the first job has finished, the second one starts. If -`JobDetail` classes implement the `Stateful` interface, this does not happen. The second -job does not start before the first one has finished. To make jobs resulting from the -`MethodInvokingJobDetailFactoryBean` be non-concurrent, set the `concurrent` flag to -`false`, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - ----- - -NOTE: By default, jobs will run in a concurrent fashion. - - -[[scheduling-quartz-cron]] -==== Wiring up Jobs by Using Triggers and `SchedulerFactoryBean` - -We have created job details and jobs. We have also reviewed the convenience bean that lets -you invoke a method on a specific object. Of course, we still need to schedule the -jobs themselves. This is done by using triggers and a `SchedulerFactoryBean`. Several -triggers are available within Quartz, and Spring offers two Quartz `FactoryBean` -implementations with convenient defaults: `CronTriggerFactoryBean` and -`SimpleTriggerFactoryBean`. - -Triggers need to be scheduled. Spring offers a `SchedulerFactoryBean` that exposes -triggers to be set as properties. `SchedulerFactoryBean` schedules the actual jobs with -those triggers. - -The following listing uses both a `SimpleTriggerFactoryBean` and a `CronTriggerFactoryBean`: - -[source,xml,indent=0] -[subs="verbatim"] ----- - - - - - - - - - - - - - - ----- - -The preceding example sets up two triggers, one running every 50 seconds with a starting delay of 10 -seconds and one running every morning at 6 AM. To finalize everything, we need to set up the -`SchedulerFactoryBean`, as the following example shows: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - ----- - -More properties are available for the `SchedulerFactoryBean`, such as the calendars used by the -job details, properties to customize Quartz with, and a Spring-provided JDBC DataSource. See -the {api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`] -javadoc for more information. - -NOTE: `SchedulerFactoryBean` also recognizes a `quartz.properties` file in the classpath, -based on Quartz property keys, as with regular Quartz configuration. Please note that many -`SchedulerFactoryBean` settings interact with common Quartz settings in the properties file; -it is therefore not recommended to specify values at both levels. For example, do not set -an "org.quartz.jobStore.class" property if you mean to rely on a Spring-provided DataSource, -or specify an `org.springframework.scheduling.quartz.LocalDataSourceJobStore` variant which -is a full-fledged replacement for the standard `org.quartz.impl.jdbcjobstore.JobStoreTX`. - - - - -[[cache]] -== Cache Abstraction - -Since version 3.1, the Spring Framework provides support for transparently adding caching to -an existing Spring application. Similar to the <> -support, the caching abstraction allows consistent use of various caching solutions with -minimal impact on the code. - -In Spring Framework 4.1, the cache abstraction was significantly extended with support -for <> and more customization options. - - - -[[cache-strategies]] -=== Understanding the Cache Abstraction - -.Cache vs Buffer -**** - -The terms, "`buffer`" and "`cache,`" tend to be used interchangeably. Note, however, -that they represent different things. Traditionally, a buffer is used as an intermediate -temporary store for data between a fast and a slow entity. As one party would have to wait -for the other (which affects performance), the buffer alleviates this by allowing entire -blocks of data to move at once rather than in small chunks. The data is written and read -only once from the buffer. Furthermore, the buffers are visible to at least one party -that is aware of it. - -A cache, on the other hand, is, by definition, hidden, and neither party is aware that -caching occurs. It also improves performance but does so by letting the same data be -read multiple times in a fast fashion. - -You can find a further explanation of the differences between a buffer and a cache -https://en.wikipedia.org/wiki/Cache_(computing)#The_difference_between_buffer_and_cache[here]. -**** - -At its core, the cache abstraction applies caching to Java methods, thus reducing the -number of executions based on the information available in the cache. That is, each time -a targeted method is invoked, the abstraction applies a caching behavior that checks -whether the method has been already invoked for the given arguments. If it has been -invoked, the cached result is returned without having to invoke the actual method. -If the method has not been invoked, then it is invoked, and the result is cached and -returned to the user so that, the next time the method is invoked, the cached result is -returned. This way, expensive methods (whether CPU- or IO-bound) can be invoked only -once for a given set of parameters and the result reused without having to actually -invoke the method again. The caching logic is applied transparently without any -interference to the invoker. - -IMPORTANT: This approach works only for methods that are guaranteed to return the same -output (result) for a given input (or arguments) no matter how many times they are invoked. - -The caching abstraction provides other cache-related operations, such as the ability -to update the content of the cache or to remove one or all entries. These are useful if -the cache deals with data that can change during the course of the application. - -As with other services in the Spring Framework, the caching service is an abstraction -(not a cache implementation) and requires the use of actual storage to store the cache data -- -that is, the abstraction frees you from having to write the caching logic but does not -provide the actual data store. This abstraction is materialized by the -`org.springframework.cache.Cache` and `org.springframework.cache.CacheManager` interfaces. - -Spring provides <> of that abstraction: -JDK `java.util.concurrent.ConcurrentMap` based caches, Gemfire cache, -https://github.com/ben-manes/caffeine/wiki[Caffeine], and JSR-107 compliant caches (such -as Ehcache 3.x). See <> for more information on plugging in other cache -stores and providers. - -IMPORTANT: The caching abstraction has no special handling for multi-threaded and -multi-process environments, as such features are handled by the cache implementation. - -If you have a multi-process environment (that is, an application deployed on several nodes), -you need to configure your cache provider accordingly. Depending on your use cases, a copy -of the same data on several nodes can be enough. However, if you change the data during -the course of the application, you may need to enable other propagation mechanisms. - -Caching a particular item is a direct equivalent of the typical -get-if-not-found-then-proceed-and-put-eventually code blocks -found with programmatic cache interaction. -No locks are applied, and several threads may try to load the same item concurrently. -The same applies to eviction. If several threads are trying to update or evict data -concurrently, you may use stale data. Certain cache providers offer advanced features -in that area. See the documentation of your cache provider for more details. - -To use the cache abstraction, you need to take care of two aspects: - -* Caching declaration: Identify the methods that need to be cached and their policies. -* Cache configuration: The backing cache where the data is stored and from which it is read. - - - -[[cache-annotations]] -=== Declarative Annotation-based Caching - -For caching declaration, Spring's caching abstraction provides a set of Java annotations: - -* `@Cacheable`: Triggers cache population. -* `@CacheEvict`: Triggers cache eviction. -* `@CachePut`: Updates the cache without interfering with the method execution. -* `@Caching`: Regroups multiple cache operations to be applied on a method. -* `@CacheConfig`: Shares some common cache-related settings at class-level. - - -[[cache-annotations-cacheable]] -==== The `@Cacheable` Annotation - -As the name implies, you can use `@Cacheable` to demarcate methods that are cacheable -- -that is, methods for which the result is stored in the cache so that, on subsequent -invocations (with the same arguments), the value in the cache is returned without -having to actually invoke the method. In its simplest form, the annotation declaration -requires the name of the cache associated with the annotated method, as the following -example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable("books") - public Book findBook(ISBN isbn) {...} ----- - -In the preceding snippet, the `findBook` method is associated with the cache named `books`. -Each time the method is called, the cache is checked to see whether the invocation has -already been run and does not have to be repeated. While in most cases, only one -cache is declared, the annotation lets multiple names be specified so that more than one -cache is being used. In this case, each of the caches is checked before invoking the -method -- if at least one cache is hit, the associated value is returned. - -NOTE: All the other caches that do not contain the value are also updated, even though -the cached method was not actually invoked. - -The following example uses `@Cacheable` on the `findBook` method with multiple caches: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable({"books", "isbns"}) - public Book findBook(ISBN isbn) {...} ----- - -[[cache-annotations-cacheable-default-key]] -===== Default Key Generation - -Since caches are essentially key-value stores, each invocation of a cached method -needs to be translated into a suitable key for cache access. The caching abstraction -uses a simple `KeyGenerator` based on the following algorithm: - -* If no params are given, return `SimpleKey.EMPTY`. -* If only one param is given, return that instance. -* If more than one param is given, return a `SimpleKey` that contains all parameters. - -This approach works well for most use-cases, as long as parameters have natural keys -and implement valid `hashCode()` and `equals()` methods. If that is not the case, -you need to change the strategy. - -To provide a different default key generator, you need to implement the -`org.springframework.cache.interceptor.KeyGenerator` interface. - -[NOTE] -==== -The default key generation strategy changed with the release of Spring 4.0. Earlier -versions of Spring used a key generation strategy that, for multiple key parameters, -considered only the `hashCode()` of parameters and not `equals()`. This could cause -unexpected key collisions (see https://jira.spring.io/browse/SPR-10237[SPR-10237] -for background). The new `SimpleKeyGenerator` uses a compound key for such scenarios. - -If you want to keep using the previous key strategy, you can configure the deprecated -`org.springframework.cache.interceptor.DefaultKeyGenerator` class or create a custom -hash-based `KeyGenerator` implementation. -==== - -[[cache-annotations-cacheable-key]] -===== Custom Key Generation Declaration - -Since caching is generic, the target methods are quite likely to have various signatures -that cannot be readily mapped on top of the cache structure. This tends to become obvious -when the target method has multiple arguments out of which only some are suitable for -caching (while the rest are used only by the method logic). Consider the following example: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable("books") - public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) ----- - -At first glance, while the two `boolean` arguments influence the way the book is found, -they are no use for the cache. Furthermore, what if only one of the two is important -while the other is not? - -For such cases, the `@Cacheable` annotation lets you specify how the key is generated -through its `key` attribute. You can use <> to pick the -arguments of interest (or their nested properties), perform operations, or even -invoke arbitrary methods without having to write any code or implement any interface. -This is the recommended approach over the -<>, since methods tend to be -quite different in signatures as the code base grows. While the default strategy might -work for some methods, it rarely works for all methods. - -The following examples use various SpEL declarations (if you are not familiar with SpEL, -do yourself a favor and read <>): - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable(cacheNames="books", key="#isbn") - public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) - - @Cacheable(cacheNames="books", key="#isbn.rawNumber") - public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) - - @Cacheable(cacheNames="books", key="T(someType).hash(#isbn)") - public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) ----- - -The preceding snippets show how easy it is to select a certain argument, one of its -properties, or even an arbitrary (static) method. - -If the algorithm responsible for generating the key is too specific or if it needs -to be shared, you can define a custom `keyGenerator` on the operation. To do so, -specify the name of the `KeyGenerator` bean implementation to use, as the following -example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable(cacheNames="books", keyGenerator="myKeyGenerator") - public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) ----- - -NOTE: The `key` and `keyGenerator` parameters are mutually exclusive and an operation -that specifies both results in an exception. - -[[cache-annotations-cacheable-default-cache-resolver]] -===== Default Cache Resolution - -The caching abstraction uses a simple `CacheResolver` that -retrieves the caches defined at the operation level by using the configured -`CacheManager`. - -To provide a different default cache resolver, you need to implement the -`org.springframework.cache.interceptor.CacheResolver` interface. - -[[cache-annotations-cacheable-cache-resolver]] -===== Custom Cache Resolution - -The default cache resolution fits well for applications that work with a -single `CacheManager` and have no complex cache resolution requirements. - -For applications that work with several cache managers, you can set the -`cacheManager` to use for each operation, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable(cacheNames="books", cacheManager="anotherCacheManager") <1> - public Book findBook(ISBN isbn) {...} ----- -<1> Specifying `anotherCacheManager`. - - -You can also replace the `CacheResolver` entirely in a fashion similar to that of -replacing <>. The resolution is -requested for every cache operation, letting the implementation actually resolve -the caches to use based on runtime arguments. The following example shows how to -specify a `CacheResolver`: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable(cacheResolver="runtimeCacheResolver") <1> - public Book findBook(ISBN isbn) {...} ----- -<1> Specifying the `CacheResolver`. - - -[NOTE] -==== -Since Spring 4.1, the `value` attribute of the cache annotations are no longer -mandatory, since this particular information can be provided by the `CacheResolver` -regardless of the content of the annotation. - -Similarly to `key` and `keyGenerator`, the `cacheManager` and `cacheResolver` -parameters are mutually exclusive, and an operation specifying both -results in an exception, as a custom `CacheManager` is ignored by the -`CacheResolver` implementation. This is probably not what you expect. -==== - -[[cache-annotations-cacheable-synchronized]] -===== Synchronized Caching - -In a multi-threaded environment, certain operations might be concurrently invoked for -the same argument (typically on startup). By default, the cache abstraction does not -lock anything, and the same value may be computed several times, defeating the purpose -of caching. - -For those particular cases, you can use the `sync` attribute to instruct the underlying -cache provider to lock the cache entry while the value is being computed. As a result, -only one thread is busy computing the value, while the others are blocked until the entry -is updated in the cache. The following example shows how to use the `sync` attribute: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable(cacheNames="foos", sync=true) <1> - public Foo executeExpensiveOperation(String id) {...} ----- -<1> Using the `sync` attribute. - -NOTE: This is an optional feature, and your favorite cache library may not support it. -All `CacheManager` implementations provided by the core framework support it. See the -documentation of your cache provider for more details. - -[[cache-annotations-cacheable-condition]] -===== Conditional Caching - -Sometimes, a method might not be suitable for caching all the time (for example, it might -depend on the given arguments). The cache annotations support such use cases through the -`condition` parameter, which takes a `SpEL` expression that is evaluated to either `true` -or `false`. If `true`, the method is cached. If not, it behaves as if the method is not -cached (that is, the method is invoked every time no matter what values are in the cache -or what arguments are used). For example, the following method is cached only if the -argument `name` has a length shorter than 32: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable(cacheNames="book", condition="#name.length() < 32") <1> - public Book findBook(String name) ----- -<1> Setting a condition on `@Cacheable`. - - -In addition to the `condition` parameter, you can use the `unless` parameter to veto the -adding of a value to the cache. Unlike `condition`, `unless` expressions are evaluated -after the method has been invoked. To expand on the previous example, perhaps we only -want to cache paperback books, as the following example does: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") <1> - public Book findBook(String name) ----- -<1> Using the `unless` attribute to block hardbacks. - - -The cache abstraction supports `java.util.Optional` return types. If an `Optional` value -is _present_, it will be stored in the associated cache. If an `Optional` value is not -present, `null` will be stored in the associated cache. `#result` always refers to the -business entity and never a supported wrapper, so the previous example can be rewritten -as follows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback") - public Optional findBook(String name) ----- - -Note that `#result` still refers to `Book` and not `Optional`. Since it might be -`null`, we use SpEL's <>. - -[[cache-spel-context]] -===== Available Caching SpEL Evaluation Context - -Each `SpEL` expression evaluates against a dedicated <>. -In addition to the built-in parameters, the framework provides dedicated caching-related -metadata, such as the argument names. The following table describes the items made -available to the context so that you can use them for key and conditional computations: - -[[cache-spel-context-tbl]] -.Cache SpEL available metadata -|=== -| Name| Location| Description| Example - -| `methodName` -| Root object -| The name of the method being invoked -| `#root.methodName` - -| `method` -| Root object -| The method being invoked -| `#root.method.name` - -| `target` -| Root object -| The target object being invoked -| `#root.target` - -| `targetClass` -| Root object -| The class of the target being invoked -| `#root.targetClass` - -| `args` -| Root object -| The arguments (as array) used for invoking the target -| `#root.args[0]` - -| `caches` -| Root object -| Collection of caches against which the current method is run -| `#root.caches[0].name` - -| Argument name -| Evaluation context -| Name of any of the method arguments. If the names are not available - (perhaps due to having no debug information), the argument names are also available under the `#a<#arg>` - where `#arg` stands for the argument index (starting from `0`). -| `#iban` or `#a0` (you can also use `#p0` or `#p<#arg>` notation as an alias). - -| `result` -| Evaluation context -| The result of the method call (the value to be cached). Only available in `unless` - expressions, `cache put` expressions (to compute the `key`), or `cache evict` - expressions (when `beforeInvocation` is `false`). For supported wrappers (such as - `Optional`), `#result` refers to the actual object, not the wrapper. -| `#result` -|=== - - -[[cache-annotations-put]] -==== The `@CachePut` Annotation - -When the cache needs to be updated without interfering with the method execution, -you can use the `@CachePut` annotation. That is, the method is always invoked and its -result is placed into the cache (according to the `@CachePut` options). It supports -the same options as `@Cacheable` and should be used for cache population rather than -method flow optimization. The following example uses the `@CachePut` annotation: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @CachePut(cacheNames="book", key="#isbn") - public Book updateBook(ISBN isbn, BookDescriptor descriptor) ----- - -IMPORTANT: Using `@CachePut` and `@Cacheable` annotations on the same method is generally -strongly discouraged because they have different behaviors. While the latter causes the -method invocation to be skipped by using the cache, the former forces the invocation in -order to run a cache update. This leads to unexpected behavior and, with the exception -of specific corner-cases (such as annotations having conditions that exclude them from each -other), such declarations should be avoided. Note also that such conditions should not rely -on the result object (that is, the `#result` variable), as these are validated up-front to -confirm the exclusion. - - -[[cache-annotations-evict]] -==== The `@CacheEvict` annotation - -The cache abstraction allows not just population of a cache store but also eviction. -This process is useful for removing stale or unused data from the cache. As opposed to -`@Cacheable`, `@CacheEvict` demarcates methods that perform cache -eviction (that is, methods that act as triggers for removing data from the cache). -Similarly to its sibling, `@CacheEvict` requires specifying one or more caches -that are affected by the action, allows a custom cache and key resolution or a -condition to be specified, and features an extra parameter -(`allEntries`) that indicates whether a cache-wide eviction needs to be performed -rather than just an entry eviction (based on the key). The following example evicts -all entries from the `books` cache: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @CacheEvict(cacheNames="books", allEntries=true) <1> - public void loadBooks(InputStream batch) ----- -<1> Using the `allEntries` attribute to evict all entries from the cache. - -This option comes in handy when an entire cache region needs to be cleared out. -Rather than evicting each entry (which would take a long time, since it is inefficient), -all the entries are removed in one operation, as the preceding example shows. -Note that the framework ignores any key specified in this scenario as it does not apply -(the entire cache is evicted, not only one entry). - -You can also indicate whether the eviction should occur after (the default) or before -the method is invoked by using the `beforeInvocation` attribute. The former provides the -same semantics as the rest of the annotations: Once the method completes successfully, -an action (in this case, eviction) on the cache is run. If the method does not -run (as it might be cached) or an exception is thrown, the eviction does not occur. -The latter (`beforeInvocation=true`) causes the eviction to always occur before the -method is invoked. This is useful in cases where the eviction does not need to be tied -to the method outcome. - -Note that `void` methods can be used with `@CacheEvict` - as the methods act as a -trigger, the return values are ignored (as they do not interact with the cache). This is -not the case with `@Cacheable` which adds data to the cache or updates data in the cache -and, thus, requires a result. - - -[[cache-annotations-caching]] -==== The `@Caching` Annotation - -Sometimes, multiple annotations of the same type (such as `@CacheEvict` or -`@CachePut`) need to be specified -- for example, because the condition or the key -expression is different between different caches. `@Caching` lets multiple nested -`@Cacheable`, `@CachePut`, and `@CacheEvict` annotations be used on the same method. -The following example uses two `@CacheEvict` annotations: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") }) - public Book importBooks(String deposit, Date date) ----- - - -[[cache-annotations-config]] -==== The `@CacheConfig` annotation - -So far, we have seen that caching operations offer many customization options and that -you can set these options for each operation. However, some of the customization options -can be tedious to configure if they apply to all operations of the class. For -instance, specifying the name of the cache to use for every cache operation of the -class can be replaced by a single class-level definition. This is where `@CacheConfig` -comes into play. The following examples uses `@CacheConfig` to set the name of the cache: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @CacheConfig("books") <1> - public class BookRepositoryImpl implements BookRepository { - - @Cacheable - public Book findBook(ISBN isbn) {...} - } ----- -<1> Using `@CacheConfig` to set the name of the cache. - -`@CacheConfig` is a class-level annotation that allows sharing the cache names, -the custom `KeyGenerator`, the custom `CacheManager`, and the custom `CacheResolver`. -Placing this annotation on the class does not turn on any caching operation. - -An operation-level customization always overrides a customization set on `@CacheConfig`. -Therefore, this gives three levels of customizations for each cache operation: - -* Globally configured, available for `CacheManager`, `KeyGenerator`. -* At the class level, using `@CacheConfig`. -* At the operation level. - - -[[cache-annotation-enable]] -==== Enabling Caching Annotations - -It is important to note that even though declaring the cache annotations does not -automatically trigger their actions - like many things in Spring, the feature has to be -declaratively enabled (which means if you ever suspect caching is to blame, you can -disable it by removing only one configuration line rather than all the annotations in -your code). - -To enable caching annotations add the annotation `@EnableCaching` to one of your -`@Configuration` classes: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Configuration - @EnableCaching - public class AppConfig { - } ----- - -Alternatively, for XML configuration you can use the `cache:annotation-driven` element: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - ----- - -Both the `cache:annotation-driven` element and the `@EnableCaching` annotation let you -specify various options that influence the way the caching behavior is added to the -application through AOP. The configuration is intentionally similar with that of -<>. - -NOTE: The default advice mode for processing caching annotations is `proxy`, which allows -for interception of calls through the proxy only. Local calls within the same class -cannot get intercepted that way. For a more advanced mode of interception, consider -switching to `aspectj` mode in combination with compile-time or load-time weaving. - -NOTE: For more detail about advanced customizations (using Java configuration) that are -required to implement `CachingConfigurer`, see the -{api-spring-framework}/cache/annotation/CachingConfigurer.html[javadoc]. - -[[cache-annotation-driven-settings]] -.Cache annotation settings -[cols="1,1,1,3"] -|=== -| XML Attribute | Annotation Attribute | Default | Description - -| `cache-manager` -| N/A (see the {api-spring-framework}/cache/annotation/CachingConfigurer.html[`CachingConfigurer`] javadoc) -| `cacheManager` -| The name of the cache manager to use. A default `CacheResolver` is initialized behind - the scenes with this cache manager (or `cacheManager` if not set). For more - fine-grained management of the cache resolution, consider setting the 'cache-resolver' - attribute. - -| `cache-resolver` -| N/A (see the {api-spring-framework}/cache/annotation/CachingConfigurer.html[`CachingConfigurer`] javadoc) -| A `SimpleCacheResolver` using the configured `cacheManager`. -| The bean name of the CacheResolver that is to be used to resolve the backing caches. - This attribute is not required and needs to be specified only as an alternative to - the 'cache-manager' attribute. - -| `key-generator` -| N/A (see the {api-spring-framework}/cache/annotation/CachingConfigurer.html[`CachingConfigurer`] javadoc) -| `SimpleKeyGenerator` -| Name of the custom key generator to use. - -| `error-handler` -| N/A (see the {api-spring-framework}/cache/annotation/CachingConfigurer.html[`CachingConfigurer`] javadoc) -| `SimpleCacheErrorHandler` -| The name of the custom cache error handler to use. By default, any exception thrown during - a cache related operation is thrown back at the client. - -| `mode` -| `mode` -| `proxy` -| The default mode (`proxy`) processes annotated beans to be proxied by using Spring's AOP - framework (following proxy semantics, as discussed earlier, applying to method calls - coming in through the proxy only). The alternative mode (`aspectj`) instead weaves the - affected classes with Spring's AspectJ caching aspect, modifying the target class byte - code to apply to any kind of method call. AspectJ weaving requires `spring-aspects.jar` - in the classpath as well as load-time weaving (or compile-time weaving) enabled. (See - <> for details on how to set up - load-time weaving.) - -| `proxy-target-class` -| `proxyTargetClass` -| `false` -| Applies to proxy mode only. Controls what type of caching proxies are created for - classes annotated with the `@Cacheable` or `@CacheEvict` annotations. If the - `proxy-target-class` attribute is set to `true`, class-based proxies are created. - If `proxy-target-class` is `false` or if the attribute is omitted, standard JDK - interface-based proxies are created. (See <> - for a detailed examination of the different proxy types.) - -| `order` -| `order` -| Ordered.LOWEST_PRECEDENCE -| Defines the order of the cache advice that is applied to beans annotated with - `@Cacheable` or `@CacheEvict`. (For more information about the rules related to - ordering AOP advice, see <>.) - No specified ordering means that the AOP subsystem determines the order of the advice. -|=== - -NOTE: `` looks for `@Cacheable/@CachePut/@CacheEvict/@Caching` -only on beans in the same application context in which it is defined. This means that, -if you put `` in a `WebApplicationContext` for a -`DispatcherServlet`, it checks for beans only in your controllers, not your services. -See <> for more information. - -.Method visibility and cache annotations -**** -When you use proxies, you should apply the cache annotations only to methods with -public visibility. If you do annotate protected, private, or package-visible methods -with these annotations, no error is raised, but the annotated method does not exhibit -the configured caching settings. Consider using AspectJ (see the rest of this section) -if you need to annotate non-public methods, as it changes the bytecode itself. -**** - -TIP: Spring recommends that you only annotate concrete classes (and methods of concrete -classes) with the `@Cache{asterisk}` annotations, as opposed to annotating interfaces. -You certainly can place an `@Cache{asterisk}` annotation on an interface (or an interface -method), but this works only if you use the proxy mode (`mode="proxy"`). If you use the -weaving-based aspect (`mode="aspectj"`), the caching settings are not recognized on -interface-level declarations by the weaving infrastructure. - -NOTE: In proxy mode (the default), only external method calls coming in through the -proxy are intercepted. This means that self-invocation (in effect, a method within the -target object that calls another method of the target object) does not lead to actual -caching at runtime even if the invoked method is marked with `@Cacheable`. Consider -using the `aspectj` mode in this case. Also, the proxy must be fully initialized to -provide the expected behavior, so you should not rely on this feature in your -initialization code (that is, `@PostConstruct`). - - -[[cache-annotation-stereotype]] -==== Using Custom Annotations - -.Custom annotation and AspectJ -**** -This feature works only with the proxy-based approach but can be enabled -with a bit of extra effort by using AspectJ. - -The `spring-aspects` module defines an aspect for the standard annotations only. -If you have defined your own annotations, you also need to define an aspect for -those. Check `AnnotationCacheAspect` for an example. -**** - -The caching abstraction lets you use your own annotations to identify what method -triggers cache population or eviction. This is quite handy as a template mechanism, -as it eliminates the need to duplicate cache annotation declarations, which is -especially useful if the key or condition are specified or if the foreign imports -(`org.springframework`) are not allowed in your code base. Similarly to the rest -of the <> annotations, you can -use `@Cacheable`, `@CachePut`, `@CacheEvict`, and `@CacheConfig` as -<> (that is, annotations that -can annotate other annotations). In the following example, we replace a common -`@Cacheable` declaration with our own custom annotation: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD}) - @Cacheable(cacheNames="books", key="#isbn") - public @interface SlowService { - } ----- - -In the preceding example, we have defined our own `SlowService` annotation, -which itself is annotated with `@Cacheable`. Now we can replace the following code: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable(cacheNames="books", key="#isbn") - public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) ----- - -The following example shows the custom annotation with which we can replace the -preceding code: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @SlowService - public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) ----- - -Even though `@SlowService` is not a Spring annotation, the container automatically picks -up its declaration at runtime and understands its meaning. Note that, as mentioned -<>, annotation-driven behavior needs to be enabled. - - - -[[cache-jsr-107]] -=== JCache (JSR-107) Annotations - -Since version 4.1, Spring's caching abstraction fully supports the JCache standard -(JSR-107) annotations: `@CacheResult`, `@CachePut`, `@CacheRemove`, and `@CacheRemoveAll` -as well as the `@CacheDefaults`, `@CacheKey`, and `@CacheValue` companions. -You can use these annotations even without migrating your cache store to JSR-107. -The internal implementation uses Spring's caching abstraction and provides default -`CacheResolver` and `KeyGenerator` implementations that are compliant with the -specification. In other words, if you are already using Spring's caching abstraction, -you can switch to these standard annotations without changing your cache storage -(or configuration, for that matter). - - -[[cache-jsr-107-summary]] -==== Feature Summary - -For those who are familiar with Spring's caching annotations, the following table -describes the main differences between the Spring annotations and their JSR-107 -counterparts: - -.Spring vs. JSR-107 caching annotations -[cols="1,1,3"] -|=== -| Spring | JSR-107 | Remark - -| `@Cacheable` -| `@CacheResult` -| Fairly similar. `@CacheResult` can cache specific exceptions and force the - execution of the method regardless of the content of the cache. - -| `@CachePut` -| `@CachePut` -| While Spring updates the cache with the result of the method invocation, JCache - requires that it be passed it as an argument that is annotated with `@CacheValue`. - Due to this difference, JCache allows updating the cache before or after the - actual method invocation. - -| `@CacheEvict` -| `@CacheRemove` -| Fairly similar. `@CacheRemove` supports conditional eviction when the - method invocation results in an exception. - -| `@CacheEvict(allEntries=true)` -| `@CacheRemoveAll` -| See `@CacheRemove`. - -| `@CacheConfig` -| `@CacheDefaults` -| Lets you configure the same concepts, in a similar fashion. -|=== - -JCache has the notion of `javax.cache.annotation.CacheResolver`, which is identical -to the Spring's `CacheResolver` interface, except that JCache supports only a single -cache. By default, a simple implementation retrieves the cache to use based on the -name declared on the annotation. It should be noted that, if no cache name is -specified on the annotation, a default is automatically generated. See the javadoc -of `@CacheResult#cacheName()` for more information. - -`CacheResolver` instances are retrieved by a `CacheResolverFactory`. It is possible -to customize the factory for each cache operation, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class) <1> - public Book findBook(ISBN isbn) ----- -<1> Customizing the factory for this operation. - -NOTE: For all referenced classes, Spring tries to locate a bean with the given type. -If more than one match exists, a new instance is created and can use the regular -bean lifecycle callbacks, such as dependency injection. - -Keys are generated by a `javax.cache.annotation.CacheKeyGenerator` that serves the -same purpose as Spring's `KeyGenerator`. By default, all method arguments are taken -into account, unless at least one parameter is annotated with `@CacheKey`. This is -similar to Spring's <>. For instance, the following are identical operations, one using -Spring's abstraction and the other using JCache: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Cacheable(cacheNames="books", key="#isbn") - public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) - - @CacheResult(cacheName="books") - public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse, boolean includeUsed) ----- - -You can also specify the `CacheKeyResolver` on the operation, similar to how you can -specify the `CacheResolverFactory`. - -JCache can manage exceptions thrown by annotated methods. This can prevent an update of -the cache, but it can also cache the exception as an indicator of the failure instead of -calling the method again. Assume that `InvalidIsbnNotFoundException` is thrown if the -structure of the ISBN is invalid. This is a permanent failure (no book could ever be -retrieved with such a parameter). The following caches the exception so that further -calls with the same, invalid, ISBN throw the cached exception directly instead of -invoking the method again: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @CacheResult(cacheName="books", exceptionCacheName="failures" - cachedExceptions = InvalidIsbnNotFoundException.class) - public Book findBook(ISBN isbn) ----- - - -==== Enabling JSR-107 Support - -You do not need to do anything specific to enable the JSR-107 support alongside Spring's -declarative annotation support. Both `@EnableCaching` and the `cache:annotation-driven` -XML element automatically enable the JCache support if both the JSR-107 API and the -`spring-context-support` module are present in the classpath. - -NOTE: Depending on your use case, the choice is basically yours. You can even mix and -match services by using the JSR-107 API on some and using Spring's own annotations on -others. However, if these services impact the same caches, you should use a consistent -and identical key generation implementation. - - - -[[cache-declarative-xml]] -=== Declarative XML-based Caching - -If annotations are not an option (perhaps due to having no access to the sources -or no external code), you can use XML for declarative caching. So, instead of -annotating the methods for caching, you can specify the target method and the -caching directives externally (similar to the declarative transaction management -<>). The example -from the previous section can be translated into the following example: - -[source,xml,indent=0] -[subs="verbatim"] ----- - - - - - - - - - - - - - - - - - ----- - -In the preceding configuration, the `bookService` is made cacheable. The caching semantics -to apply are encapsulated in the `cache:advice` definition, which causes the `findBooks` -method to be used for putting data into the cache and the `loadBooks` method for evicting -data. Both definitions work against the `books` cache. - -The `aop:config` definition applies the cache advice to the appropriate points in the -program by using the AspectJ pointcut expression (more information is available in -<>). In the preceding example, -all methods from the `BookService` are considered and the cache advice is applied to them. - -The declarative XML caching supports all of the annotation-based model, so moving between -the two should be fairly easy. Furthermore, both can be used inside the same application. -The XML-based approach does not touch the target code. However, it is inherently more -verbose. When dealing with classes that have overloaded methods that are targeted for -caching, identifying the proper methods does take an extra effort, since the `method` -argument is not a good discriminator. In these cases, you can use the AspectJ pointcut -to cherry pick the target methods and apply the appropriate caching functionality. -However, through XML, it is easier to apply package or group or interface-wide caching -(again, due to the AspectJ pointcut) and to create template-like definitions (as we did -in the preceding example by defining the target cache through the `cache:definitions` -`cache` attribute). - - - -[[cache-store-configuration]] -=== Configuring the Cache Storage - -The cache abstraction provides several storage integration options. To use them, you need -to declare an appropriate `CacheManager` (an entity that controls and manages `Cache` -instances and that can be used to retrieve these for storage). - - -[[cache-store-configuration-jdk]] -==== JDK `ConcurrentMap`-based Cache - -The JDK-based `Cache` implementation resides under -`org.springframework.cache.concurrent` package. It lets you use `ConcurrentHashMap` -as a backing `Cache` store. The following example shows how to configure two caches: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - ----- - -The preceding snippet uses the `SimpleCacheManager` to create a `CacheManager` for the -two nested `ConcurrentMapCache` instances named `default` and `books`. Note that the -names are configured directly for each cache. - -As the cache is created by the application, it is bound to its lifecycle, making it -suitable for basic use cases, tests, or simple applications. The cache scales well -and is very fast, but it does not provide any management, persistence capabilities, -or eviction contracts. - - -[[cache-store-configuration-eviction]] -==== Ehcache-based Cache - -Ehcache 3.x is fully JSR-107 compliant and no dedicated support is required for it. See -<> for details. - - -[[cache-store-configuration-caffeine]] -==== Caffeine Cache - -Caffeine is a Java 8 rewrite of Guava's cache, and its implementation is located in the -`org.springframework.cache.caffeine` package and provides access to several features -of Caffeine. - -The following example configures a `CacheManager` that creates the cache on demand: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - ----- - -You can also provide the caches to use explicitly. In that case, only those -are made available by the manager. The following example shows how to do so: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - default - books - - - ----- - -The Caffeine `CacheManager` also supports custom `Caffeine` and `CacheLoader`. -See the https://github.com/ben-manes/caffeine/wiki[Caffeine documentation] -for more information about those. - - -[[cache-store-configuration-gemfire]] -==== GemFire-based Cache - -GemFire is a memory-oriented, disk-backed, elastically scalable, continuously available, -active (with built-in pattern-based subscription notifications), globally replicated -database and provides fully-featured edge caching. For further information on how to -use GemFire as a `CacheManager` (and more), see the -{doc-spring-gemfire}/html/[Spring Data GemFire reference documentation]. - - -[[cache-store-configuration-jsr107]] -==== JSR-107 Cache - -Spring's caching abstraction can also use JSR-107-compliant caches. The JCache -implementation is located in the `org.springframework.cache.jcache` package. - -Again, to use it, you need to declare the appropriate `CacheManager`. -The following example shows how to do so: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - ----- - - -[[cache-store-configuration-noop]] -==== Dealing with Caches without a Backing Store - -Sometimes, when switching environments or doing testing, you might have cache -declarations without having an actual backing cache configured. As this is an invalid -configuration, an exception is thrown at runtime, since the caching infrastructure -is unable to find a suitable store. In situations like this, rather than removing the -cache declarations (which can prove tedious), you can wire in a simple dummy cache that -performs no caching -- that is, it forces the cached methods to be invoked every time. -The following example shows how to do so: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - ----- - -The `CompositeCacheManager` in the preceding chains multiple `CacheManager` instances and, -through the `fallbackToNoOpCache` flag, adds a no-op cache for all the definitions not -handled by the configured cache managers. That is, every cache definition not found in -either `jdkCache` or `gemfireCache` (configured earlier in the example) is handled by -the no-op cache, which does not store any information, causing the target method to be -invoked every time. - - - -[[cache-plug]] -=== Plugging-in Different Back-end Caches - -Clearly, there are plenty of caching products out there that you can use as a backing -store. For those that do not support JSR-107 you need to provide a `CacheManager` and a -`Cache` implementation. This may sound harder than it is, since, in practice, the classes -tend to be simple https://en.wikipedia.org/wiki/Adapter_pattern[adapters] that map the -caching abstraction framework on top of the storage API, as the _Caffeine_ classes do. -Most `CacheManager` classes can use the classes in the -`org.springframework.cache.support` package (such as `AbstractCacheManager` which takes -care of the boiler-plate code, leaving only the actual mapping to be completed). - - - -[[cache-specific-config]] -=== How can I Set the TTL/TTI/Eviction policy/XXX feature? - -Directly through your cache provider. The cache abstraction is an abstraction, -not a cache implementation. The solution you use might support various data -policies and different topologies that other solutions do not support (for example, -the JDK `ConcurrentHashMap` -- exposing that in the cache abstraction would be useless -because there would no backing support). Such functionality should be controlled -directly through the backing cache (when configuring it) or through its native API. +include::integration/email.adoc[leveloffset=+1] +include::integration/scheduling.adoc[leveloffset=+1] +include::integration/cache.adoc[leveloffset=+1] +include::integration/observability.adoc[leveloffset=+1] include::integration/integration-appendix.adoc[leveloffset=+1] diff --git a/framework-docs/src/docs/asciidoc/integration/cache.adoc b/framework-docs/src/docs/asciidoc/integration/cache.adoc new file mode 100644 index 000000000000..af0c885f80ad --- /dev/null +++ b/framework-docs/src/docs/asciidoc/integration/cache.adoc @@ -0,0 +1,1070 @@ +[[cache]] += Cache Abstraction + +Since version 3.1, the Spring Framework provides support for transparently adding caching to +an existing Spring application. Similar to the <> +support, the caching abstraction allows consistent use of various caching solutions with +minimal impact on the code. + +In Spring Framework 4.1, the cache abstraction was significantly extended with support +for <> and more customization options. + + + +[[cache-strategies]] +== Understanding the Cache Abstraction + +.Cache vs Buffer +**** + +The terms, "`buffer`" and "`cache,`" tend to be used interchangeably. Note, however, +that they represent different things. Traditionally, a buffer is used as an intermediate +temporary store for data between a fast and a slow entity. As one party would have to wait +for the other (which affects performance), the buffer alleviates this by allowing entire +blocks of data to move at once rather than in small chunks. The data is written and read +only once from the buffer. Furthermore, the buffers are visible to at least one party +that is aware of it. + +A cache, on the other hand, is, by definition, hidden, and neither party is aware that +caching occurs. It also improves performance but does so by letting the same data be +read multiple times in a fast fashion. + +You can find a further explanation of the differences between a buffer and a cache +https://en.wikipedia.org/wiki/Cache_(computing)#The_difference_between_buffer_and_cache[here]. +**** + +At its core, the cache abstraction applies caching to Java methods, thus reducing the +number of executions based on the information available in the cache. That is, each time +a targeted method is invoked, the abstraction applies a caching behavior that checks +whether the method has been already invoked for the given arguments. If it has been +invoked, the cached result is returned without having to invoke the actual method. +If the method has not been invoked, then it is invoked, and the result is cached and +returned to the user so that, the next time the method is invoked, the cached result is +returned. This way, expensive methods (whether CPU- or IO-bound) can be invoked only +once for a given set of parameters and the result reused without having to actually +invoke the method again. The caching logic is applied transparently without any +interference to the invoker. + +IMPORTANT: This approach works only for methods that are guaranteed to return the same +output (result) for a given input (or arguments) no matter how many times they are invoked. + +The caching abstraction provides other cache-related operations, such as the ability +to update the content of the cache or to remove one or all entries. These are useful if +the cache deals with data that can change during the course of the application. + +As with other services in the Spring Framework, the caching service is an abstraction +(not a cache implementation) and requires the use of actual storage to store the cache data -- +that is, the abstraction frees you from having to write the caching logic but does not +provide the actual data store. This abstraction is materialized by the +`org.springframework.cache.Cache` and `org.springframework.cache.CacheManager` interfaces. + +Spring provides <> of that abstraction: +JDK `java.util.concurrent.ConcurrentMap` based caches, Gemfire cache, +https://github.com/ben-manes/caffeine/wiki[Caffeine], and JSR-107 compliant caches (such +as Ehcache 3.x). See <> for more information on plugging in other cache +stores and providers. + +IMPORTANT: The caching abstraction has no special handling for multi-threaded and +multi-process environments, as such features are handled by the cache implementation. + +If you have a multi-process environment (that is, an application deployed on several nodes), +you need to configure your cache provider accordingly. Depending on your use cases, a copy +of the same data on several nodes can be enough. However, if you change the data during +the course of the application, you may need to enable other propagation mechanisms. + +Caching a particular item is a direct equivalent of the typical +get-if-not-found-then-proceed-and-put-eventually code blocks +found with programmatic cache interaction. +No locks are applied, and several threads may try to load the same item concurrently. +The same applies to eviction. If several threads are trying to update or evict data +concurrently, you may use stale data. Certain cache providers offer advanced features +in that area. See the documentation of your cache provider for more details. + +To use the cache abstraction, you need to take care of two aspects: + +* Caching declaration: Identify the methods that need to be cached and their policies. +* Cache configuration: The backing cache where the data is stored and from which it is read. + + + +[[cache-annotations]] +== Declarative Annotation-based Caching + +For caching declaration, Spring's caching abstraction provides a set of Java annotations: + +* `@Cacheable`: Triggers cache population. +* `@CacheEvict`: Triggers cache eviction. +* `@CachePut`: Updates the cache without interfering with the method execution. +* `@Caching`: Regroups multiple cache operations to be applied on a method. +* `@CacheConfig`: Shares some common cache-related settings at class-level. + + +[[cache-annotations-cacheable]] +=== The `@Cacheable` Annotation + +As the name implies, you can use `@Cacheable` to demarcate methods that are cacheable -- +that is, methods for which the result is stored in the cache so that, on subsequent +invocations (with the same arguments), the value in the cache is returned without +having to actually invoke the method. In its simplest form, the annotation declaration +requires the name of the cache associated with the annotated method, as the following +example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable("books") + public Book findBook(ISBN isbn) {...} +---- + +In the preceding snippet, the `findBook` method is associated with the cache named `books`. +Each time the method is called, the cache is checked to see whether the invocation has +already been run and does not have to be repeated. While in most cases, only one +cache is declared, the annotation lets multiple names be specified so that more than one +cache is being used. In this case, each of the caches is checked before invoking the +method -- if at least one cache is hit, the associated value is returned. + +NOTE: All the other caches that do not contain the value are also updated, even though +the cached method was not actually invoked. + +The following example uses `@Cacheable` on the `findBook` method with multiple caches: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable({"books", "isbns"}) + public Book findBook(ISBN isbn) {...} +---- + +[[cache-annotations-cacheable-default-key]] +==== Default Key Generation + +Since caches are essentially key-value stores, each invocation of a cached method +needs to be translated into a suitable key for cache access. The caching abstraction +uses a simple `KeyGenerator` based on the following algorithm: + +* If no parameters are given, return `SimpleKey.EMPTY`. +* If only one parameter is given, return that instance. +* If more than one parameter is given, return a `SimpleKey` that contains all parameters. + +This approach works well for most use-cases, as long as parameters have natural keys +and implement valid `hashCode()` and `equals()` methods. If that is not the case, +you need to change the strategy. + +To provide a different default key generator, you need to implement the +`org.springframework.cache.interceptor.KeyGenerator` interface. + +[NOTE] +==== +The default key generation strategy changed with the release of Spring 4.0. Earlier +versions of Spring used a key generation strategy that, for multiple key parameters, +considered only the `hashCode()` of parameters and not `equals()`. This could cause +unexpected key collisions (see https://jira.spring.io/browse/SPR-10237[SPR-10237] +for background). The new `SimpleKeyGenerator` uses a compound key for such scenarios. + +If you want to keep using the previous key strategy, you can configure the deprecated +`org.springframework.cache.interceptor.DefaultKeyGenerator` class or create a custom +hash-based `KeyGenerator` implementation. +==== + +[[cache-annotations-cacheable-key]] +==== Custom Key Generation Declaration + +Since caching is generic, the target methods are quite likely to have various signatures +that cannot be readily mapped on top of the cache structure. This tends to become obvious +when the target method has multiple arguments out of which only some are suitable for +caching (while the rest are used only by the method logic). Consider the following example: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable("books") + public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) +---- + +At first glance, while the two `boolean` arguments influence the way the book is found, +they are no use for the cache. Furthermore, what if only one of the two is important +while the other is not? + +For such cases, the `@Cacheable` annotation lets you specify how the key is generated +through its `key` attribute. You can use <> to pick the +arguments of interest (or their nested properties), perform operations, or even +invoke arbitrary methods without having to write any code or implement any interface. +This is the recommended approach over the +<>, since methods tend to be +quite different in signatures as the code base grows. While the default strategy might +work for some methods, it rarely works for all methods. + +The following examples use various SpEL declarations (if you are not familiar with SpEL, +do yourself a favor and read <>): + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable(cacheNames="books", key="#isbn") + public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) + + @Cacheable(cacheNames="books", key="#isbn.rawNumber") + public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) + + @Cacheable(cacheNames="books", key="T(someType).hash(#isbn)") + public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) +---- + +The preceding snippets show how easy it is to select a certain argument, one of its +properties, or even an arbitrary (static) method. + +If the algorithm responsible for generating the key is too specific or if it needs +to be shared, you can define a custom `keyGenerator` on the operation. To do so, +specify the name of the `KeyGenerator` bean implementation to use, as the following +example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable(cacheNames="books", keyGenerator="myKeyGenerator") + public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) +---- + +NOTE: The `key` and `keyGenerator` parameters are mutually exclusive and an operation +that specifies both results in an exception. + +[[cache-annotations-cacheable-default-cache-resolver]] +==== Default Cache Resolution + +The caching abstraction uses a simple `CacheResolver` that +retrieves the caches defined at the operation level by using the configured +`CacheManager`. + +To provide a different default cache resolver, you need to implement the +`org.springframework.cache.interceptor.CacheResolver` interface. + +[[cache-annotations-cacheable-cache-resolver]] +==== Custom Cache Resolution + +The default cache resolution fits well for applications that work with a +single `CacheManager` and have no complex cache resolution requirements. + +For applications that work with several cache managers, you can set the +`cacheManager` to use for each operation, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable(cacheNames="books", cacheManager="anotherCacheManager") <1> + public Book findBook(ISBN isbn) {...} +---- +<1> Specifying `anotherCacheManager`. + + +You can also replace the `CacheResolver` entirely in a fashion similar to that of +replacing <>. The resolution is +requested for every cache operation, letting the implementation actually resolve +the caches to use based on runtime arguments. The following example shows how to +specify a `CacheResolver`: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable(cacheResolver="runtimeCacheResolver") <1> + public Book findBook(ISBN isbn) {...} +---- +<1> Specifying the `CacheResolver`. + + +[NOTE] +==== +Since Spring 4.1, the `value` attribute of the cache annotations are no longer +mandatory, since this particular information can be provided by the `CacheResolver` +regardless of the content of the annotation. + +Similarly to `key` and `keyGenerator`, the `cacheManager` and `cacheResolver` +parameters are mutually exclusive, and an operation specifying both +results in an exception, as a custom `CacheManager` is ignored by the +`CacheResolver` implementation. This is probably not what you expect. +==== + +[[cache-annotations-cacheable-synchronized]] +==== Synchronized Caching + +In a multi-threaded environment, certain operations might be concurrently invoked for +the same argument (typically on startup). By default, the cache abstraction does not +lock anything, and the same value may be computed several times, defeating the purpose +of caching. + +For those particular cases, you can use the `sync` attribute to instruct the underlying +cache provider to lock the cache entry while the value is being computed. As a result, +only one thread is busy computing the value, while the others are blocked until the entry +is updated in the cache. The following example shows how to use the `sync` attribute: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable(cacheNames="foos", sync=true) <1> + public Foo executeExpensiveOperation(String id) {...} +---- +<1> Using the `sync` attribute. + +NOTE: This is an optional feature, and your favorite cache library may not support it. +All `CacheManager` implementations provided by the core framework support it. See the +documentation of your cache provider for more details. + +[[cache-annotations-cacheable-condition]] +==== Conditional Caching + +Sometimes, a method might not be suitable for caching all the time (for example, it might +depend on the given arguments). The cache annotations support such use cases through the +`condition` parameter, which takes a `SpEL` expression that is evaluated to either `true` +or `false`. If `true`, the method is cached. If not, it behaves as if the method is not +cached (that is, the method is invoked every time no matter what values are in the cache +or what arguments are used). For example, the following method is cached only if the +argument `name` has a length shorter than 32: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable(cacheNames="book", condition="#name.length() < 32") <1> + public Book findBook(String name) +---- +<1> Setting a condition on `@Cacheable`. + + +In addition to the `condition` parameter, you can use the `unless` parameter to veto the +adding of a value to the cache. Unlike `condition`, `unless` expressions are evaluated +after the method has been invoked. To expand on the previous example, perhaps we only +want to cache paperback books, as the following example does: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") <1> + public Book findBook(String name) +---- +<1> Using the `unless` attribute to block hardbacks. + + +The cache abstraction supports `java.util.Optional` return types. If an `Optional` value +is _present_, it will be stored in the associated cache. If an `Optional` value is not +present, `null` will be stored in the associated cache. `#result` always refers to the +business entity and never a supported wrapper, so the previous example can be rewritten +as follows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback") + public Optional findBook(String name) +---- + +Note that `#result` still refers to `Book` and not `Optional`. Since it might be +`null`, we use SpEL's <>. + +[[cache-spel-context]] +==== Available Caching SpEL Evaluation Context + +Each `SpEL` expression evaluates against a dedicated <>. +In addition to the built-in parameters, the framework provides dedicated caching-related +metadata, such as the argument names. The following table describes the items made +available to the context so that you can use them for key and conditional computations: + +[[cache-spel-context-tbl]] +.Cache SpEL available metadata +|=== +| Name| Location| Description| Example + +| `methodName` +| Root object +| The name of the method being invoked +| `#root.methodName` + +| `method` +| Root object +| The method being invoked +| `#root.method.name` + +| `target` +| Root object +| The target object being invoked +| `#root.target` + +| `targetClass` +| Root object +| The class of the target being invoked +| `#root.targetClass` + +| `args` +| Root object +| The arguments (as array) used for invoking the target +| `#root.args[0]` + +| `caches` +| Root object +| Collection of caches against which the current method is run +| `#root.caches[0].name` + +| Argument name +| Evaluation context +| Name of any of the method arguments. If the names are not available + (perhaps due to having no debug information), the argument names are also available under the `#a<#arg>` + where `#arg` stands for the argument index (starting from `0`). +| `#iban` or `#a0` (you can also use `#p0` or `#p<#arg>` notation as an alias). + +| `result` +| Evaluation context +| The result of the method call (the value to be cached). Only available in `unless` + expressions, `cache put` expressions (to compute the `key`), or `cache evict` + expressions (when `beforeInvocation` is `false`). For supported wrappers (such as + `Optional`), `#result` refers to the actual object, not the wrapper. +| `#result` +|=== + + +[[cache-annotations-put]] +=== The `@CachePut` Annotation + +When the cache needs to be updated without interfering with the method execution, +you can use the `@CachePut` annotation. That is, the method is always invoked and its +result is placed into the cache (according to the `@CachePut` options). It supports +the same options as `@Cacheable` and should be used for cache population rather than +method flow optimization. The following example uses the `@CachePut` annotation: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @CachePut(cacheNames="book", key="#isbn") + public Book updateBook(ISBN isbn, BookDescriptor descriptor) +---- + +IMPORTANT: Using `@CachePut` and `@Cacheable` annotations on the same method is generally +strongly discouraged because they have different behaviors. While the latter causes the +method invocation to be skipped by using the cache, the former forces the invocation in +order to run a cache update. This leads to unexpected behavior and, with the exception +of specific corner-cases (such as annotations having conditions that exclude them from each +other), such declarations should be avoided. Note also that such conditions should not rely +on the result object (that is, the `#result` variable), as these are validated up-front to +confirm the exclusion. + + +[[cache-annotations-evict]] +=== The `@CacheEvict` annotation + +The cache abstraction allows not just population of a cache store but also eviction. +This process is useful for removing stale or unused data from the cache. As opposed to +`@Cacheable`, `@CacheEvict` demarcates methods that perform cache +eviction (that is, methods that act as triggers for removing data from the cache). +Similarly to its sibling, `@CacheEvict` requires specifying one or more caches +that are affected by the action, allows a custom cache and key resolution or a +condition to be specified, and features an extra parameter +(`allEntries`) that indicates whether a cache-wide eviction needs to be performed +rather than just an entry eviction (based on the key). The following example evicts +all entries from the `books` cache: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @CacheEvict(cacheNames="books", allEntries=true) <1> + public void loadBooks(InputStream batch) +---- +<1> Using the `allEntries` attribute to evict all entries from the cache. + +This option comes in handy when an entire cache region needs to be cleared out. +Rather than evicting each entry (which would take a long time, since it is inefficient), +all the entries are removed in one operation, as the preceding example shows. +Note that the framework ignores any key specified in this scenario as it does not apply +(the entire cache is evicted, not only one entry). + +You can also indicate whether the eviction should occur after (the default) or before +the method is invoked by using the `beforeInvocation` attribute. The former provides the +same semantics as the rest of the annotations: Once the method completes successfully, +an action (in this case, eviction) on the cache is run. If the method does not +run (as it might be cached) or an exception is thrown, the eviction does not occur. +The latter (`beforeInvocation=true`) causes the eviction to always occur before the +method is invoked. This is useful in cases where the eviction does not need to be tied +to the method outcome. + +Note that `void` methods can be used with `@CacheEvict` - as the methods act as a +trigger, the return values are ignored (as they do not interact with the cache). This is +not the case with `@Cacheable` which adds data to the cache or updates data in the cache +and, thus, requires a result. + + +[[cache-annotations-caching]] +=== The `@Caching` Annotation + +Sometimes, multiple annotations of the same type (such as `@CacheEvict` or +`@CachePut`) need to be specified -- for example, because the condition or the key +expression is different between different caches. `@Caching` lets multiple nested +`@Cacheable`, `@CachePut`, and `@CacheEvict` annotations be used on the same method. +The following example uses two `@CacheEvict` annotations: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") }) + public Book importBooks(String deposit, Date date) +---- + + +[[cache-annotations-config]] +=== The `@CacheConfig` annotation + +So far, we have seen that caching operations offer many customization options and that +you can set these options for each operation. However, some of the customization options +can be tedious to configure if they apply to all operations of the class. For +instance, specifying the name of the cache to use for every cache operation of the +class can be replaced by a single class-level definition. This is where `@CacheConfig` +comes into play. The following examples uses `@CacheConfig` to set the name of the cache: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @CacheConfig("books") <1> + public class BookRepositoryImpl implements BookRepository { + + @Cacheable + public Book findBook(ISBN isbn) {...} + } +---- +<1> Using `@CacheConfig` to set the name of the cache. + +`@CacheConfig` is a class-level annotation that allows sharing the cache names, +the custom `KeyGenerator`, the custom `CacheManager`, and the custom `CacheResolver`. +Placing this annotation on the class does not turn on any caching operation. + +An operation-level customization always overrides a customization set on `@CacheConfig`. +Therefore, this gives three levels of customizations for each cache operation: + +* Globally configured, available for `CacheManager`, `KeyGenerator`. +* At the class level, using `@CacheConfig`. +* At the operation level. + + +[[cache-annotation-enable]] +=== Enabling Caching Annotations + +It is important to note that even though declaring the cache annotations does not +automatically trigger their actions - like many things in Spring, the feature has to be +declaratively enabled (which means if you ever suspect caching is to blame, you can +disable it by removing only one configuration line rather than all the annotations in +your code). + +To enable caching annotations add the annotation `@EnableCaching` to one of your +`@Configuration` classes: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Configuration + @EnableCaching + public class AppConfig { + } +---- + +Alternatively, for XML configuration you can use the `cache:annotation-driven` element: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + +---- + +Both the `cache:annotation-driven` element and the `@EnableCaching` annotation let you +specify various options that influence the way the caching behavior is added to the +application through AOP. The configuration is intentionally similar with that of +<>. + +NOTE: The default advice mode for processing caching annotations is `proxy`, which allows +for interception of calls through the proxy only. Local calls within the same class +cannot get intercepted that way. For a more advanced mode of interception, consider +switching to `aspectj` mode in combination with compile-time or load-time weaving. + +NOTE: For more detail about advanced customizations (using Java configuration) that are +required to implement `CachingConfigurer`, see the +{api-spring-framework}/cache/annotation/CachingConfigurer.html[javadoc]. + +[[cache-annotation-driven-settings]] +.Cache annotation settings +[cols="1,1,1,3"] +|=== +| XML Attribute | Annotation Attribute | Default | Description + +| `cache-manager` +| N/A (see the {api-spring-framework}/cache/annotation/CachingConfigurer.html[`CachingConfigurer`] javadoc) +| `cacheManager` +| The name of the cache manager to use. A default `CacheResolver` is initialized behind + the scenes with this cache manager (or `cacheManager` if not set). For more + fine-grained management of the cache resolution, consider setting the 'cache-resolver' + attribute. + +| `cache-resolver` +| N/A (see the {api-spring-framework}/cache/annotation/CachingConfigurer.html[`CachingConfigurer`] javadoc) +| A `SimpleCacheResolver` using the configured `cacheManager`. +| The bean name of the CacheResolver that is to be used to resolve the backing caches. + This attribute is not required and needs to be specified only as an alternative to + the 'cache-manager' attribute. + +| `key-generator` +| N/A (see the {api-spring-framework}/cache/annotation/CachingConfigurer.html[`CachingConfigurer`] javadoc) +| `SimpleKeyGenerator` +| Name of the custom key generator to use. + +| `error-handler` +| N/A (see the {api-spring-framework}/cache/annotation/CachingConfigurer.html[`CachingConfigurer`] javadoc) +| `SimpleCacheErrorHandler` +| The name of the custom cache error handler to use. By default, any exception thrown during + a cache related operation is thrown back at the client. + +| `mode` +| `mode` +| `proxy` +| The default mode (`proxy`) processes annotated beans to be proxied by using Spring's AOP + framework (following proxy semantics, as discussed earlier, applying to method calls + coming in through the proxy only). The alternative mode (`aspectj`) instead weaves the + affected classes with Spring's AspectJ caching aspect, modifying the target class byte + code to apply to any kind of method call. AspectJ weaving requires `spring-aspects.jar` + in the classpath as well as load-time weaving (or compile-time weaving) enabled. (See + <> for details on how to set up + load-time weaving.) + +| `proxy-target-class` +| `proxyTargetClass` +| `false` +| Applies to proxy mode only. Controls what type of caching proxies are created for + classes annotated with the `@Cacheable` or `@CacheEvict` annotations. If the + `proxy-target-class` attribute is set to `true`, class-based proxies are created. + If `proxy-target-class` is `false` or if the attribute is omitted, standard JDK + interface-based proxies are created. (See <> + for a detailed examination of the different proxy types.) + +| `order` +| `order` +| Ordered.LOWEST_PRECEDENCE +| Defines the order of the cache advice that is applied to beans annotated with + `@Cacheable` or `@CacheEvict`. (For more information about the rules related to + ordering AOP advice, see <>.) + No specified ordering means that the AOP subsystem determines the order of the advice. +|=== + +NOTE: `` looks for `@Cacheable/@CachePut/@CacheEvict/@Caching` +only on beans in the same application context in which it is defined. This means that, +if you put `` in a `WebApplicationContext` for a +`DispatcherServlet`, it checks for beans only in your controllers, not your services. +See <> for more information. + +.Method visibility and cache annotations +**** +When you use proxies, you should apply the cache annotations only to methods with +public visibility. If you do annotate protected, private, or package-visible methods +with these annotations, no error is raised, but the annotated method does not exhibit +the configured caching settings. Consider using AspectJ (see the rest of this section) +if you need to annotate non-public methods, as it changes the bytecode itself. +**** + +TIP: Spring recommends that you only annotate concrete classes (and methods of concrete +classes) with the `@Cache{asterisk}` annotations, as opposed to annotating interfaces. +You certainly can place an `@Cache{asterisk}` annotation on an interface (or an interface +method), but this works only if you use the proxy mode (`mode="proxy"`). If you use the +weaving-based aspect (`mode="aspectj"`), the caching settings are not recognized on +interface-level declarations by the weaving infrastructure. + +NOTE: In proxy mode (the default), only external method calls coming in through the +proxy are intercepted. This means that self-invocation (in effect, a method within the +target object that calls another method of the target object) does not lead to actual +caching at runtime even if the invoked method is marked with `@Cacheable`. Consider +using the `aspectj` mode in this case. Also, the proxy must be fully initialized to +provide the expected behavior, so you should not rely on this feature in your +initialization code (that is, `@PostConstruct`). + + +[[cache-annotation-stereotype]] +=== Using Custom Annotations + +.Custom annotation and AspectJ +**** +This feature works only with the proxy-based approach but can be enabled +with a bit of extra effort by using AspectJ. + +The `spring-aspects` module defines an aspect for the standard annotations only. +If you have defined your own annotations, you also need to define an aspect for +those. Check `AnnotationCacheAspect` for an example. +**** + +The caching abstraction lets you use your own annotations to identify what method +triggers cache population or eviction. This is quite handy as a template mechanism, +as it eliminates the need to duplicate cache annotation declarations, which is +especially useful if the key or condition are specified or if the foreign imports +(`org.springframework`) are not allowed in your code base. Similarly to the rest +of the <> annotations, you can +use `@Cacheable`, `@CachePut`, `@CacheEvict`, and `@CacheConfig` as +<> (that is, annotations that +can annotate other annotations). In the following example, we replace a common +`@Cacheable` declaration with our own custom annotation: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + @Cacheable(cacheNames="books", key="#isbn") + public @interface SlowService { + } +---- + +In the preceding example, we have defined our own `SlowService` annotation, +which itself is annotated with `@Cacheable`. Now we can replace the following code: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable(cacheNames="books", key="#isbn") + public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) +---- + +The following example shows the custom annotation with which we can replace the +preceding code: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @SlowService + public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) +---- + +Even though `@SlowService` is not a Spring annotation, the container automatically picks +up its declaration at runtime and understands its meaning. Note that, as mentioned +<>, annotation-driven behavior needs to be enabled. + + + +[[cache-jsr-107]] +== JCache (JSR-107) Annotations + +Since version 4.1, Spring's caching abstraction fully supports the JCache standard +(JSR-107) annotations: `@CacheResult`, `@CachePut`, `@CacheRemove`, and `@CacheRemoveAll` +as well as the `@CacheDefaults`, `@CacheKey`, and `@CacheValue` companions. +You can use these annotations even without migrating your cache store to JSR-107. +The internal implementation uses Spring's caching abstraction and provides default +`CacheResolver` and `KeyGenerator` implementations that are compliant with the +specification. In other words, if you are already using Spring's caching abstraction, +you can switch to these standard annotations without changing your cache storage +(or configuration, for that matter). + + +[[cache-jsr-107-summary]] +=== Feature Summary + +For those who are familiar with Spring's caching annotations, the following table +describes the main differences between the Spring annotations and their JSR-107 +counterparts: + +.Spring vs. JSR-107 caching annotations +[cols="1,1,3"] +|=== +| Spring | JSR-107 | Remark + +| `@Cacheable` +| `@CacheResult` +| Fairly similar. `@CacheResult` can cache specific exceptions and force the + execution of the method regardless of the content of the cache. + +| `@CachePut` +| `@CachePut` +| While Spring updates the cache with the result of the method invocation, JCache + requires that it be passed it as an argument that is annotated with `@CacheValue`. + Due to this difference, JCache allows updating the cache before or after the + actual method invocation. + +| `@CacheEvict` +| `@CacheRemove` +| Fairly similar. `@CacheRemove` supports conditional eviction when the + method invocation results in an exception. + +| `@CacheEvict(allEntries=true)` +| `@CacheRemoveAll` +| See `@CacheRemove`. + +| `@CacheConfig` +| `@CacheDefaults` +| Lets you configure the same concepts, in a similar fashion. +|=== + +JCache has the notion of `javax.cache.annotation.CacheResolver`, which is identical +to the Spring's `CacheResolver` interface, except that JCache supports only a single +cache. By default, a simple implementation retrieves the cache to use based on the +name declared on the annotation. It should be noted that, if no cache name is +specified on the annotation, a default is automatically generated. See the javadoc +of `@CacheResult#cacheName()` for more information. + +`CacheResolver` instances are retrieved by a `CacheResolverFactory`. It is possible +to customize the factory for each cache operation, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class) <1> + public Book findBook(ISBN isbn) +---- +<1> Customizing the factory for this operation. + +NOTE: For all referenced classes, Spring tries to locate a bean with the given type. +If more than one match exists, a new instance is created and can use the regular +bean lifecycle callbacks, such as dependency injection. + +Keys are generated by a `javax.cache.annotation.CacheKeyGenerator` that serves the +same purpose as Spring's `KeyGenerator`. By default, all method arguments are taken +into account, unless at least one parameter is annotated with `@CacheKey`. This is +similar to Spring's <>. For instance, the following are identical operations, one using +Spring's abstraction and the other using JCache: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Cacheable(cacheNames="books", key="#isbn") + public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) + + @CacheResult(cacheName="books") + public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse, boolean includeUsed) +---- + +You can also specify the `CacheKeyResolver` on the operation, similar to how you can +specify the `CacheResolverFactory`. + +JCache can manage exceptions thrown by annotated methods. This can prevent an update of +the cache, but it can also cache the exception as an indicator of the failure instead of +calling the method again. Assume that `InvalidIsbnNotFoundException` is thrown if the +structure of the ISBN is invalid. This is a permanent failure (no book could ever be +retrieved with such a parameter). The following caches the exception so that further +calls with the same, invalid, ISBN throw the cached exception directly instead of +invoking the method again: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @CacheResult(cacheName="books", exceptionCacheName="failures" + cachedExceptions = InvalidIsbnNotFoundException.class) + public Book findBook(ISBN isbn) +---- + + +=== Enabling JSR-107 Support + +You do not need to do anything specific to enable the JSR-107 support alongside Spring's +declarative annotation support. Both `@EnableCaching` and the `cache:annotation-driven` +XML element automatically enable the JCache support if both the JSR-107 API and the +`spring-context-support` module are present in the classpath. + +NOTE: Depending on your use case, the choice is basically yours. You can even mix and +match services by using the JSR-107 API on some and using Spring's own annotations on +others. However, if these services impact the same caches, you should use a consistent +and identical key generation implementation. + + + +[[cache-declarative-xml]] +== Declarative XML-based Caching + +If annotations are not an option (perhaps due to having no access to the sources +or no external code), you can use XML for declarative caching. So, instead of +annotating the methods for caching, you can specify the target method and the +caching directives externally (similar to the declarative transaction management +<>). The example +from the previous section can be translated into the following example: + +[source,xml,indent=0] +[subs="verbatim"] +---- + + + + + + + + + + + + + + + + + +---- + +In the preceding configuration, the `bookService` is made cacheable. The caching semantics +to apply are encapsulated in the `cache:advice` definition, which causes the `findBooks` +method to be used for putting data into the cache and the `loadBooks` method for evicting +data. Both definitions work against the `books` cache. + +The `aop:config` definition applies the cache advice to the appropriate points in the +program by using the AspectJ pointcut expression (more information is available in +<>). In the preceding example, +all methods from the `BookService` are considered and the cache advice is applied to them. + +The declarative XML caching supports all of the annotation-based model, so moving between +the two should be fairly easy. Furthermore, both can be used inside the same application. +The XML-based approach does not touch the target code. However, it is inherently more +verbose. When dealing with classes that have overloaded methods that are targeted for +caching, identifying the proper methods does take an extra effort, since the `method` +argument is not a good discriminator. In these cases, you can use the AspectJ pointcut +to cherry pick the target methods and apply the appropriate caching functionality. +However, through XML, it is easier to apply package or group or interface-wide caching +(again, due to the AspectJ pointcut) and to create template-like definitions (as we did +in the preceding example by defining the target cache through the `cache:definitions` +`cache` attribute). + + + +[[cache-store-configuration]] +== Configuring the Cache Storage + +The cache abstraction provides several storage integration options. To use them, you need +to declare an appropriate `CacheManager` (an entity that controls and manages `Cache` +instances and that can be used to retrieve these for storage). + + +[[cache-store-configuration-jdk]] +=== JDK `ConcurrentMap`-based Cache + +The JDK-based `Cache` implementation resides under +`org.springframework.cache.concurrent` package. It lets you use `ConcurrentHashMap` +as a backing `Cache` store. The following example shows how to configure two caches: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + +---- + +The preceding snippet uses the `SimpleCacheManager` to create a `CacheManager` for the +two nested `ConcurrentMapCache` instances named `default` and `books`. Note that the +names are configured directly for each cache. + +As the cache is created by the application, it is bound to its lifecycle, making it +suitable for basic use cases, tests, or simple applications. The cache scales well +and is very fast, but it does not provide any management, persistence capabilities, +or eviction contracts. + + +[[cache-store-configuration-eviction]] +=== Ehcache-based Cache + +Ehcache 3.x is fully JSR-107 compliant and no dedicated support is required for it. See +<> for details. + + +[[cache-store-configuration-caffeine]] +=== Caffeine Cache + +Caffeine is a Java 8 rewrite of Guava's cache, and its implementation is located in the +`org.springframework.cache.caffeine` package and provides access to several features +of Caffeine. + +The following example configures a `CacheManager` that creates the cache on demand: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + +You can also provide the caches to use explicitly. In that case, only those +are made available by the manager. The following example shows how to do so: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + default + books + + + +---- + +The Caffeine `CacheManager` also supports custom `Caffeine` and `CacheLoader`. +See the https://github.com/ben-manes/caffeine/wiki[Caffeine documentation] +for more information about those. + + +[[cache-store-configuration-gemfire]] +=== GemFire-based Cache + +GemFire is a memory-oriented, disk-backed, elastically scalable, continuously available, +active (with built-in pattern-based subscription notifications), globally replicated +database and provides fully-featured edge caching. For further information on how to +use GemFire as a `CacheManager` (and more), see the +{docs-spring-gemfire}/html/[Spring Data GemFire reference documentation]. + + +[[cache-store-configuration-jsr107]] +=== JSR-107 Cache + +Spring's caching abstraction can also use JSR-107-compliant caches. The JCache +implementation is located in the `org.springframework.cache.jcache` package. + +Again, to use it, you need to declare the appropriate `CacheManager`. +The following example shows how to do so: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + +---- + + +[[cache-store-configuration-noop]] +=== Dealing with Caches without a Backing Store + +Sometimes, when switching environments or doing testing, you might have cache +declarations without having an actual backing cache configured. As this is an invalid +configuration, an exception is thrown at runtime, since the caching infrastructure +is unable to find a suitable store. In situations like this, rather than removing the +cache declarations (which can prove tedious), you can wire in a simple dummy cache that +performs no caching -- that is, it forces the cached methods to be invoked every time. +The following example shows how to do so: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + +---- + +The `CompositeCacheManager` in the preceding chains multiple `CacheManager` instances and, +through the `fallbackToNoOpCache` flag, adds a no-op cache for all the definitions not +handled by the configured cache managers. That is, every cache definition not found in +either `jdkCache` or `gemfireCache` (configured earlier in the example) is handled by +the no-op cache, which does not store any information, causing the target method to be +invoked every time. + + + +[[cache-plug]] +== Plugging-in Different Back-end Caches + +Clearly, there are plenty of caching products out there that you can use as a backing +store. For those that do not support JSR-107 you need to provide a `CacheManager` and a +`Cache` implementation. This may sound harder than it is, since, in practice, the classes +tend to be simple https://en.wikipedia.org/wiki/Adapter_pattern[adapters] that map the +caching abstraction framework on top of the storage API, as the _Caffeine_ classes do. +Most `CacheManager` classes can use the classes in the +`org.springframework.cache.support` package (such as `AbstractCacheManager` which takes +care of the boiler-plate code, leaving only the actual mapping to be completed). + + + +[[cache-specific-config]] +== How can I Set the TTL/TTI/Eviction policy/XXX feature? + +Directly through your cache provider. The cache abstraction is an abstraction, +not a cache implementation. The solution you use might support various data +policies and different topologies that other solutions do not support (for example, +the JDK `ConcurrentHashMap` -- exposing that in the cache abstraction would be useless +because there would no backing support). Such functionality should be controlled +directly through the backing cache (when configuring it) or through its native API. + diff --git a/framework-docs/src/docs/asciidoc/integration/email.adoc b/framework-docs/src/docs/asciidoc/integration/email.adoc new file mode 100644 index 000000000000..fd82a39febfa --- /dev/null +++ b/framework-docs/src/docs/asciidoc/integration/email.adoc @@ -0,0 +1,303 @@ +[[mail]] += Email + +This section describes how to send email with the Spring Framework. + +.Library dependencies +**** +The following JAR needs to be on the classpath of your application in order to use the +Spring Framework's email support: + +* The https://jakartaee.github.io/mail-api/[Jakarta Mail] library + +This library is freely available on the web -- for example, in Maven Central as +`com.sun.mail:jakarta.mail`. Please make sure to use the latest 2.x version (which uses +the `jakarta.mail` package namespace) rather than Jakarta Mail 1.6.x (which uses the +`javax.mail` package namespace). +**** + +The Spring Framework provides a helpful utility library for sending email that shields +you from the specifics of the underlying mailing system and is responsible for +low-level resource handling on behalf of the client. + +The `org.springframework.mail` package is the root level package for the Spring +Framework's email support. The central interface for sending emails is the `MailSender` +interface. A simple value object that encapsulates the properties of a simple mail such +as `from` and `to` (plus many others) is the `SimpleMailMessage` class. This package +also contains a hierarchy of checked exceptions that provide a higher level of +abstraction over the lower level mail system exceptions, with the root exception being +`MailException`. See the {api-spring-framework}/mail/MailException.html[javadoc] +for more information on the rich mail exception hierarchy. + +The `org.springframework.mail.javamail.JavaMailSender` interface adds specialized +JavaMail features, such as MIME message support to the `MailSender` interface +(from which it inherits). `JavaMailSender` also provides a callback interface called +`org.springframework.mail.javamail.MimeMessagePreparator` for preparing a `MimeMessage`. + + + +[[mail-usage]] +== Usage + +Assume that we have a business interface called `OrderManager`, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public interface OrderManager { + + void placeOrder(Order order); + + } +---- + +Further assume that we have a requirement stating that an email message with an +order number needs to be generated and sent to a customer who placed the relevant order. + + +[[mail-usage-simple]] +=== Basic `MailSender` and `SimpleMailMessage` Usage + +The following example shows how to use `MailSender` and `SimpleMailMessage` to send an +email when someone places an order: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + import org.springframework.mail.MailException; + import org.springframework.mail.MailSender; + import org.springframework.mail.SimpleMailMessage; + + public class SimpleOrderManager implements OrderManager { + + private MailSender mailSender; + private SimpleMailMessage templateMessage; + + public void setMailSender(MailSender mailSender) { + this.mailSender = mailSender; + } + + public void setTemplateMessage(SimpleMailMessage templateMessage) { + this.templateMessage = templateMessage; + } + + public void placeOrder(Order order) { + + // Do the business calculations... + + // Call the collaborators to persist the order... + + // Create a thread safe "copy" of the template message and customize it + SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage); + msg.setTo(order.getCustomer().getEmailAddress()); + msg.setText( + "Dear " + order.getCustomer().getFirstName() + + order.getCustomer().getLastName() + + ", thank you for placing order. Your order number is " + + order.getOrderNumber()); + try { + this.mailSender.send(msg); + } + catch (MailException ex) { + // simply log it and go on... + System.err.println(ex.getMessage()); + } + } + + } +---- + +The following example shows the bean definitions for the preceding code: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + +---- + + +[[mail-usage-mime]] +=== Using `JavaMailSender` and `MimeMessagePreparator` + +This section describes another implementation of `OrderManager` that uses the `MimeMessagePreparator` +callback interface. In the following example, the `mailSender` property is of type +`JavaMailSender` so that we are able to use the JavaMail `MimeMessage` class: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + import jakarta.mail.Message; + import jakarta.mail.MessagingException; + import jakarta.mail.internet.InternetAddress; + import jakarta.mail.internet.MimeMessage; + + import jakarta.mail.internet.MimeMessage; + import org.springframework.mail.MailException; + import org.springframework.mail.javamail.JavaMailSender; + import org.springframework.mail.javamail.MimeMessagePreparator; + + public class SimpleOrderManager implements OrderManager { + + private JavaMailSender mailSender; + + public void setMailSender(JavaMailSender mailSender) { + this.mailSender = mailSender; + } + + public void placeOrder(final Order order) { + // Do the business calculations... + // Call the collaborators to persist the order... + + MimeMessagePreparator preparator = new MimeMessagePreparator() { + public void prepare(MimeMessage mimeMessage) throws Exception { + mimeMessage.setRecipient(Message.RecipientType.TO, + new InternetAddress(order.getCustomer().getEmailAddress())); + mimeMessage.setFrom(new InternetAddress("mail@mycompany.example")); + mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " + + order.getCustomer().getLastName() + ", thanks for your order. " + + "Your order number is " + order.getOrderNumber() + "."); + } + }; + + try { + this.mailSender.send(preparator); + } + catch (MailException ex) { + // simply log it and go on... + System.err.println(ex.getMessage()); + } + } + + } +---- + +NOTE: The mail code is a crosscutting concern and could well be a candidate for +refactoring into a <>, which could then +be run at appropriate joinpoints on the `OrderManager` target. + +The Spring Framework's mail support ships with the standard JavaMail implementation. +See the relevant javadoc for more information. + + + +[[mail-javamail-mime]] +== Using the JavaMail `MimeMessageHelper` + +A class that comes in pretty handy when dealing with JavaMail messages is +`org.springframework.mail.javamail.MimeMessageHelper`, which shields you from +having to use the verbose JavaMail API. Using the `MimeMessageHelper`, it is +pretty easy to create a `MimeMessage`, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + // of course you would use DI in any real-world cases + JavaMailSenderImpl sender = new JavaMailSenderImpl(); + sender.setHost("mail.host.com"); + + MimeMessage message = sender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message); + helper.setTo("test@host.com"); + helper.setText("Thank you for ordering!"); + + sender.send(message); +---- + + +[[mail-javamail-mime-attachments]] +=== Sending Attachments and Inline Resources + +Multipart email messages allow for both attachments and inline resources. Examples of +inline resources include an image or a stylesheet that you want to use in your message but +that you do not want displayed as an attachment. + +[[mail-javamail-mime-attachments-attachment]] +==== Attachments + +The following example shows you how to use the `MimeMessageHelper` to send an email +with a single JPEG image attachment: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + JavaMailSenderImpl sender = new JavaMailSenderImpl(); + sender.setHost("mail.host.com"); + + MimeMessage message = sender.createMimeMessage(); + + // use the true flag to indicate you need a multipart message + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setTo("test@host.com"); + + helper.setText("Check out this image!"); + + // let's attach the infamous windows Sample file (this time copied to c:/) + FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg")); + helper.addAttachment("CoolImage.jpg", file); + + sender.send(message); +---- + +[[mail-javamail-mime-attachments-inline]] +==== Inline Resources + +The following example shows you how to use the `MimeMessageHelper` to send an email +with an inline image: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + JavaMailSenderImpl sender = new JavaMailSenderImpl(); + sender.setHost("mail.host.com"); + + MimeMessage message = sender.createMimeMessage(); + + // use the true flag to indicate you need a multipart message + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setTo("test@host.com"); + + // use the true flag to indicate the text included is HTML + helper.setText("", true); + + // let's include the infamous windows Sample file (this time copied to c:/) + FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg")); + helper.addInline("identifier1234", res); + + sender.send(message); +---- + +WARNING: Inline resources are added to the `MimeMessage` by using the specified `Content-ID` +(`identifier1234` in the above example). The order in which you add the text +and the resource are very important. Be sure to first add the text and then +the resources. If you are doing it the other way around, it does not work. + + +[[mail-templates]] +=== Creating Email Content by Using a Templating Library + +The code in the examples shown in the previous sections explicitly created the content of the email message, +by using methods calls such as `message.setText(..)`. This is fine for simple cases, and it +is okay in the context of the aforementioned examples, where the intent was to show you +the very basics of the API. + +In your typical enterprise application, though, developers often do not create the content +of email messages by using the previously shown approach for a number of reasons: + +* Creating HTML-based email content in Java code is tedious and error prone. +* There is no clear separation between display logic and business logic. +* Changing the display structure of the email content requires writing Java code, + recompiling, redeploying, and so on. + +Typically, the approach taken to address these issues is to use a template library (such +as FreeMarker) to define the display structure of email content. This leaves your code +tasked only with creating the data that is to be rendered in the email template and +sending the email. It is definitely a best practice when the content of your email messages +becomes even moderately complex, and, with the Spring Framework's support classes for +FreeMarker, it becomes quite easy to do. + diff --git a/framework-docs/src/docs/asciidoc/integration/integration-appendix.adoc b/framework-docs/src/docs/asciidoc/integration/integration-appendix.adoc index 4b71d758733d..e714522b6165 100644 --- a/framework-docs/src/docs/asciidoc/integration/integration-appendix.adoc +++ b/framework-docs/src/docs/asciidoc/integration/integration-appendix.adoc @@ -28,8 +28,10 @@ correct schema so that the elements in the `jee` namespace are available to you: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation=" - http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/jee https://www.springframework.org/schema/jee/spring-jee.xsd"> + http://www.springframework.org/schema/beans + https://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/jee + https://www.springframework.org/schema/jee/spring-jee.xsd"> @@ -287,8 +289,10 @@ are available to you: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jms="http://www.springframework.org/schema/jms" xsi:schemaLocation=" - http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/jms https://www.springframework.org/schema/jms/spring-jms.xsd"> + http://www.springframework.org/schema/beans + https://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/jms + https://www.springframework.org/schema/jms/spring-jms.xsd"> @@ -324,8 +328,10 @@ the correct schema so that the elements in the `cache` namespace are available t xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation=" - http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd - http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd"> + http://www.springframework.org/schema/beans + https://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/cache + https://www.springframework.org/schema/cache/spring-cache.xsd"> diff --git a/framework-docs/src/docs/asciidoc/integration/jms.adoc b/framework-docs/src/docs/asciidoc/integration/jms.adoc new file mode 100644 index 000000000000..d5336dea0e65 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/integration/jms.adoc @@ -0,0 +1,1474 @@ +[[jms]] += JMS (Java Message Service) + +Spring provides a JMS integration framework that simplifies the use of the JMS API in much +the same way as Spring's integration does for the JDBC API. + +JMS can be roughly divided into two areas of functionality, namely the production and +consumption of messages. The `JmsTemplate` class is used for message production and +synchronous message reception. For asynchronous reception similar to Jakarta EE's +message-driven bean style, Spring provides a number of message-listener containers that +you can use to create Message-Driven POJOs (MDPs). Spring also provides a declarative way +to create message listeners. + +The `org.springframework.jms.core` package provides the core functionality for using +JMS. It contains JMS template classes that simplify the use of the JMS by handling the +creation and release of resources, much like the `JdbcTemplate` does for JDBC. The +design principle common to Spring template classes is to provide helper methods to +perform common operations and, for more sophisticated usage, delegate the essence of the +processing task to user-implemented callback interfaces. The JMS template follows the +same design. The classes offer various convenience methods for sending messages, +consuming messages synchronously, and exposing the JMS session and message producer to +the user. + +The `org.springframework.jms.support` package provides `JMSException` translation +functionality. The translation converts the checked `JMSException` hierarchy to a +mirrored hierarchy of unchecked exceptions. If any provider-specific subclasses +of the checked `jakarta.jms.JMSException` exist, this exception is wrapped in the +unchecked `UncategorizedJmsException`. + +The `org.springframework.jms.support.converter` package provides a `MessageConverter` +abstraction to convert between Java objects and JMS messages. + +The `org.springframework.jms.support.destination` package provides various strategies +for managing JMS destinations, such as providing a service locator for destinations +stored in JNDI. + +The `org.springframework.jms.annotation` package provides the necessary infrastructure +to support annotation-driven listener endpoints by using `@JmsListener`. + +The `org.springframework.jms.config` package provides the parser implementation for the +`jms` namespace as well as the java config support to configure listener containers and +create listener endpoints. + +Finally, the `org.springframework.jms.connection` package provides an implementation of +the `ConnectionFactory` suitable for use in standalone applications. It also contains an +implementation of Spring's `PlatformTransactionManager` for JMS (the cunningly named +`JmsTransactionManager`). This allows for seamless integration of JMS as a transactional +resource into Spring's transaction management mechanisms. + +[NOTE] +==== +As of Spring Framework 5, Spring's JMS package fully supports JMS 2.0 and requires the +JMS 2.0 API to be present at runtime. We recommend the use of a JMS 2.0 compatible provider. + +If you happen to use an older message broker in your system, you may try upgrading to a +JMS 2.0 compatible driver for your existing broker generation. Alternatively, you may also +try to run against a JMS 1.1 based driver, simply putting the JMS 2.0 API jar on the +classpath but only using JMS 1.1 compatible API against your driver. Spring's JMS support +adheres to JMS 1.1 conventions by default, so with corresponding configuration it does +support such a scenario. However, please consider this for transition scenarios only. +==== + + + +[[jms-using]] +== Using Spring JMS + +This section describes how to use Spring's JMS components. + + +[[jms-jmstemplate]] +=== Using `JmsTemplate` + +The `JmsTemplate` class is the central class in the JMS core package. It simplifies the +use of JMS, since it handles the creation and release of resources when sending or +synchronously receiving messages. + +Code that uses the `JmsTemplate` needs only to implement callback interfaces that give them +a clearly defined high-level contract. The `MessageCreator` callback interface creates a +message when given a `Session` provided by the calling code in `JmsTemplate`. To +allow for more complex usage of the JMS API, `SessionCallback` provides the +JMS session, and `ProducerCallback` exposes a `Session` and +`MessageProducer` pair. + +The JMS API exposes two types of send methods, one that takes delivery mode, priority, +and time-to-live as Quality of Service (QOS) parameters and one that takes no QOS +parameters and uses default values. Since `JmsTemplate` has many send methods, +setting the QOS parameters have been exposed as bean properties to +avoid duplication in the number of send methods. Similarly, the timeout value for +synchronous receive calls is set by using the `setReceiveTimeout` property. + +Some JMS providers allow the setting of default QOS values administratively through the +configuration of the `ConnectionFactory`. This has the effect that a call to a +`MessageProducer` instance's `send` method (`send(Destination destination, Message message)`) +uses different QOS default values than those specified in the JMS specification. In order +to provide consistent management of QOS values, the `JmsTemplate` must, therefore, be +specifically enabled to use its own QOS values by setting the boolean property +`isExplicitQosEnabled` to `true`. + +For convenience, `JmsTemplate` also exposes a basic request-reply operation that allows +for sending a message and waiting for a reply on a temporary queue that is created as part of +the operation. + +IMPORTANT: Instances of the `JmsTemplate` class are thread-safe, once configured. This is +important, because it means that you can configure a single instance of a `JmsTemplate` +and then safely inject this shared reference into multiple collaborators. To be +clear, the `JmsTemplate` is stateful, in that it maintains a reference to a +`ConnectionFactory`, but this state is not conversational state. + +As of Spring Framework 4.1, `JmsMessagingTemplate` is built on top of `JmsTemplate` +and provides an integration with the messaging abstraction -- that is, +`org.springframework.messaging.Message`. This lets you create the message to +send in a generic manner. + + +[[jms-connections]] +=== Connections + +The `JmsTemplate` requires a reference to a `ConnectionFactory`. The `ConnectionFactory` +is part of the JMS specification and serves as the entry point for working with JMS. It +is used by the client application as a factory to create connections with the JMS +provider and encapsulates various configuration parameters, many of which are +vendor-specific, such as SSL configuration options. + +When using JMS inside an EJB, the vendor provides implementations of the JMS interfaces +so that they can participate in declarative transaction management and perform pooling +of connections and sessions. In order to use this implementation, Jakarta EE containers +typically require that you declare a JMS connection factory as a `resource-ref` inside +the EJB or servlet deployment descriptors. To ensure the use of these features with the +`JmsTemplate` inside an EJB, the client application should ensure that it references the +managed implementation of the `ConnectionFactory`. + +[[jms-caching-resources]] +==== Caching Messaging Resources + +The standard API involves creating many intermediate objects. To send a message, the +following 'API' walk is performed: + +[literal] +[subs="verbatim,quotes"] +---- +ConnectionFactory->Connection->Session->MessageProducer->send +---- + +Between the `ConnectionFactory` and the `Send` operation, three intermediate +objects are created and destroyed. To optimize the resource usage and increase +performance, Spring provides two implementations of `ConnectionFactory`. + +[[jms-connection-factory]] +==== Using `SingleConnectionFactory` + +Spring provides an implementation of the `ConnectionFactory` interface, +`SingleConnectionFactory`, that returns the same `Connection` on all +`createConnection()` calls and ignores calls to `close()`. This is useful for testing and +standalone environments so that the same connection can be used for multiple +`JmsTemplate` calls that may span any number of transactions. `SingleConnectionFactory` +takes a reference to a standard `ConnectionFactory` that would typically come from JNDI. + +[[jdbc-connection-factory-caching]] +==== Using `CachingConnectionFactory` + +The `CachingConnectionFactory` extends the functionality of `SingleConnectionFactory` +and adds the caching of `Session`, `MessageProducer`, and `MessageConsumer` instances. +The initial cache size is set to `1`. You can use the `sessionCacheSize` property to +increase the number of cached sessions. Note that the number of actual cached sessions +is more than that number, as sessions are cached based on their acknowledgment mode, +so there can be up to four cached session instances (one for each acknowledgment mode) +when `sessionCacheSize` is set to one. `MessageProducer` and `MessageConsumer` instances +are cached within their owning session and also take into account the unique properties +of the producers and consumers when caching. MessageProducers are cached based on their +destination. MessageConsumers are cached based on a key composed of the destination, selector, +noLocal delivery flag, and the durable subscription name (if creating durable consumers). + +[NOTE] +==== +MessageProducers and MessageConsumers for temporary queues and topics +(TemporaryQueue/TemporaryTopic) will never be cached. Unfortunately, WebLogic JMS happens +to implement the temporary queue/topic interfaces on its regular destination implementation, +mis-indicating that none of its destinations can be cached. Please use a different connection +pool/cache on WebLogic, or customize `CachingConnectionFactory` for WebLogic purposes. +==== + + +[[jms-destinations]] +=== Destination Management + +Destinations, as `ConnectionFactory` instances, are JMS administered objects that you can store +and retrieve in JNDI. When configuring a Spring application context, you can use the +JNDI `JndiObjectFactoryBean` factory class or `` to perform dependency +injection on your object's references to JMS destinations. However, this strategy +is often cumbersome if there are a large number of destinations in the application or if there +are advanced destination management features unique to the JMS provider. Examples of +such advanced destination management include the creation of dynamic destinations or +support for a hierarchical namespace of destinations. The `JmsTemplate` delegates the +resolution of a destination name to a JMS destination object that implements the +`DestinationResolver` interface. `DynamicDestinationResolver` is the default +implementation used by `JmsTemplate` and accommodates resolving dynamic destinations. A +`JndiDestinationResolver` is also provided to act as a service locator for +destinations contained in JNDI and optionally falls back to the behavior contained in +`DynamicDestinationResolver`. + +Quite often, the destinations used in a JMS application are only known at runtime and, +therefore, cannot be administratively created when the application is deployed. This is +often because there is shared application logic between interacting system components +that create destinations at runtime according to a well-known naming convention. Even +though the creation of dynamic destinations is not part of the JMS specification, most +vendors have provided this functionality. Dynamic destinations are created with a user-defined name, +which differentiates them from temporary destinations, and are often +not registered in JNDI. The API used to create dynamic destinations varies from provider +to provider since the properties associated with the destination are vendor-specific. +However, a simple implementation choice that is sometimes made by vendors is to +disregard the warnings in the JMS specification and to use the method `TopicSession` +`createTopic(String topicName)` or the `QueueSession` `createQueue(String +queueName)` method to create a new destination with default destination properties. Depending +on the vendor implementation, `DynamicDestinationResolver` can then also create a +physical destination instead of only resolving one. + +The boolean property `pubSubDomain` is used to configure the `JmsTemplate` with +knowledge of what JMS domain is being used. By default, the value of this property is +false, indicating that the point-to-point domain, `Queues`, is to be used. This property +(used by `JmsTemplate`) determines the behavior of dynamic destination resolution through +implementations of the `DestinationResolver` interface. + +You can also configure the `JmsTemplate` with a default destination through the +property `defaultDestination`. The default destination is with send and receive +operations that do not refer to a specific destination. + + +[[jms-mdp]] +=== Message Listener Containers + +One of the most common uses of JMS messages in the EJB world is to drive message-driven +beans (MDBs). Spring offers a solution to create message-driven POJOs (MDPs) in a way +that does not tie a user to an EJB container. (See <> for detailed +coverage of Spring's MDP support.) Since Spring Framework 4.1, endpoint methods can be +annotated with `@JmsListener` -- see <> for more details. + +A message listener container is used to receive messages from a JMS message queue and +drive the `MessageListener` that is injected into it. The listener container is +responsible for all threading of message reception and dispatches into the listener for +processing. A message listener container is the intermediary between an MDP and a +messaging provider and takes care of registering to receive messages, participating in +transactions, resource acquisition and release, exception conversion, and so on. This +lets you write the (possibly complex) business logic +associated with receiving a message (and possibly respond to it), and delegates +boilerplate JMS infrastructure concerns to the framework. + +There are two standard JMS message listener containers packaged with Spring, each with +its specialized feature set. + +* <> +* <> + +[[jms-mdp-simple]] +==== Using `SimpleMessageListenerContainer` + +This message listener container is the simpler of the two standard flavors. It creates +a fixed number of JMS sessions and consumers at startup, registers the listener by using +the standard JMS `MessageConsumer.setMessageListener()` method, and leaves it up the JMS +provider to perform listener callbacks. This variant does not allow for dynamic adaption +to runtime demands or for participation in externally managed transactions. +Compatibility-wise, it stays very close to the spirit of the standalone JMS +specification, but is generally not compatible with Jakarta EE's JMS restrictions. + +NOTE: While `SimpleMessageListenerContainer` does not allow for participation in externally +managed transactions, it does support native JMS transactions. To enable this feature, +you can switch the `sessionTransacted` flag to `true` or, in the XML namespace, set the +`acknowledge` attribute to `transacted`. Exceptions thrown from your listener then lead +to a rollback, with the message getting redelivered. Alternatively, consider using +`CLIENT_ACKNOWLEDGE` mode, which provides redelivery in case of an exception as well but +does not use transacted `Session` instances and, therefore, does not include any other +`Session` operations (such as sending response messages) in the transaction protocol. + +IMPORTANT: The default `AUTO_ACKNOWLEDGE` mode does not provide proper reliability guarantees. +Messages can get lost when listener execution fails (since the provider automatically +acknowledges each message after listener invocation, with no exceptions to be propagated to +the provider) or when the listener container shuts down (you can configure this by setting +the `acceptMessagesWhileStopping` flag). Make sure to use transacted sessions in case of +reliability needs (for example, for reliable queue handling and durable topic subscriptions). + +[[jms-mdp-default]] +==== Using `DefaultMessageListenerContainer` + +This message listener container is used in most cases. In contrast to +`SimpleMessageListenerContainer`, this container variant allows for dynamic adaptation +to runtime demands and is able to participate in externally managed transactions. +Each received message is registered with an XA transaction when configured with a +`JtaTransactionManager`. As a result, processing may take advantage of XA transaction +semantics. This listener container strikes a good balance between low requirements on +the JMS provider, advanced functionality (such as participation in externally managed +transactions), and compatibility with Jakarta EE environments. + +You can customize the cache level of the container. Note that, when no caching is enabled, +a new connection and a new session is created for each message reception. Combining this +with a non-durable subscription with high loads may lead to message loss. Make sure to +use a proper cache level in such a case. + +This container also has recoverable capabilities when the broker goes down. By default, +a simple `BackOff` implementation retries every five seconds. You can specify +a custom `BackOff` implementation for more fine-grained recovery options. See +{api-spring-framework}/util/backoff/ExponentialBackOff.html[`ExponentialBackOff`] for an example. + +NOTE: Like its sibling (<>), +`DefaultMessageListenerContainer` supports native JMS transactions and allows for +customizing the acknowledgment mode. If feasible for your scenario, This is strongly +recommended over externally managed transactions -- that is, if you can live with +occasional duplicate messages in case of the JVM dying. Custom duplicate message +detection steps in your business logic can cover such situations -- for example, +in the form of a business entity existence check or a protocol table check. +Any such arrangements are significantly more efficient than the alternative: +wrapping your entire processing with an XA transaction (through configuring your +`DefaultMessageListenerContainer` with an `JtaTransactionManager`) to cover the +reception of the JMS message as well as the execution of the business logic in your +message listener (including database operations, etc.). + +IMPORTANT: The default `AUTO_ACKNOWLEDGE` mode does not provide proper reliability guarantees. +Messages can get lost when listener execution fails (since the provider automatically +acknowledges each message after listener invocation, with no exceptions to be propagated to +the provider) or when the listener container shuts down (you can configure this by setting +the `acceptMessagesWhileStopping` flag). Make sure to use transacted sessions in case of +reliability needs (for example, for reliable queue handling and durable topic subscriptions). + + +[[jms-tx]] +=== Transaction Management + +Spring provides a `JmsTransactionManager` that manages transactions for a single JMS +`ConnectionFactory`. This lets JMS applications leverage the managed-transaction +features of Spring, as described in +<>. +The `JmsTransactionManager` performs local resource transactions, binding a JMS +Connection/Session pair from the specified `ConnectionFactory` to the thread. +`JmsTemplate` automatically detects such transactional resources and operates +on them accordingly. + +In a Jakarta EE environment, the `ConnectionFactory` pools Connection and Session instances, +so those resources are efficiently reused across transactions. In a standalone environment, +using Spring's `SingleConnectionFactory` result in a shared JMS `Connection`, with +each transaction having its own independent `Session`. Alternatively, consider the use +of a provider-specific pooling adapter, such as ActiveMQ's `PooledConnectionFactory` +class. + +You can also use `JmsTemplate` with the `JtaTransactionManager` and an XA-capable JMS +`ConnectionFactory` to perform distributed transactions. Note that this requires the +use of a JTA transaction manager as well as a properly XA-configured ConnectionFactory. +(Check your Jakarta EE server's or JMS provider's documentation.) + +Reusing code across a managed and unmanaged transactional environment can be confusing +when using the JMS API to create a `Session` from a `Connection`. This is because the +JMS API has only one factory method to create a `Session`, and it requires values for the +transaction and acknowledgment modes. In a managed environment, setting these values is +the responsibility of the environment's transactional infrastructure, so these values +are ignored by the vendor's wrapper to the JMS Connection. When you use the `JmsTemplate` +in an unmanaged environment, you can specify these values through the use of the +properties `sessionTransacted` and `sessionAcknowledgeMode`. When you use a +`PlatformTransactionManager` with `JmsTemplate`, the template is always given a +transactional JMS `Session`. + + + +[[jms-sending]] +== Sending a Message + +The `JmsTemplate` contains many convenience methods to send a message. Send +methods specify the destination by using a `jakarta.jms.Destination` object, and others +specify the destination by using a `String` in a JNDI lookup. The `send` method +that takes no destination argument uses the default destination. + +The following example uses the `MessageCreator` callback to create a text message from the +supplied `Session` object: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + import jakarta.jms.ConnectionFactory; + import jakarta.jms.JMSException; + import jakarta.jms.Message; + import jakarta.jms.Queue; + import jakarta.jms.Session; + + import org.springframework.jms.core.MessageCreator; + import org.springframework.jms.core.JmsTemplate; + + public class JmsQueueSender { + + private JmsTemplate jmsTemplate; + private Queue queue; + + public void setConnectionFactory(ConnectionFactory cf) { + this.jmsTemplate = new JmsTemplate(cf); + } + + public void setQueue(Queue queue) { + this.queue = queue; + } + + public void simpleSend() { + this.jmsTemplate.send(this.queue, new MessageCreator() { + public Message createMessage(Session session) throws JMSException { + return session.createTextMessage("hello queue world"); + } + }); + } + } +---- + +In the preceding example, the `JmsTemplate` is constructed by passing a reference to a +`ConnectionFactory`. As an alternative, a zero-argument constructor and +`connectionFactory` is provided and can be used for constructing the instance in +JavaBean style (using a `BeanFactory` or plain Java code). Alternatively, consider +deriving from Spring's `JmsGatewaySupport` convenience base class, which provides +pre-built bean properties for JMS configuration. + +The `send(String destinationName, MessageCreator creator)` method lets you send a +message by using the string name of the destination. If these names are registered in JNDI, +you should set the `destinationResolver` property of the template to an instance of +`JndiDestinationResolver`. + +If you created the `JmsTemplate` and specified a default destination, the +`send(MessageCreator c)` sends a message to that destination. + + +[[jms-msg-conversion]] +=== Using Message Converters + +To facilitate the sending of domain model objects, the `JmsTemplate` has +various send methods that take a Java object as an argument for a message's data +content. The overloaded methods `convertAndSend()` and `receiveAndConvert()` methods in +`JmsTemplate` delegate the conversion process to an instance of the `MessageConverter` +interface. This interface defines a simple contract to convert between Java objects and +JMS messages. The default implementation (`SimpleMessageConverter`) supports conversion +between `String` and `TextMessage`, `byte[]` and `BytesMessage`, and `java.util.Map` +and `MapMessage`. By using the converter, you and your application code can focus on the +business object that is being sent or received through JMS and not be concerned with the +details of how it is represented as a JMS message. + +The sandbox currently includes a `MapMessageConverter`, which uses reflection to convert +between a JavaBean and a `MapMessage`. Other popular implementation choices you might +implement yourself are converters that use an existing XML marshalling package (such as +JAXB or XStream) to create a `TextMessage` that represents the object. + +To accommodate the setting of a message's properties, headers, and body that can not be +generically encapsulated inside a converter class, the `MessagePostProcessor` interface +gives you access to the message after it has been converted but before it is sent. The +following example shows how to modify a message header and a property after a +`java.util.Map` is converted to a message: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public void sendWithConversion() { + Map map = new HashMap(); + map.put("Name", "Mark"); + map.put("Age", new Integer(47)); + jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() { + public Message postProcessMessage(Message message) throws JMSException { + message.setIntProperty("AccountID", 1234); + message.setJMSCorrelationID("123-00001"); + return message; + } + }); + } +---- + +This results in a message of the following form: + +[literal] +[subs="verbatim,quotes"] +---- +MapMessage={ + Header={ + ... standard headers ... + CorrelationID={123-00001} + } + Properties={ + AccountID={Integer:1234} + } + Fields={ + Name={String:Mark} + Age={Integer:47} + } +} +---- + + +[[jms-callbacks]] +=== Using `SessionCallback` and `ProducerCallback` + +While the send operations cover many common usage scenarios, you might sometimes +want to perform multiple operations on a JMS `Session` or `MessageProducer`. The +`SessionCallback` and `ProducerCallback` expose the JMS `Session` and `Session` / +`MessageProducer` pair, respectively. The `execute()` methods on `JmsTemplate` run +these callback methods. + + + +[[jms-receiving]] +== Receiving a Message + +This describes how to receive messages with JMS in Spring. + + +[[jms-receiving-sync]] +=== Synchronous Reception + +While JMS is typically associated with asynchronous processing, you can +consume messages synchronously. The overloaded `receive(..)` methods provide this +functionality. During a synchronous receive, the calling thread blocks until a message +becomes available. This can be a dangerous operation, since the calling thread can +potentially be blocked indefinitely. The `receiveTimeout` property specifies how long +the receiver should wait before giving up waiting for a message. + + +[[jms-receiving-async]] +=== Asynchronous reception: Message-Driven POJOs + +NOTE: Spring also supports annotated-listener endpoints through the use of the `@JmsListener` +annotation and provides an open infrastructure to register endpoints programmatically. +This is, by far, the most convenient way to setup an asynchronous receiver. +See <> for more details. + +In a fashion similar to a Message-Driven Bean (MDB) in the EJB world, the Message-Driven +POJO (MDP) acts as a receiver for JMS messages. The one restriction (but see +<>) on an MDP is that it must implement +the `jakarta.jms.MessageListener` interface. Note that, if your POJO receives messages +on multiple threads, it is important to ensure that your implementation is thread-safe. + +The following example shows a simple implementation of an MDP: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + import jakarta.jms.JMSException; + import jakarta.jms.Message; + import jakarta.jms.MessageListener; + import jakarta.jms.TextMessage; + + public class ExampleListener implements MessageListener { + + public void onMessage(Message message) { + if (message instanceof TextMessage textMessage) { + try { + System.out.println(textMessage.getText()); + } + catch (JMSException ex) { + throw new RuntimeException(ex); + } + } + else { + throw new IllegalArgumentException("Message must be of type TextMessage"); + } + } + } +---- + +Once you have implemented your `MessageListener`, it is time to create a message listener +container. + +The following example shows how to define and configure one of the message listener +containers that ships with Spring (in this case, `DefaultMessageListenerContainer`): + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + +---- + +See the Spring javadoc of the various message listener containers (all of which implement +{api-spring-framework}/jms/listener/MessageListenerContainer.html[MessageListenerContainer]) +for a full description of the features supported by each implementation. + + +[[jms-receiving-async-session-aware-message-listener]] +=== Using the `SessionAwareMessageListener` Interface + +The `SessionAwareMessageListener` interface is a Spring-specific interface that provides +a similar contract to the JMS `MessageListener` interface but also gives the message-handling +method access to the JMS `Session` from which the `Message` was received. +The following listing shows the definition of the `SessionAwareMessageListener` interface: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + package org.springframework.jms.listener; + + public interface SessionAwareMessageListener { + + void onMessage(Message message, Session session) throws JMSException; + } +---- + +You can choose to have your MDPs implement this interface (in preference to the standard +JMS `MessageListener` interface) if you want your MDPs to be able to respond to any +received messages (by using the `Session` supplied in the `onMessage(Message, Session)` +method). All of the message listener container implementations that ship with Spring +have support for MDPs that implement either the `MessageListener` or +`SessionAwareMessageListener` interface. Classes that implement the +`SessionAwareMessageListener` come with the caveat that they are then tied to Spring +through the interface. The choice of whether or not to use it is left entirely up to you +as an application developer or architect. + +Note that the `onMessage(..)` method of the `SessionAwareMessageListener` +interface throws `JMSException`. In contrast to the standard JMS `MessageListener` +interface, when using the `SessionAwareMessageListener` interface, it is the +responsibility of the client code to handle any thrown exceptions. + + +[[jms-receiving-async-message-listener-adapter]] +=== Using `MessageListenerAdapter` + +The `MessageListenerAdapter` class is the final component in Spring's asynchronous +messaging support. In a nutshell, it lets you expose almost any class as an MDP +(though there are some constraints). + +Consider the following interface definition: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public interface MessageDelegate { + + void handleMessage(String message); + + void handleMessage(Map message); + + void handleMessage(byte[] message); + + void handleMessage(Serializable message); + } +---- + +Notice that, although the interface extends neither the `MessageListener` nor the +`SessionAwareMessageListener` interface, you can still use it as an MDP by using the +`MessageListenerAdapter` class. Notice also how the various message handling methods are +strongly typed according to the contents of the various `Message` types that they can +receive and handle. + +Now consider the following implementation of the `MessageDelegate` interface: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public class DefaultMessageDelegate implements MessageDelegate { + // implementation elided for clarity... + } +---- + +In particular, note how the preceding implementation of the `MessageDelegate` interface (the +`DefaultMessageDelegate` class) has no JMS dependencies at all. It truly is a +POJO that we can make into an MDP through the following configuration: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + +---- + +The next example shows another MDP that can handle only receiving JMS +`TextMessage` messages. Notice how the message handling method is actually called +`receive` (the name of the message handling method in a `MessageListenerAdapter` +defaults to `handleMessage`), but it is configurable (as you can see later in this section). Notice +also how the `receive(..)` method is strongly typed to receive and respond only to JMS +`TextMessage` messages. +The following listing shows the definition of the `TextMessageDelegate` interface: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public interface TextMessageDelegate { + + void receive(TextMessage message); + } +---- + +The following listing shows a class that implements the `TextMessageDelegate` interface: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public class DefaultTextMessageDelegate implements TextMessageDelegate { + // implementation elided for clarity... + } +---- + +The configuration of the attendant `MessageListenerAdapter` would then be as follows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + +---- + +Note that, if the `messageListener` receives a JMS `Message` of a type +other than `TextMessage`, an `IllegalStateException` is thrown (and subsequently +swallowed). Another of the capabilities of the `MessageListenerAdapter` class is the +ability to automatically send back a response `Message` if a handler method returns a +non-void value. Consider the following interface and class: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public interface ResponsiveTextMessageDelegate { + + // notice the return type... + String receive(TextMessage message); + } +---- + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate { + // implementation elided for clarity... + } +---- + +If you use the `DefaultResponsiveTextMessageDelegate` in conjunction with a +`MessageListenerAdapter`, any non-null value that is returned from the execution of +the `'receive(..)'` method is (in the default configuration) converted into a +`TextMessage`. The resulting `TextMessage` is then sent to the `Destination` (if +one exists) defined in the JMS `Reply-To` property of the original `Message` or the +default `Destination` set on the `MessageListenerAdapter` (if one has been configured). +If no `Destination` is found, an `InvalidDestinationException` is thrown +(note that this exception is not swallowed and propagates up the +call stack). + + +[[jms-tx-participation]] +=== Processing Messages Within Transactions + +Invoking a message listener within a transaction requires only reconfiguration of the +listener container. + +You can activate local resource transactions through the `sessionTransacted` flag +on the listener container definition. Each message listener invocation then operates +within an active JMS transaction, with message reception rolled back in case of listener +execution failure. Sending a response message (through `SessionAwareMessageListener`) is +part of the same local transaction, but any other resource operations (such as +database access) operate independently. This usually requires duplicate message +detection in the listener implementation, to cover the case where database processing +has committed but message processing failed to commit. + +Consider the following bean definition: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + +---- + +To participate in an externally managed transaction, you need to configure a +transaction manager and use a listener container that supports externally managed +transactions (typically, `DefaultMessageListenerContainer`). + +To configure a message listener container for XA transaction participation, you want +to configure a `JtaTransactionManager` (which, by default, delegates to the Jakarta EE +server's transaction subsystem). Note that the underlying JMS `ConnectionFactory` needs to +be XA-capable and properly registered with your JTA transaction coordinator. (Check your +Jakarta EE server's configuration of JNDI resources.) This lets message reception as well +as (for example) database access be part of the same transaction (with unified commit +semantics, at the expense of XA transaction log overhead). + +The following bean definition creates a transaction manager: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + +Then we need to add it to our earlier container configuration. The container +takes care of the rest. The following example shows how to do so: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + <1> + +---- +<1> Our transaction manager. + + + +[[jms-jca-message-endpoint-manager]] +== Support for JCA Message Endpoints + +Beginning with version 2.5, Spring also provides support for a JCA-based +`MessageListener` container. The `JmsMessageEndpointManager` tries to +automatically determine the `ActivationSpec` class name from the provider's +`ResourceAdapter` class name. Therefore, it is typically possible to provide +Spring's generic `JmsActivationSpecConfig`, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + +---- + +Alternatively, you can set up a `JmsMessageEndpointManager` with a given +`ActivationSpec` object. The `ActivationSpec` object may also come from a JNDI lookup +(using ``). The following example shows how to do so: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + +---- + +Using Spring's `ResourceAdapterFactoryBean`, you can configure the target `ResourceAdapter` +locally, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + +---- + +The specified `WorkManager` can also point to an environment-specific thread pool -- +typically through a `SimpleTaskWorkManager` instance's `asyncTaskExecutor` property. Consider +defining a shared thread pool for all your `ResourceAdapter` instances if you happen to +use multiple adapters. + +In some environments (such as WebLogic 9 or above), you can instead obtain the entire `ResourceAdapter` object +from JNDI (by using ``). The Spring-based message +listeners can then interact with the server-hosted `ResourceAdapter`, which also use the +server's built-in `WorkManager`. + +See the javadoc for {api-spring-framework}/jms/listener/endpoint/JmsMessageEndpointManager.html[`JmsMessageEndpointManager`], +{api-spring-framework}/jms/listener/endpoint/JmsActivationSpecConfig.html[`JmsActivationSpecConfig`], +and {api-spring-framework}/jca/support/ResourceAdapterFactoryBean.html[`ResourceAdapterFactoryBean`] +for more details. + +Spring also provides a generic JCA message endpoint manager that is not tied to JMS: +`org.springframework.jca.endpoint.GenericMessageEndpointManager`. This component allows +for using any message listener type (such as a JMS `MessageListener`) and any +provider-specific `ActivationSpec` object. See your JCA provider's documentation to +find out about the actual capabilities of your connector, and see the +{api-spring-framework}/jca/endpoint/GenericMessageEndpointManager.html[`GenericMessageEndpointManager`] +javadoc for the Spring-specific configuration details. + +NOTE: JCA-based message endpoint management is very analogous to EJB 2.1 Message-Driven Beans. +It uses the same underlying resource provider contract. As with EJB 2.1 MDBs, you can use any +message listener interface supported by your JCA provider in the Spring context as well. +Spring nevertheless provides explicit "`convenience`" support for JMS, because JMS is the +most common endpoint API used with the JCA endpoint management contract. + + + +[[jms-annotated]] +== Annotation-driven Listener Endpoints + +The easiest way to receive a message asynchronously is to use the annotated listener +endpoint infrastructure. In a nutshell, it lets you expose a method of a managed +bean as a JMS listener endpoint. The following example shows how to use it: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Component + public class MyService { + + @JmsListener(destination = "myDestination") + public void processOrder(String data) { ... } + } +---- + +The idea of the preceding example is that, whenever a message is available on the +`jakarta.jms.Destination` `myDestination`, the `processOrder` method is invoked +accordingly (in this case, with the content of the JMS message, similar to +what the <> +provides). + +The annotated endpoint infrastructure creates a message listener container +behind the scenes for each annotated method, by using a `JmsListenerContainerFactory`. +Such a container is not registered against the application context but can be easily +located for management purposes by using the `JmsListenerEndpointRegistry` bean. + +TIP: `@JmsListener` is a repeatable annotation on Java 8, so you can associate +several JMS destinations with the same method by adding additional `@JmsListener` +declarations to it. + + +[[jms-annotated-support]] +=== Enable Listener Endpoint Annotations + +To enable support for `@JmsListener` annotations, you can add `@EnableJms` to one of +your `@Configuration` classes, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Configuration + @EnableJms + public class AppConfig { + + @Bean + public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + factory.setConnectionFactory(connectionFactory()); + factory.setDestinationResolver(destinationResolver()); + factory.setSessionTransacted(true); + factory.setConcurrency("3-10"); + return factory; + } + } +---- + +By default, the infrastructure looks for a bean named `jmsListenerContainerFactory` +as the source for the factory to use to create message listener containers. In this +case (and ignoring the JMS infrastructure setup), you can invoke the `processOrder` +method with a core poll size of three threads and a maximum pool size of ten threads. + +You can customize the listener container factory to use for each annotation or you can +configure an explicit default by implementing the `JmsListenerConfigurer` interface. +The default is required only if at least one endpoint is registered without a specific +container factory. See the javadoc of classes that implement +{api-spring-framework}/jms/annotation/JmsListenerConfigurer.html[`JmsListenerConfigurer`] +for details and examples. + +If you prefer <>, you can use the `` +element, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + +---- + + +[[jms-annotated-programmatic-registration]] +=== Programmatic Endpoint Registration + +`JmsListenerEndpoint` provides a model of a JMS endpoint and is responsible for configuring +the container for that model. The infrastructure lets you programmatically configure endpoints +in addition to the ones that are detected by the `JmsListener` annotation. +The following example shows how to do so: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Configuration + @EnableJms + public class AppConfig implements JmsListenerConfigurer { + + @Override + public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) { + SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint(); + endpoint.setId("myJmsEndpoint"); + endpoint.setDestination("anotherQueue"); + endpoint.setMessageListener(message -> { + // processing + }); + registrar.registerEndpoint(endpoint); + } + } +---- + +In the preceding example, we used `SimpleJmsListenerEndpoint`, which provides the actual +`MessageListener` to invoke. However, you could also build your own endpoint variant +to describe a custom invocation mechanism. + +Note that you could skip the use of `@JmsListener` altogether +and programmatically register only your endpoints through `JmsListenerConfigurer`. + + +[[jms-annotated-method-signature]] +=== Annotated Endpoint Method Signature + +So far, we have been injecting a simple `String` in our endpoint, but it can actually +have a very flexible method signature. In the following example, we rewrite it to inject the `Order` with +a custom header: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Component + public class MyService { + + @JmsListener(destination = "myDestination") + public void processOrder(Order order, @Header("order_type") String orderType) { + ... + } + } +---- + +The main elements you can inject in JMS listener endpoints are as follows: + +* The raw `jakarta.jms.Message` or any of its subclasses (provided that it + matches the incoming message type). +* The `jakarta.jms.Session` for optional access to the native JMS API (for example, for sending + a custom reply). +* The `org.springframework.messaging.Message` that represents the incoming JMS message. + Note that this message holds both the custom and the standard headers (as defined + by `JmsHeaders`). +* `@Header`-annotated method arguments to extract a specific header value, including + standard JMS headers. +* A `@Headers`-annotated argument that must also be assignable to `java.util.Map` for + getting access to all headers. +* A non-annotated element that is not one of the supported types (`Message` or + `Session`) is considered to be the payload. You can make that explicit by annotating + the parameter with `@Payload`. You can also turn on validation by adding an extra + `@Valid`. + +The ability to inject Spring's `Message` abstraction is particularly useful to benefit +from all the information stored in the transport-specific message without relying on +transport-specific API. The following example shows how to do so: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @JmsListener(destination = "myDestination") + public void processOrder(Message order) { ... } +---- + +Handling of method arguments is provided by `DefaultMessageHandlerMethodFactory`, which you can +further customize to support additional method arguments. You can customize the conversion and validation +support there as well. + +For instance, if we want to make sure our `Order` is valid before processing it, we can +annotate the payload with `@Valid` and configure the necessary validator, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Configuration + @EnableJms + public class AppConfig implements JmsListenerConfigurer { + + @Override + public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) { + registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory()); + } + + @Bean + public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() { + DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); + factory.setValidator(myValidator()); + return factory; + } + } +---- + + +[[jms-annotated-response]] +=== Response Management + +The existing support in <> +already lets your method have a non-`void` return type. When that is the case, the result of +the invocation is encapsulated in a `jakarta.jms.Message`, sent either in the destination specified +in the `JMSReplyTo` header of the original message or in the default destination configured on +the listener. You can now set that default destination by using the `@SendTo` annotation of the +messaging abstraction. + +Assuming that our `processOrder` method should now return an `OrderStatus`, we can write it +to automatically send a response, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @JmsListener(destination = "myDestination") + @SendTo("status") + public OrderStatus processOrder(Order order) { + // order processing + return status; + } +---- + +TIP: If you have several `@JmsListener`-annotated methods, you can also place the `@SendTo` +annotation at the class level to share a default reply destination. + +If you need to set additional headers in a transport-independent manner, you can return a +`Message` instead, with a method similar to the following: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @JmsListener(destination = "myDestination") + @SendTo("status") + public Message processOrder(Order order) { + // order processing + return MessageBuilder + .withPayload(status) + .setHeader("code", 1234) + .build(); + } +---- + +If you need to compute the response destination at runtime, you can encapsulate your response +in a `JmsResponse` instance that also provides the destination to use at runtime. We can rewrite the previous +example as follows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @JmsListener(destination = "myDestination") + public JmsResponse> processOrder(Order order) { + // order processing + Message response = MessageBuilder + .withPayload(status) + .setHeader("code", 1234) + .build(); + return JmsResponse.forQueue(response, "status"); + } +---- + +Finally, if you need to specify some QoS values for the response such as the priority or +the time to live, you can configure the `JmsListenerContainerFactory` accordingly, +as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Configuration + @EnableJms + public class AppConfig { + + @Bean + public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + factory.setConnectionFactory(connectionFactory()); + QosSettings replyQosSettings = new QosSettings(); + replyQosSettings.setPriority(2); + replyQosSettings.setTimeToLive(10000); + factory.setReplyQosSettings(replyQosSettings); + return factory; + } + } +---- + + + +[[jms-namespace]] +== JMS Namespace Support + +Spring provides an XML namespace for simplifying JMS configuration. To use the JMS +namespace elements, you need to reference the JMS schema, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + https://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/jms + https://www.springframework.org/schema/jms/spring-jms.xsd"> + + + + +---- +<1> Referencing the JMS schema. + + +The namespace consists of three top-level elements: ``, `` +and ``. `` enables the use of <>. `` and `` +define shared listener container configuration and can contain `` child elements. +The following example shows a basic configuration for two listeners: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + +---- + +The preceding example is equivalent to creating two distinct listener container bean +definitions and two distinct `MessageListenerAdapter` bean definitions, as shown +in <>. In addition to the attributes shown +in the preceding example, the `listener` element can contain several optional ones. +The following table describes all of the available attributes: + +[[jms-namespace-listener-tbl]] +.Attributes of the JMS element +[cols="1,6"] +|=== +| Attribute | Description + +| `id` +| A bean name for the hosting listener container. If not specified, a bean name is + automatically generated. + +| `destination` (required) +| The destination name for this listener, resolved through the `DestinationResolver` + strategy. + +| `ref` (required) +| The bean name of the handler object. + +| `method` +| The name of the handler method to invoke. If the `ref` attribute points to a `MessageListener` + or Spring `SessionAwareMessageListener`, you can omit this attribute. + +| `response-destination` +| The name of the default response destination to which to send response messages. This is + applied in case of a request message that does not carry a `JMSReplyTo` field. The + type of this destination is determined by the listener-container's + `response-destination-type` attribute. Note that this applies only to a listener method with a + return value, for which each result object is converted into a response message. + +| `subscription` +| The name of the durable subscription, if any. + +| `selector` +| An optional message selector for this listener. + +| `concurrency` +| The number of concurrent sessions or consumers to start for this listener. This value can either be + a simple number indicating the maximum number (for example, `5`) or a range indicating the lower + as well as the upper limit (for example, `3-5`). Note that a specified minimum is only a hint + and might be ignored at runtime. The default is the value provided by the container. +|=== + +The `` element also accepts several optional attributes. This +allows for customization of the various strategies (for example, `taskExecutor` and +`destinationResolver`) as well as basic JMS settings and resource references. By using +these attributes, you can define highly-customized listener containers while +still benefiting from the convenience of the namespace. + +You can automatically expose such settings as a `JmsListenerContainerFactory` by +specifying the `id` of the bean to expose through the `factory-id` attribute, +as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + +---- + +The following table describes all available attributes. See the class-level javadoc +of the {api-spring-framework}/jms/listener/AbstractMessageListenerContainer.html[`AbstractMessageListenerContainer`] +and its concrete subclasses for more details on the individual properties. The javadoc +also provides a discussion of transaction choices and message redelivery scenarios. + +[[jms-namespace-listener-container-tbl]] +.Attributes of the JMS element +[cols="1,6"] +|=== +| Attribute | Description + +| `container-type` +| The type of this listener container. The available options are `default`, `simple`, + `default102`, or `simple102` (the default option is `default`). + +| `container-class` +| A custom listener container implementation class as a fully qualified class name. + The default is Spring's standard `DefaultMessageListenerContainer` or + `SimpleMessageListenerContainer`, according to the `container-type` attribute. + +| `factory-id` +| Exposes the settings defined by this element as a `JmsListenerContainerFactory` + with the specified `id` so that they can be reused with other endpoints. + +| `connection-factory` +| A reference to the JMS `ConnectionFactory` bean (the default bean name is + `connectionFactory`). + +| `task-executor` +| A reference to the Spring `TaskExecutor` for the JMS listener invokers. + +| `destination-resolver` +| A reference to the `DestinationResolver` strategy for resolving JMS `Destination` instances. + +| `message-converter` +| A reference to the `MessageConverter` strategy for converting JMS Messages to listener + method arguments. The default is a `SimpleMessageConverter`. + +| `error-handler` +| A reference to an `ErrorHandler` strategy for handling any uncaught exceptions that + may occur during the execution of the `MessageListener`. + +| `destination-type` +| The JMS destination type for this listener: `queue`, `topic`, `durableTopic`, `sharedTopic`, + or `sharedDurableTopic`. This potentially enables the `pubSubDomain`, `subscriptionDurable` + and `subscriptionShared` properties of the container. The default is `queue` (which disables + those three properties). + +| `response-destination-type` +| The JMS destination type for responses: `queue` or `topic`. The default is the value of the + `destination-type` attribute. + +| `client-id` +| The JMS client ID for this listener container. You must specify it when you use + durable subscriptions. + +| `cache` +| The cache level for JMS resources: `none`, `connection`, `session`, `consumer`, or + `auto`. By default (`auto`), the cache level is effectively `consumer`, unless + an external transaction manager has been specified -- in which case, the effective + default will be `none` (assuming Jakarta EE-style transaction management, where the given + ConnectionFactory is an XA-aware pool). + +| `acknowledge` +| The native JMS acknowledge mode: `auto`, `client`, `dups-ok`, or `transacted`. A value + of `transacted` activates a locally transacted `Session`. As an alternative, you can specify + the `transaction-manager` attribute, described later in table. The default is `auto`. + +| `transaction-manager` +| A reference to an external `PlatformTransactionManager` (typically an XA-based + transaction coordinator, such as Spring's `JtaTransactionManager`). If not specified, + native acknowledging is used (see the `acknowledge` attribute). + +| `concurrency` +| The number of concurrent sessions or consumers to start for each listener. It can either be + a simple number indicating the maximum number (for example, `5`) or a range indicating the + lower as well as the upper limit (for example, `3-5`). Note that a specified minimum is just a + hint and might be ignored at runtime. The default is `1`. You should keep concurrency limited to `1` in + case of a topic listener or if queue ordering is important. Consider raising it for + general queues. + +| `prefetch` +| The maximum number of messages to load into a single session. Note that raising this + number might lead to starvation of concurrent consumers. + +| `receive-timeout` +| The timeout (in milliseconds) to use for receive calls. The default is `1000` (one + second). `-1` indicates no timeout. + +| `back-off` +| Specifies the `BackOff` instance to use to compute the interval between recovery + attempts. If the `BackOffExecution` implementation returns `BackOffExecution#STOP`, + the listener container does not further try to recover. The `recovery-interval` + value is ignored when this property is set. The default is a `FixedBackOff` with + an interval of 5000 milliseconds (that is, five seconds). + +| `recovery-interval` +| Specifies the interval between recovery attempts, in milliseconds. It offers a convenient + way to create a `FixedBackOff` with the specified interval. For more recovery + options, consider specifying a `BackOff` instance instead. The default is 5000 milliseconds + (that is, five seconds). + +| `phase` +| The lifecycle phase within which this container should start and stop. The lower the + value, the earlier this container starts and the later it stops. The default is + `Integer.MAX_VALUE`, meaning that the container starts as late as possible and stops as + soon as possible. +|=== + +Configuring a JCA-based listener container with the `jms` schema support is very similar, +as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + +---- + +The following table describes the available configuration options for the JCA variant: + +[[jms-namespace-jca-listener-container-tbl]] +.Attributes of the JMS element +[cols="1,6"] +|=== +| Attribute | Description + +| `factory-id` +| Exposes the settings defined by this element as a `JmsListenerContainerFactory` + with the specified `id` so that they can be reused with other endpoints. + +| `resource-adapter` +| A reference to the JCA `ResourceAdapter` bean (the default bean name is + `resourceAdapter`). + +| `activation-spec-factory` +| A reference to the `JmsActivationSpecFactory`. The default is to autodetect the JMS + provider and its `ActivationSpec` class (see {api-spring-framework}/jms/listener/endpoint/DefaultJmsActivationSpecFactory.html[`DefaultJmsActivationSpecFactory`]). + +| `destination-resolver` +| A reference to the `DestinationResolver` strategy for resolving JMS `Destinations`. + +| `message-converter` +| A reference to the `MessageConverter` strategy for converting JMS Messages to listener + method arguments. The default is `SimpleMessageConverter`. + +| `destination-type` +| The JMS destination type for this listener: `queue`, `topic`, `durableTopic`, `sharedTopic`. + or `sharedDurableTopic`. This potentially enables the `pubSubDomain`, `subscriptionDurable`, + and `subscriptionShared` properties of the container. The default is `queue` (which disables + those three properties). + +| `response-destination-type` +| The JMS destination type for responses: `queue` or `topic`. The default is the value of the + `destination-type` attribute. + +| `client-id` +| The JMS client ID for this listener container. It needs to be specified when using + durable subscriptions. + +| `acknowledge` +| The native JMS acknowledge mode: `auto`, `client`, `dups-ok`, or `transacted`. A value + of `transacted` activates a locally transacted `Session`. As an alternative, you can specify + the `transaction-manager` attribute described later. The default is `auto`. + +| `transaction-manager` +| A reference to a Spring `JtaTransactionManager` or a + `jakarta.transaction.TransactionManager` for kicking off an XA transaction for each + incoming message. If not specified, native acknowledging is used (see the + `acknowledge` attribute). + +| `concurrency` +| The number of concurrent sessions or consumers to start for each listener. It can either be + a simple number indicating the maximum number (for example `5`) or a range indicating the + lower as well as the upper limit (for example, `3-5`). Note that a specified minimum is only a + hint and is typically ignored at runtime when you use a JCA listener container. + The default is 1. + +| `prefetch` +| The maximum number of messages to load into a single session. Note that raising this + number might lead to starvation of concurrent consumers. +|=== diff --git a/framework-docs/src/docs/asciidoc/integration/jmx.adoc b/framework-docs/src/docs/asciidoc/integration/jmx.adoc new file mode 100644 index 000000000000..31a27d567abf --- /dev/null +++ b/framework-docs/src/docs/asciidoc/integration/jmx.adoc @@ -0,0 +1,1375 @@ +[[jmx]] += JMX + +The JMX (Java Management Extensions) support in Spring provides features that let you +easily and transparently integrate your Spring application into a JMX infrastructure. + +.JMX? +**** +This chapter is not an introduction to JMX. It does not try to explain why you might want +to use JMX. If you are new to JMX, see <> at the end of this chapter. +**** + +Specifically, Spring's JMX support provides four core features: + +* The automatic registration of any Spring bean as a JMX MBean. +* A flexible mechanism for controlling the management interface of your beans. +* The declarative exposure of MBeans over remote, JSR-160 connectors. +* The simple proxying of both local and remote MBean resources. + +These features are designed to work without coupling your application components to +either Spring or JMX interfaces and classes. Indeed, for the most part, your application +classes need not be aware of either Spring or JMX in order to take advantage of the +Spring JMX features. + + + +[[jmx-exporting]] +== Exporting Your Beans to JMX + +The core class in Spring's JMX framework is the `MBeanExporter`. This class is +responsible for taking your Spring beans and registering them with a JMX `MBeanServer`. +For example, consider the following class: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + package org.springframework.jmx; + + public class JmxTestBean implements IJmxTestBean { + + private String name; + private int age; + private boolean isSuperman; + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public int add(int x, int y) { + return x + y; + } + + public void dontExposeMe() { + throw new RuntimeException(); + } + } +---- + +To expose the properties and methods of this bean as attributes and operations of an +MBean, you can configure an instance of the `MBeanExporter` class in your +configuration file and pass in the bean, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + +---- + +The pertinent bean definition from the preceding configuration snippet is the `exporter` +bean. The `beans` property tells the `MBeanExporter` exactly which of your beans must be +exported to the JMX `MBeanServer`. In the default configuration, the key of each entry +in the `beans` `Map` is used as the `ObjectName` for the bean referenced by the +corresponding entry value. You can change this behavior, as described in <>. + +With this configuration, the `testBean` bean is exposed as an MBean under the +`ObjectName` `bean:name=testBean1`. By default, all `public` properties of the bean +are exposed as attributes and all `public` methods (except those inherited from the +`Object` class) are exposed as operations. + +NOTE: `MBeanExporter` is a `Lifecycle` bean (see <>). By default, MBeans are exported as late as possible during +the application lifecycle. You can configure the `phase` at which +the export happens or disable automatic registration by setting the `autoStartup` flag. + + +[[jmx-exporting-mbeanserver]] +=== Creating an MBeanServer + +The configuration shown in the <> assumes that the +application is running in an environment that has one (and only one) `MBeanServer` +already running. In this case, Spring tries to locate the running `MBeanServer` and +register your beans with that server (if any). This behavior is useful when your +application runs inside a container (such as Tomcat or IBM WebSphere) that has its +own `MBeanServer`. + +However, this approach is of no use in a standalone environment or when running inside +a container that does not provide an `MBeanServer`. To address this, you can create an +`MBeanServer` instance declaratively by adding an instance of the +`org.springframework.jmx.support.MBeanServerFactoryBean` class to your configuration. +You can also ensure that a specific `MBeanServer` is used by setting the value of the +`MBeanExporter` instance's `server` property to the `MBeanServer` value returned by an +`MBeanServerFactoryBean`, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + + + + + + + +---- + +In the preceding example, an instance of `MBeanServer` is created by the `MBeanServerFactoryBean` and is +supplied to the `MBeanExporter` through the `server` property. When you supply your own +`MBeanServer` instance, the `MBeanExporter` does not try to locate a running +`MBeanServer` and uses the supplied `MBeanServer` instance. For this to work +correctly, you must have a JMX implementation on your classpath. + + +[[jmx-mbean-server]] +=== Reusing an Existing `MBeanServer` + +If no server is specified, the `MBeanExporter` tries to automatically detect a running +`MBeanServer`. This works in most environments, where only one `MBeanServer` instance is +used. However, when multiple instances exist, the exporter might pick the wrong server. +In such cases, you should use the `MBeanServer` `agentId` to indicate which instance to +be used, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + ... + + +---- + +For platforms or cases where the existing `MBeanServer` has a dynamic (or unknown) +`agentId` that is retrieved through lookup methods, you should use +<>, +as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + +---- + + +[[jmx-exporting-lazy]] +=== Lazily Initialized MBeans + +If you configure a bean with an `MBeanExporter` that is also configured for lazy +initialization, the `MBeanExporter` does not break this contract and avoids +instantiating the bean. Instead, it registers a proxy with the `MBeanServer` and +defers obtaining the bean from the container until the first invocation on the proxy +occurs. + + +[[jmx-exporting-auto]] +=== Automatic Registration of MBeans + +Any beans that are exported through the `MBeanExporter` and are already valid MBeans are +registered as-is with the `MBeanServer` without further intervention from Spring. You can cause MBeans +to be automatically detected by the `MBeanExporter` by setting the `autodetect` +property to `true`, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + +---- + +In the preceding example, the bean called `spring:mbean=true` is already a valid JMX MBean +and is automatically registered by Spring. By default, a bean that is autodetected for JMX +registration has its bean name used as the `ObjectName`. You can override this behavior, +as detailed in <>. + + +[[jmx-exporting-registration-behavior]] +=== Controlling the Registration Behavior + +Consider the scenario where a Spring `MBeanExporter` attempts to register an `MBean` +with an `MBeanServer` by using the `ObjectName` `bean:name=testBean1`. If an `MBean` +instance has already been registered under that same `ObjectName`, the default behavior +is to fail (and throw an `InstanceAlreadyExistsException`). + +You can control exactly what happens when an `MBean` is +registered with an `MBeanServer`. Spring's JMX support allows for three different +registration behaviors to control the registration behavior when the registration +process finds that an `MBean` has already been registered under the same `ObjectName`. +The following table summarizes these registration behaviors: + +[[jmx-registration-behaviors]] +.Registration Behaviors +[cols="1,4"] +|=== +| Registration behavior | Explanation + +| `FAIL_ON_EXISTING` +| This is the default registration behavior. If an `MBean` instance has already been + registered under the same `ObjectName`, the `MBean` that is being registered is not + registered, and an `InstanceAlreadyExistsException` is thrown. The existing + `MBean` is unaffected. + +| `IGNORE_EXISTING` +| If an `MBean` instance has already been registered under the same `ObjectName`, the + `MBean` that is being registered is not registered. The existing `MBean` is + unaffected, and no `Exception` is thrown. This is useful in settings where + multiple applications want to share a common `MBean` in a shared `MBeanServer`. + +| `REPLACE_EXISTING` +| If an `MBean` instance has already been registered under the same `ObjectName`, the + existing `MBean` that was previously registered is unregistered, and the new + `MBean` is registered in its place (the new `MBean` effectively replaces the + previous instance). +|=== + +The values in the preceding table are defined as enums on the `RegistrationPolicy` class. +If you want to change the default registration behavior, you need to set the value of the +`registrationPolicy` property on your `MBeanExporter` definition to one of those +values. + +The following example shows how to change from the default registration +behavior to the `REPLACE_EXISTING` behavior: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + + + + +---- + + + +[[jmx-interface]] +== Controlling the Management Interface of Your Beans + +In the example in the <>, +you had little control over the management interface of your bean. All of the `public` +properties and methods of each exported bean were exposed as JMX attributes and +operations, respectively. To exercise finer-grained control over exactly which +properties and methods of your exported beans are actually exposed as JMX attributes +and operations, Spring JMX provides a comprehensive and extensible mechanism for +controlling the management interfaces of your beans. + + +[[jmx-interface-assembler]] +=== Using the `MBeanInfoAssembler` Interface + +Behind the scenes, the `MBeanExporter` delegates to an implementation of the +`org.springframework.jmx.export.assembler.MBeanInfoAssembler` interface, which is +responsible for defining the management interface of each bean that is exposed. +The default implementation, +`org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler`, +defines a management interface that exposes all public properties and methods +(as you saw in the examples in the preceding sections). Spring provides two +additional implementations of the `MBeanInfoAssembler` interface that let you +control the generated management interface by using either source-level metadata +or any arbitrary interface. + + +[[jmx-interface-metadata]] +=== Using Source-level Metadata: Java Annotations + +By using the `MetadataMBeanInfoAssembler`, you can define the management interfaces +for your beans by using source-level metadata. The reading of metadata is encapsulated +by the `org.springframework.jmx.export.metadata.JmxAttributeSource` interface. +Spring JMX provides a default implementation that uses Java annotations, namely +`org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource`. +You must configure the `MetadataMBeanInfoAssembler` with an implementation instance of +the `JmxAttributeSource` interface for it to function correctly (there is no default). + +To mark a bean for export to JMX, you should annotate the bean class with the +`ManagedResource` annotation. You must mark each method you wish to expose as an operation +with the `ManagedOperation` annotation and mark each property you wish to expose +with the `ManagedAttribute` annotation. When marking properties, you can omit +either the annotation of the getter or the setter to create a write-only or read-only +attribute, respectively. + +NOTE: A `ManagedResource`-annotated bean must be public, as must the methods exposing +an operation or an attribute. + +The following example shows the annotated version of the `JmxTestBean` class that we +used in <>: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + package org.springframework.jmx; + + import org.springframework.jmx.export.annotation.ManagedResource; + import org.springframework.jmx.export.annotation.ManagedOperation; + import org.springframework.jmx.export.annotation.ManagedAttribute; + + @ManagedResource( + objectName="bean:name=testBean4", + description="My Managed Bean", + log=true, + logFile="jmx.log", + currencyTimeLimit=15, + persistPolicy="OnUpdate", + persistPeriod=200, + persistLocation="foo", + persistName="bar") + public class AnnotationTestBean implements IJmxTestBean { + + private String name; + private int age; + + @ManagedAttribute(description="The Age Attribute", currencyTimeLimit=15) + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @ManagedAttribute(description="The Name Attribute", + currencyTimeLimit=20, + defaultValue="bar", + persistPolicy="OnUpdate") + public void setName(String name) { + this.name = name; + } + + @ManagedAttribute(defaultValue="foo", persistPeriod=300) + public String getName() { + return name; + } + + @ManagedOperation(description="Add two numbers") + @ManagedOperationParameters({ + @ManagedOperationParameter(name = "x", description = "The first number"), + @ManagedOperationParameter(name = "y", description = "The second number")}) + public int add(int x, int y) { + return x + y; + } + + public void dontExposeMe() { + throw new RuntimeException(); + } + + } +---- + +In the preceding example, you can see that the `JmxTestBean` class is marked with the +`ManagedResource` annotation and that this `ManagedResource` annotation is configured +with a set of properties. These properties can be used to configure various aspects +of the MBean that is generated by the `MBeanExporter` and are explained in greater +detail later in <>. + +Both the `age` and `name` properties are annotated with the `ManagedAttribute` +annotation, but, in the case of the `age` property, only the getter is marked. +This causes both of these properties to be included in the management interface +as attributes, but the `age` attribute is read-only. + +Finally, the `add(int, int)` method is marked with the `ManagedOperation` attribute, +whereas the `dontExposeMe()` method is not. This causes the management interface to +contain only one operation (`add(int, int)`) when you use the `MetadataMBeanInfoAssembler`. + +The following configuration shows how you can configure the `MBeanExporter` to use the +`MetadataMBeanInfoAssembler`: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + + + + + + + + + + + +---- + +In the preceding example, an `MetadataMBeanInfoAssembler` bean has been configured with an +instance of the `AnnotationJmxAttributeSource` class and passed to the `MBeanExporter` +through the assembler property. This is all that is required to take advantage of +metadata-driven management interfaces for your Spring-exposed MBeans. + + +[[jmx-interface-metadata-types]] +=== Source-level Metadata Types + +The following table describes the source-level metadata types that are available for use in Spring JMX: + +[[jmx-metadata-types]] +.Source-level metadata types +|=== +| Purpose| Annotation| Annotation Type + +| Mark all instances of a `Class` as JMX managed resources. +| `@ManagedResource` +| Class + +| Mark a method as a JMX operation. +| `@ManagedOperation` +| Method + +| Mark a getter or setter as one half of a JMX attribute. +| `@ManagedAttribute` +| Method (only getters and setters) + +| Define descriptions for operation parameters. +| `@ManagedOperationParameter` and `@ManagedOperationParameters` +| Method +|=== + +The following table describes the configuration parameters that are available for use on these source-level +metadata types: + +[[jmx-metadata-parameters]] +.Source-level metadata parameters +[cols="1,3,1"] +|=== +| Parameter | Description | Applies to + +| `ObjectName` +| Used by `MetadataNamingStrategy` to determine the `ObjectName` of a managed resource. +| `ManagedResource` + +| `description` +| Sets the friendly description of the resource, attribute or operation. +| `ManagedResource`, `ManagedAttribute`, `ManagedOperation`, or `ManagedOperationParameter` + +| `currencyTimeLimit` +| Sets the value of the `currencyTimeLimit` descriptor field. +| `ManagedResource` or `ManagedAttribute` + +| `defaultValue` +| Sets the value of the `defaultValue` descriptor field. +| `ManagedAttribute` + +| `log` +| Sets the value of the `log` descriptor field. +| `ManagedResource` + +| `logFile` +| Sets the value of the `logFile` descriptor field. +| `ManagedResource` + +| `persistPolicy` +| Sets the value of the `persistPolicy` descriptor field. +| `ManagedResource` + +| `persistPeriod` +| Sets the value of the `persistPeriod` descriptor field. +| `ManagedResource` + +| `persistLocation` +| Sets the value of the `persistLocation` descriptor field. +| `ManagedResource` + +| `persistName` +| Sets the value of the `persistName` descriptor field. +| `ManagedResource` + +| `name` +| Sets the display name of an operation parameter. +| `ManagedOperationParameter` + +| `index` +| Sets the index of an operation parameter. +| `ManagedOperationParameter` +|=== + + +[[jmx-interface-autodetect]] +=== Using the `AutodetectCapableMBeanInfoAssembler` Interface + +To simplify configuration even further, Spring includes the +`AutodetectCapableMBeanInfoAssembler` interface, which extends the `MBeanInfoAssembler` +interface to add support for autodetection of MBean resources. If you configure the +`MBeanExporter` with an instance of `AutodetectCapableMBeanInfoAssembler`, it is +allowed to "`vote`" on the inclusion of beans for exposure to JMX. + +The only implementation of the `AutodetectCapableMBeanInfo` interface is +the `MetadataMBeanInfoAssembler`, which votes to include any bean that is marked +with the `ManagedResource` attribute. The default approach in this case is to use the +bean name as the `ObjectName`, which results in a configuration similar to the following: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + + + + + + + +---- + +Notice that, in the preceding configuration, no beans are passed to the `MBeanExporter`. +However, the `JmxTestBean` is still registered, since it is marked with the `ManagedResource` +attribute and the `MetadataMBeanInfoAssembler` detects this and votes to include it. +The only problem with this approach is that the name of the `JmxTestBean` now has business +meaning. You can address this issue by changing the default behavior for `ObjectName` +creation as defined in <>. + + +[[jmx-interface-java]] +=== Defining Management Interfaces by Using Java Interfaces + +In addition to the `MetadataMBeanInfoAssembler`, Spring also includes the +`InterfaceBasedMBeanInfoAssembler`, which lets you constrain the methods and +properties that are exposed based on the set of methods defined in a collection of +interfaces. + +Although the standard mechanism for exposing MBeans is to use interfaces and a simple +naming scheme, `InterfaceBasedMBeanInfoAssembler` extends this functionality by +removing the need for naming conventions, letting you use more than one interface +and removing the need for your beans to implement the MBean interfaces. + +Consider the following interface, which is used to define a management interface for the +`JmxTestBean` class that we showed earlier: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public interface IJmxTestBean { + + public int add(int x, int y); + + public long myOperation(); + + public int getAge(); + + public void setAge(int age); + + public void setName(String name); + + public String getName(); + + } +---- + +This interface defines the methods and properties that are exposed as operations and +attributes on the JMX MBean. The following code shows how to configure Spring JMX to use +this interface as the definition for the management interface: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + org.springframework.jmx.IJmxTestBean + + + + + + + + + + + +---- + +In the preceding example, the `InterfaceBasedMBeanInfoAssembler` is configured to use the +`IJmxTestBean` interface when constructing the management interface for any bean. It is +important to understand that beans processed by the `InterfaceBasedMBeanInfoAssembler` +are not required to implement the interface used to generate the JMX management +interface. + +In the preceding case, the `IJmxTestBean` interface is used to construct all management +interfaces for all beans. In many cases, this is not the desired behavior, and you may +want to use different interfaces for different beans. In this case, you can pass +`InterfaceBasedMBeanInfoAssembler` a `Properties` instance through the `interfaceMappings` +property, where the key of each entry is the bean name and the value of each entry is a +comma-separated list of interface names to use for that bean. + +If no management interface is specified through either the `managedInterfaces` or +`interfaceMappings` properties, the `InterfaceBasedMBeanInfoAssembler` reflects +on the bean and uses all of the interfaces implemented by that bean to create the +management interface. + + +[[jmx-interface-methodnames]] +=== Using `MethodNameBasedMBeanInfoAssembler` + +`MethodNameBasedMBeanInfoAssembler` lets you specify a list of method names +that are exposed to JMX as attributes and operations. The following code shows a sample +configuration: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + add,myOperation,getName,setName,getAge + + + + +---- + +In the preceding example, you can see that the `add` and `myOperation` methods are exposed as JMX +operations, and `getName()`, `setName(String)`, and `getAge()` are exposed as the +appropriate half of a JMX attribute. In the preceding code, the method mappings apply to +beans that are exposed to JMX. To control method exposure on a bean-by-bean basis, you can use +the `methodMappings` property of `MethodNameMBeanInfoAssembler` to map bean names to +lists of method names. + + + +[[jmx-naming]] +== Controlling `ObjectName` Instances for Your Beans + +Behind the scenes, the `MBeanExporter` delegates to an implementation of the +`ObjectNamingStrategy` to obtain an `ObjectName` instance for each of the beans it registers. +By default, the default implementation, `KeyNamingStrategy` uses the key of the +`beans` `Map` as the `ObjectName`. In addition, the `KeyNamingStrategy` can map the key +of the `beans` `Map` to an entry in a `Properties` file (or files) to resolve the +`ObjectName`. In addition to the `KeyNamingStrategy`, Spring provides two additional +`ObjectNamingStrategy` implementations: the `IdentityNamingStrategy` (which builds an +`ObjectName` based on the JVM identity of the bean) and the `MetadataNamingStrategy` (which +uses source-level metadata to obtain the `ObjectName`). + + +[[jmx-naming-properties]] +=== Reading `ObjectName` Instances from Properties + +You can configure your own `KeyNamingStrategy` instance and configure it to read +`ObjectName` instances from a `Properties` instance rather than use a bean key. The +`KeyNamingStrategy` tries to locate an entry in the `Properties` with a key +that corresponds to the bean key. If no entry is found or if the `Properties` instance is +`null`, the bean key itself is used. + +The following code shows a sample configuration for the `KeyNamingStrategy`: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + + + + + + + bean:name=testBean1 + + + + names1.properties,names2.properties + + + + +---- + +The preceding example configures an instance of `KeyNamingStrategy` with a `Properties` instance that +is merged from the `Properties` instance defined by the mapping property and the +properties files located in the paths defined by the mappings property. In this +configuration, the `testBean` bean is given an `ObjectName` of `bean:name=testBean1`, +since this is the entry in the `Properties` instance that has a key corresponding to the +bean key. + +If no entry in the `Properties` instance can be found, the bean key name is used as +the `ObjectName`. + + +[[jmx-naming-metadata]] +=== Using `MetadataNamingStrategy` + +`MetadataNamingStrategy` uses the `objectName` property of the `ManagedResource` +attribute on each bean to create the `ObjectName`. The following code shows the +configuration for the `MetadataNamingStrategy`: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + + + + + + + + + + +---- + +If no `objectName` has been provided for the `ManagedResource` attribute, an +`ObjectName` is created with the following +format: _[fully-qualified-package-name]:type=[short-classname],name=[bean-name]_. For +example, the generated `ObjectName` for the following bean would be +`com.example:type=MyClass,name=myBean`: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + + +[[jmx-context-mbeanexport]] +=== Configuring Annotation-based MBean Export + +If you prefer to use <> to define +your management interfaces, a convenience subclass of `MBeanExporter` is available: +`AnnotationMBeanExporter`. When defining an instance of this subclass, you no longer need the +`namingStrategy`, `assembler`, and `attributeSource` configuration, +since it always uses standard Java annotation-based metadata (autodetection is +always enabled as well). In fact, rather than defining an `MBeanExporter` bean, an even +simpler syntax is supported by the `@EnableMBeanExport` `@Configuration` annotation, +as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Configuration + @EnableMBeanExport + public class AppConfig { + + } +---- + +If you prefer XML-based configuration, the `` element serves the +same purpose and is shown in the following listing: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + +If necessary, you can provide a reference to a particular MBean `server`, and the +`defaultDomain` attribute (a property of `AnnotationMBeanExporter`) accepts an alternate +value for the generated MBean `ObjectName` domains. This is used in place of the +fully qualified package name as described in the previous section on +<>, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain") + @Configuration + ContextConfiguration { + + } +---- + +The following example shows the XML equivalent of the preceding annotation-based example: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + +CAUTION: Do not use interface-based AOP proxies in combination with autodetection of JMX +annotations in your bean classes. Interface-based proxies "`hide`" the target class, which +also hides the JMX-managed resource annotations. Hence, you should use target-class proxies in that +case (through setting the 'proxy-target-class' flag on ``, +`` and so on). Otherwise, your JMX beans might be silently ignored at +startup. + + + +[[jmx-jsr160]] +== Using JSR-160 Connectors + +For remote access, Spring JMX module offers two `FactoryBean` implementations inside the +`org.springframework.jmx.support` package for creating both server- and client-side +connectors. + + +[[jmx-jsr160-server]] +=== Server-side Connectors + +To have Spring JMX create, start, and expose a JSR-160 `JMXConnectorServer`, you can use the +following configuration: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + +By default, `ConnectorServerFactoryBean` creates a `JMXConnectorServer` bound to +`service:jmx:jmxmp://localhost:9875`. The `serverConnector` bean thus exposes the +local `MBeanServer` to clients through the JMXMP protocol on localhost, port 9875. Note +that the JMXMP protocol is marked as optional by the JSR 160 specification. Currently, +the main open-source JMX implementation, MX4J, and the one provided with the JDK +do not support JMXMP. + +To specify another URL and register the `JMXConnectorServer` itself with the +`MBeanServer`, you can use the `serviceUrl` and `ObjectName` properties, respectively, +as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + +---- + +If the `ObjectName` property is set, Spring automatically registers your connector +with the `MBeanServer` under that `ObjectName`. The following example shows the full set of +parameters that you can pass to the `ConnectorServerFactoryBean` when creating a +`JMXConnector`: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + +---- + +Note that, when you use a RMI-based connector, you need the lookup service (`tnameserv` or +`rmiregistry`) to be started in order for the name registration to complete. + + +[[jmx-jsr160-client]] +=== Client-side Connectors + +To create an `MBeanServerConnection` to a remote JSR-160-enabled `MBeanServer`, you can use the +`MBeanServerConnectionFactoryBean`, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + +---- + + +[[jmx-jsr160-protocols]] +=== JMX over Hessian or SOAP + +JSR-160 permits extensions to the way in which communication is done between the client +and the server. The examples shown in the preceding sections use the mandatory RMI-based implementation +required by the JSR-160 specification (IIOP and JRMP) and the (optional) JMXMP. By using +other providers or JMX implementations (such as http://mx4j.sourceforge.net[MX4J]) you +can take advantage of protocols such as SOAP or Hessian over simple HTTP or SSL and others, +as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + +---- + +In the preceding example, we used MX4J 3.0.0. See the official MX4J +documentation for more information. + + + +[[jmx-proxy]] +== Accessing MBeans through Proxies + +Spring JMX lets you create proxies that re-route calls to MBeans that are registered in a +local or remote `MBeanServer`. These proxies provide you with a standard Java interface, +through which you can interact with your MBeans. The following code shows how to configure a +proxy for an MBean running in a local `MBeanServer`: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + +---- + +In the preceding example, you can see that a proxy is created for the MBean registered under the +`ObjectName` of `bean:name=testBean`. The set of interfaces that the proxy implements +is controlled by the `proxyInterfaces` property, and the rules for mapping methods and +properties on these interfaces to operations and attributes on the MBean are the same +rules used by the `InterfaceBasedMBeanInfoAssembler`. + +The `MBeanProxyFactoryBean` can create a proxy to any MBean that is accessible through an +`MBeanServerConnection`. By default, the local `MBeanServer` is located and used, but +you can override this and provide an `MBeanServerConnection` that points to a remote +`MBeanServer` to cater for proxies that point to remote MBeans: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + +---- + +In the preceding example, we create an `MBeanServerConnection` that points to a remote machine +that uses the `MBeanServerConnectionFactoryBean`. This `MBeanServerConnection` is then +passed to the `MBeanProxyFactoryBean` through the `server` property. The proxy that is +created forwards all invocations to the `MBeanServer` through this +`MBeanServerConnection`. + + + +[[jmx-notifications]] +== Notifications + +Spring's JMX offering includes comprehensive support for JMX notifications. + + +[[jmx-notifications-listeners]] +=== Registering Listeners for Notifications + +Spring's JMX support makes it easy to register any number of +`NotificationListeners` with any number of MBeans (this includes MBeans exported by +Spring's `MBeanExporter` and MBeans registered through some other mechanism). For +example, consider the scenario where one would like to be informed (through a +`Notification`) each and every time an attribute of a target MBean changes. The following +example writes notifications to the console: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + package com.example; + + import javax.management.AttributeChangeNotification; + import javax.management.Notification; + import javax.management.NotificationFilter; + import javax.management.NotificationListener; + + public class ConsoleLoggingNotificationListener + implements NotificationListener, NotificationFilter { + + public void handleNotification(Notification notification, Object handback) { + System.out.println(notification); + System.out.println(handback); + } + + public boolean isNotificationEnabled(Notification notification) { + return AttributeChangeNotification.class.isAssignableFrom(notification.getClass()); + } + + } +---- + +The following example adds `ConsoleLoggingNotificationListener` (defined in the preceding +example) to `notificationListenerMappings`: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + + + + + + + + + + +---- + +With the preceding configuration in place, every time a JMX `Notification` is broadcast from +the target MBean (`bean:name=testBean1`), the `ConsoleLoggingNotificationListener` bean +that was registered as a listener through the `notificationListenerMappings` property is +notified. The `ConsoleLoggingNotificationListener` bean can then take whatever action +it deems appropriate in response to the `Notification`. + +You can also use straight bean names as the link between exported beans and listeners, +as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + + + + + + + + + + +---- + +If you want to register a single `NotificationListener` instance for all of the beans +that the enclosing `MBeanExporter` exports, you can use the special wildcard (`{asterisk}`) +as the key for an entry in the `notificationListenerMappings` property +map, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + +---- + +If you need to do the inverse (that is, register a number of distinct listeners against +an MBean), you must instead use the `notificationListeners` list property (in +preference to the `notificationListenerMappings` property). This time, instead of +configuring a `NotificationListener` for a single MBean, we configure +`NotificationListenerBean` instances. A `NotificationListenerBean` encapsulates a +`NotificationListener` and the `ObjectName` (or `ObjectNames`) that it is to be +registered against in an `MBeanServer`. The `NotificationListenerBean` also encapsulates +a number of other properties, such as a `NotificationFilter` and an arbitrary handback +object that can be used in advanced JMX notification scenarios. + +The configuration when using `NotificationListenerBean` instances is not wildly +different to what was presented previously, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + + + + bean:name=testBean1 + + + + + + + + + + + + + +---- + +The preceding example is equivalent to the first notification example. Assume, then, that +we want to be given a handback object every time a `Notification` is raised and that +we also want to filter out extraneous `Notifications` by supplying a +`NotificationFilter`. The following example accomplishes these goals: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + + + + bean:name=testBean1 + bean:name=testBean2 + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +(For a full discussion of what a handback object is and, +indeed, what a `NotificationFilter` is, see the section of the JMX +specification (1.2) entitled 'The JMX Notification Model'.) + + +[[jmx-notifications-publishing]] +=== Publishing Notifications + +Spring provides support not only for registering to receive `Notifications` but also +for publishing `Notifications`. + +NOTE: This section is really only relevant to Spring-managed beans that have +been exposed as MBeans through an `MBeanExporter`. Any existing user-defined MBeans should +use the standard JMX APIs for notification publication. + +The key interface in Spring's JMX notification publication support is the +`NotificationPublisher` interface (defined in the +`org.springframework.jmx.export.notification` package). Any bean that is going to be +exported as an MBean through an `MBeanExporter` instance can implement the related +`NotificationPublisherAware` interface to gain access to a `NotificationPublisher` +instance. The `NotificationPublisherAware` interface supplies an instance of a +`NotificationPublisher` to the implementing bean through a simple setter method, +which the bean can then use to publish `Notifications`. + +As stated in the javadoc of the +{api-spring-framework}/jmx/export/notification/NotificationPublisher.html[`NotificationPublisher`] +interface, managed beans that publish events through the `NotificationPublisher` +mechanism are not responsible for the state management of notification listeners. +Spring's JMX support takes care of handling all the JMX infrastructure issues. +All you need to do, as an application developer, is implement the +`NotificationPublisherAware` interface and start publishing events by using the +supplied `NotificationPublisher` instance. Note that the `NotificationPublisher` +is set after the managed bean has been registered with an `MBeanServer`. + +Using a `NotificationPublisher` instance is quite straightforward. You create a JMX +`Notification` instance (or an instance of an appropriate `Notification` subclass), +populate the notification with the data pertinent to the event that is to be +published, and invoke the `sendNotification(Notification)` on the +`NotificationPublisher` instance, passing in the `Notification`. + +In the following example, exported instances of the `JmxTestBean` publish a +`NotificationEvent` every time the `add(int, int)` operation is invoked: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + package org.springframework.jmx; + + import org.springframework.jmx.export.notification.NotificationPublisherAware; + import org.springframework.jmx.export.notification.NotificationPublisher; + import javax.management.Notification; + + public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware { + + private String name; + private int age; + private boolean isSuperman; + private NotificationPublisher publisher; + + // other getters and setters omitted for clarity + + public int add(int x, int y) { + int answer = x + y; + this.publisher.sendNotification(new Notification("add", this, 0)); + return answer; + } + + public void dontExposeMe() { + throw new RuntimeException(); + } + + public void setNotificationPublisher(NotificationPublisher notificationPublisher) { + this.publisher = notificationPublisher; + } + + } +---- + +The `NotificationPublisher` interface and the machinery to get it all working is one of +the nicer features of Spring's JMX support. It does, however, come with the price tag of +coupling your classes to both Spring and JMX. As always, the advice here is to be +pragmatic. If you need the functionality offered by the `NotificationPublisher` and +you can accept the coupling to both Spring and JMX, then do so. + + + +[[jmx-resources]] +== Further Resources + +This section contains links to further resources about JMX: + +* The https://www.oracle.com/technetwork/java/javase/tech/javamanagement-140525.html[JMX +homepage] at Oracle. +* The https://jcp.org/aboutJava/communityprocess/final/jsr003/index3.html[JMX + specification] (JSR-000003). +* The https://jcp.org/aboutJava/communityprocess/final/jsr160/index.html[JMX Remote API + specification] (JSR-000160). +* The http://mx4j.sourceforge.net/[MX4J homepage]. (MX4J is an open-source implementation of + various JMX specs.) + diff --git a/framework-docs/src/docs/asciidoc/integration/observability.adoc b/framework-docs/src/docs/asciidoc/integration/observability.adoc new file mode 100644 index 000000000000..dabca4a72ec3 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/integration/observability.adoc @@ -0,0 +1,175 @@ +[[integration.observability]] += Observability Support + +Micrometer defines an https://micrometer.io/docs/observation[Observation concept that enables both Metrics and Traces] in applications. +Metrics support offers a way to create timers, gauges or counters for collecting statistics about the runtime behavior of your application. +Metrics can help you to track error rates, usage patterns, performance and more. +Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications. + +Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured. +You can learn more about {docs-spring-boot}/html/actuator.html#actuator.metrics[configuring the observability infrastructure in Spring Boot]. + +[[integration.observability.concepts]] +== Micrometer Observation concepts + +If you are not familiar with Micrometer Observation, here's a quick summary of the new concepts you should know about. + +* `Observation` is the actual recording of something happening in your application. This is processed by `ObservationHandler` implementations to produce metrics or traces. +* Each observation has a corresponding `ObservationContext` implementation; this type holds all the relevant information for extracting metadata for it. + In the case of an HTTP server observation, the context implementation could hold the HTTP request, the HTTP response, any Exception thrown during processing... +* Each `Observation` holds `KeyValues` metadata. In the case of a server HTTP observation, this could be the HTTP request method, the HTTP response status... + This metadata is contributed by `ObservationConvention` implementations which should declare the type of `ObservationContext` they support. +* `KeyValues` are said to be "low cardinality" if there is a low, bounded number of possible values for the `KeyValue` tuple (HTTP method is a good example). + Low cardinality values are contributed to metrics only. + High cardinality values are on the other hand unbounded (for example, HTTP request URIs) and are only contributed to Traces. +* An `ObservationDocumentation` documents all observations in a particular domain, listing the expected key names and their meaning. + + +[[integration.observability.config]] +== Configuring Observations + +Global configuration options are available at the `ObservationRegistry#observationConfig()` level. +Each instrumented component will provide two extension points: + +* setting the `ObservationRegistry`; if not set, observations will not be recorded and will be no-ops +* providing a custom `ObservationConvention` to change the default observation name and extracted `KeyValues` + + +[[integration.observability.config.conventions]] +=== Using custom Observation conventions + +Let's take the example of the Spring MVC "http.server.requests" metrics instrumentation with the `ServerHttpObservationFilter`. +This observation is using a `ServerRequestObservationConvention` with a `ServerRequestObservationContext`; custom conventions can be configured on the Servlet filter. +If you would like to customize the metadata produced with the observation, you can extend the `DefaultServerRequestObservationConvention` for your requirements: + +include::code:ExtendedServerRequestObservationConvention[] + +If you want full control, you can then implement the entire convention contract for the observation you're interested in: + +include::code:CustomServerRequestObservationConvention[] + +You can also achieve similar goals using a custom `ObservationFilter` - adding or removing key values for an observation. +Filters do not replace the default convention and are used as a post-processing component. + +include::code:ServerRequestObservationFilter[] + +You can configure `ObservationFilter` instances on the `ObservationRegistry`. + + +[[integration.observability.http-server]] +== HTTP Server instrumentation + +HTTP server exchanges observations are created with the name `"http.server.requests"` for Servlet and Reactive applications. + +[[integration.observability.http-server.servlet]] +=== Servlet applications + +Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application. +It is using the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`. + +By default, the following `KeyValues` are created: + +.Low cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE`} if no exception happened. +|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly. +|`outcome` _(required)_|Outcome of the HTTP server exchange. +|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created. +|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests. +|=== + +.High cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`http.url` _(required)_|HTTP request URI. +|=== + + +[[integration.observability.http-server.reactive]] +=== Reactive applications + +Applications need to configure the `org.springframework.web.filter.reactive.ServerHttpObservationFilter` reactive `WebFilter` in their application. +It is using the `org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`. + +By default, the following `KeyValues` are created: + +.Low cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened. +|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly. +|`outcome` _(required)_|Outcome of the HTTP server exchange. +|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created. +|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests. +|=== + +.High cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`http.url` _(required)_|HTTP request URI. +|=== + + + +[[integration.observability.http-client]] +== HTTP Client instrumentation + +HTTP client exchanges observations are created with the name `"http.client.requests"` for blocking and reactive clients. +Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client. + +[[integration.observability.http-client.resttemplate]] +=== RestTemplate + +Instrumentation is using the `org.springframework.http.client.observation.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`. + +.Low cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened. +|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created. +|`outcome` _(required)_|Outcome of the HTTP client exchange. +|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received. +|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. +|=== + +.High cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`client.name` _(required)_|Client name derived from the request URI host. +|`http.url` _(required)_|HTTP request URI. +|=== + + + +[[integration.observability.http-client.webclient]] +=== WebClient + +Instrumentation is using the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`. + +.Low cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened. +|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created. +|`outcome` _(required)_|Outcome of the HTTP client exchange. +|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received. +|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. +|=== + +.High cardinality Keys +[cols="a,a"] +|=== +|Name | Description +|`client.name` _(required)_|Client name derived from the request URI host. +|`http.url` _(required)_|HTTP request URI. +|=== + + diff --git a/framework-docs/src/docs/asciidoc/integration/rest-clients.adoc b/framework-docs/src/docs/asciidoc/integration/rest-clients.adoc new file mode 100644 index 000000000000..b85fe2ae4736 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/integration/rest-clients.adoc @@ -0,0 +1,517 @@ +[[rest-client-access]] += REST Clients + +The Spring Framework provides the following choices for making calls to REST endpoints: + +* <> - non-blocking, reactive client w fluent API. +* <> - synchronous client with template method API. +* <> - annotated interface with generated, dynamic proxy implementation. + + +[[rest-webclient]] +== `WebClient` + +`WebClient` is a non-blocking, reactive client to perform HTTP requests. It was +introduced in 5.0 and offers an alternative to the `RestTemplate`, with support for +synchronous, asynchronous, and streaming scenarios. + +`WebClient` supports the following: + +* Non-blocking I/O. +* Reactive Streams back pressure. +* High concurrency with fewer hardware resources. +* Functional-style, fluent API that takes advantage of Java 8 lambdas. +* Synchronous and asynchronous interactions. +* Streaming up to or streaming down from a server. + +See <> for more details. + + + + +[[rest-resttemplate]] +== `RestTemplate` + +The `RestTemplate` provides a higher level API over HTTP client libraries. It makes it +easy to invoke REST endpoints in a single line. It exposes the following groups of +overloaded methods: + +NOTE: `RestTemplate` is in maintenance mode, with only requests for minor +changes and bugs to be accepted. Please, consider using the +<> instead. + +[[rest-overview-of-resttemplate-methods-tbl]] +.RestTemplate methods +[cols="1,3"] +|=== +| Method group | Description + +| `getForObject` +| Retrieves a representation via GET. + +| `getForEntity` +| Retrieves a `ResponseEntity` (that is, status, headers, and body) by using GET. + +| `headForHeaders` +| Retrieves all headers for a resource by using HEAD. + +| `postForLocation` +| Creates a new resource by using POST and returns the `Location` header from the response. + +| `postForObject` +| Creates a new resource by using POST and returns the representation from the response. + +| `postForEntity` +| Creates a new resource by using POST and returns the representation from the response. + +| `put` +| Creates or updates a resource by using PUT. + +| `patchForObject` +| Updates a resource by using PATCH and returns the representation from the response. +Note that the JDK `HttpURLConnection` does not support `PATCH`, but Apache +HttpComponents and others do. + +| `delete` +| Deletes the resources at the specified URI by using DELETE. + +| `optionsForAllow` +| Retrieves allowed HTTP methods for a resource by using ALLOW. + +| `exchange` +| More generalized (and less opinionated) version of the preceding methods that provides extra +flexibility when needed. It accepts a `RequestEntity` (including HTTP method, URL, headers, +and body as input) and returns a `ResponseEntity`. + +These methods allow the use of `ParameterizedTypeReference` instead of `Class` to specify +a response type with generics. + +| `execute` +| The most generalized way to perform a request, with full control over request +preparation and response extraction through callback interfaces. + +|=== + +[[rest-resttemplate-create]] +=== Initialization + +The default constructor uses `java.net.HttpURLConnection` to perform requests. You can +switch to a different HTTP library with an implementation of `ClientHttpRequestFactory`. +There is built-in support for the following: + +* Apache HttpComponents +* Netty +* OkHttp + +For example, to switch to Apache HttpComponents, you can use the following: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); +---- + +Each `ClientHttpRequestFactory` exposes configuration options specific to the underlying +HTTP client library -- for example, for credentials, connection pooling, and other details. + +TIP: Note that the `java.net` implementation for HTTP requests can raise an exception when +accessing the status of a response that represents an error (such as 401). If this is an +issue, switch to another HTTP client library. + +[[rest-resttemplate-uri]] +==== URIs + +Many of the `RestTemplate` methods accept a URI template and URI template variables, +either as a `String` variable argument, or as `Map`. + +The following example uses a `String` variable argument: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + String result = restTemplate.getForObject( + "https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21"); +---- + +The following example uses a `Map`: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + Map vars = Collections.singletonMap("hotel", "42"); + + String result = restTemplate.getForObject( + "https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars); +---- + +Keep in mind URI templates are automatically encoded, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + restTemplate.getForObject("https://example.com/hotel list", String.class); + + // Results in request to "https://example.com/hotel%20list" +---- + +You can use the `uriTemplateHandler` property of `RestTemplate` to customize how URIs +are encoded. Alternatively, you can prepare a `java.net.URI` and pass it into one of +the `RestTemplate` methods that accepts a `URI`. + +For more details on working with and encoding URIs, see <>. + +[[rest-template-headers]] +==== Headers + +You can use the `exchange()` methods to specify request headers, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + String uriTemplate = "https://example.com/hotels/{hotel}"; + URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42); + + RequestEntity requestEntity = RequestEntity.get(uri) + .header("MyRequestHeader", "MyValue") + .build(); + + ResponseEntity response = template.exchange(requestEntity, String.class); + + String responseHeader = response.getHeaders().getFirst("MyResponseHeader"); + String body = response.getBody(); +---- + +You can obtain response headers through many `RestTemplate` method variants that return +`ResponseEntity`. + +[[rest-template-body]] +=== Body + +Objects passed into and returned from `RestTemplate` methods are converted to and from raw +content with the help of an `HttpMessageConverter`. + +On a POST, an input object is serialized to the request body, as the following example shows: + +---- +URI location = template.postForLocation("https://example.com/people", person); +---- + +You need not explicitly set the Content-Type header of the request. In most cases, +you can find a compatible message converter based on the source `Object` type, and the chosen +message converter sets the content type accordingly. If necessary, you can use the +`exchange` methods to explicitly provide the `Content-Type` request header, and that, in +turn, influences what message converter is selected. + +On a GET, the body of the response is deserialized to an output `Object`, as the following example shows: + +---- +Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42); +---- + +The `Accept` header of the request does not need to be explicitly set. In most cases, +a compatible message converter can be found based on the expected response type, which +then helps to populate the `Accept` header. If necessary, you can use the `exchange` +methods to provide the `Accept` header explicitly. + +By default, `RestTemplate` registers all built-in +<>, depending on classpath checks that help +to determine what optional conversion libraries are present. You can also set the message +converters to use explicitly. + +[[rest-message-conversion]] +==== Message Conversion +[.small]#<># + +The `spring-web` module contains the `HttpMessageConverter` contract for reading and +writing the body of HTTP requests and responses through `InputStream` and `OutputStream`. +`HttpMessageConverter` instances are used on the client side (for example, in the `RestTemplate`) and +on the server side (for example, in Spring MVC REST controllers). + +Concrete implementations for the main media (MIME) types are provided in the framework +and are, by default, registered with the `RestTemplate` on the client side and with +`RequestMappingHandlerAdapter` on the server side (see +<>). + +The implementations of `HttpMessageConverter` are described in the following sections. +For all converters, a default media type is used, but you can override it by setting the +`supportedMediaTypes` bean property. The following table describes each implementation: + +[[rest-message-converters-tbl]] +.HttpMessageConverter Implementations +[cols="1,3"] +|=== +| MessageConverter | Description + +| `StringHttpMessageConverter` +| An `HttpMessageConverter` implementation that can read and write `String` instances from the HTTP +request and response. By default, this converter supports all text media types +(`text/{asterisk}`) and writes with a `Content-Type` of `text/plain`. + +| `FormHttpMessageConverter` +| An `HttpMessageConverter` implementation that can read and write form data from the HTTP +request and response. By default, this converter reads and writes the +`application/x-www-form-urlencoded` media type. Form data is read from and written into a +`MultiValueMap`. The converter can also write (but not read) multipart +data read from a `MultiValueMap`. By default, `multipart/form-data` is +supported. As of Spring Framework 5.2, additional multipart subtypes can be supported for +writing form data. Consult the javadoc for `FormHttpMessageConverter` for further details. + +| `ByteArrayHttpMessageConverter` +| An `HttpMessageConverter` implementation that can read and write byte arrays from the +HTTP request and response. By default, this converter supports all media types (`{asterisk}/{asterisk}`) +and writes with a `Content-Type` of `application/octet-stream`. You can override this +by setting the `supportedMediaTypes` property and overriding `getContentType(byte[])`. + +| `MarshallingHttpMessageConverter` +| An `HttpMessageConverter` implementation that can read and write XML by using Spring's +`Marshaller` and `Unmarshaller` abstractions from the `org.springframework.oxm` package. +This converter requires a `Marshaller` and `Unmarshaller` before it can be used. You can inject these +through constructor or bean properties. By default, this converter supports +`text/xml` and `application/xml`. + +| `MappingJackson2HttpMessageConverter` +| An `HttpMessageConverter` implementation that can read and write JSON by using Jackson's +`ObjectMapper`. You can customize JSON mapping as needed through the use of Jackson's +provided annotations. When you need further control (for cases where custom JSON +serializers/deserializers need to be provided for specific types), you can inject a custom `ObjectMapper` +through the `ObjectMapper` property. By default, this +converter supports `application/json`. + +| `MappingJackson2XmlHttpMessageConverter` +| An `HttpMessageConverter` implementation that can read and write XML by using +https://github.com/FasterXML/jackson-dataformat-xml[Jackson XML] extension's +`XmlMapper`. You can customize XML mapping as needed through the use of JAXB +or Jackson's provided annotations. When you need further control (for cases where custom XML +serializers/deserializers need to be provided for specific types), you can inject a custom `XmlMapper` +through the `ObjectMapper` property. By default, this +converter supports `application/xml`. + +| `SourceHttpMessageConverter` +| An `HttpMessageConverter` implementation that can read and write +`javax.xml.transform.Source` from the HTTP request and response. Only `DOMSource`, +`SAXSource`, and `StreamSource` are supported. By default, this converter supports +`text/xml` and `application/xml`. + +| `BufferedImageHttpMessageConverter` +| An `HttpMessageConverter` implementation that can read and write +`java.awt.image.BufferedImage` from the HTTP request and response. This converter reads +and writes the media type supported by the Java I/O API. + +|=== + +[[rest-template-jsonview]] +=== Jackson JSON Views + +You can specify a https://www.baeldung.com/jackson-json-view-annotation[Jackson JSON View] +to serialize only a subset of the object properties, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23")); + value.setSerializationView(User.WithoutPasswordView.class); + + RequestEntity requestEntity = + RequestEntity.post(new URI("https://example.com/user")).body(value); + + ResponseEntity response = template.exchange(requestEntity, String.class); +---- + +[[rest-template-multipart]] +=== Multipart + +To send multipart data, you need to provide a `MultiValueMap` whose values +may be an `Object` for part content, a `Resource` for a file part, or an `HttpEntity` for +part content with headers. For example: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + MultiValueMap parts = new LinkedMultiValueMap<>(); + + parts.add("fieldPart", "fieldValue"); + parts.add("filePart", new FileSystemResource("...logo.png")); + parts.add("jsonPart", new Person("Jason")); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_XML); + parts.add("xmlPart", new HttpEntity<>(myBean, headers)); +---- + +In most cases, you do not have to specify the `Content-Type` for each part. The content +type is determined automatically based on the `HttpMessageConverter` chosen to serialize +it or, in the case of a `Resource` based on the file extension. If necessary, you can +explicitly provide the `MediaType` with an `HttpEntity` wrapper. + +Once the `MultiValueMap` is ready, you can pass it to the `RestTemplate`, as show below: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + MultiValueMap parts = ...; + template.postForObject("https://example.com/upload", parts, Void.class); +---- + +If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set +to `multipart/form-data` by the `FormHttpMessageConverter`. If the `MultiValueMap` has +`String` values the `Content-Type` is defaulted to `application/x-www-form-urlencoded`. +If necessary the `Content-Type` may also be set explicitly. + + +[[rest-http-interface]] +== HTTP Interface + +The Spring Framework lets you define an HTTP service as a Java interface with annotated +methods for HTTP exchanges. You can then generate a proxy that implements this interface +and performs the exchanges. This helps to simplify HTTP remote access which often +involves a facade that wraps the details of using the underlying HTTP client. + +One, declare an interface with `@HttpExchange` methods: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + interface RepositoryService { + + @GetExchange("/repos/{owner}/{repo}") + Repository getRepository(@PathVariable String owner, @PathVariable String repo); + + // more HTTP exchange methods... + + } +---- + +Two, create a proxy that will perform the declared HTTP exchanges: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build(); + HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build(); + + RepositoryService service = factory.createClient(RepositoryService.class); +---- + +`@HttpExchange` is supported at the type level where it applies to all methods: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json") + interface RepositoryService { + + @GetExchange + Repository getRepository(@PathVariable String owner, @PathVariable String repo); + + @PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + void updateRepository(@PathVariable String owner, @PathVariable String repo, + @RequestParam String name, @RequestParam String description, @RequestParam String homepage); + + } +---- + + +[[rest-http-interface-method-parameters]] +=== Method Parameters + +Annotated, HTTP exchange methods support flexible method signatures with the following +method parameters: + +[cols="1,2", options="header"] +|=== +| Method argument | Description + +| `URI` +| Dynamically set the URL for the request, overriding the annotation's `url` attribute. + +| `HttpMethod` +| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute + +| `@RequestHeader` +| Add a request header or multiple headers. The argument may be a `Map` or + `MultiValueMap` with multiple headers, a `Collection` of values, or an + individual value. Type conversion is supported for non-String values. + +| `@PathVariable` +| Add a variable for expand a placeholder in the request URL. The argument may be a + `Map` with multiple variables, or an individual value. Type conversion + is supported for non-String values. + +| `@RequestBody` +| Provide the body of the request either as an Object to be serialized, or a + Reactive Streams `Publisher` such as `Mono`, `Flux`, or any other async type supported + through the configured `ReactiveAdapterRegistry`. + +| `@RequestParam` +| Add a request parameter or multiple parameters. The argument may be a `Map` + or `MultiValueMap` with multiple parameters, a `Collection` of values, or + an individual value. Type conversion is supported for non-String values. + + When `"content-type"` is set to `"application/x-www-form-urlencoded"`, request + parameters are encoded in the request body. Otherwise, they are added as URL query + parameters. + +| `@RequestPart` +| Add a request part, which may be a String (form field), `Resource` (file part), + Object (entity to be encoded, e.g. as JSON), `HttpEntity` (part content and headers), + a Spring `Part`, or Reactive Streams `Publisher` of any of the above. + +| `@CookieValue` +| Add a cookie or multiple cookies. The argument may be a `Map` or + `MultiValueMap` with multiple cookies, a `Collection` of values, or an + individual value. Type conversion is supported for non-String values. + +|=== + + +[[rest-http-interface-return-values]] +=== Return Values + +Annotated, HTTP exchange methods support the following return values: + +[cols="1,2", options="header"] +|=== +| Method return value | Description + +| `void`, `Mono` +| Perform the given request, and release the response content, if any. + +| `HttpHeaders`, `Mono` +| Perform the given request, release the response content, if any, and return the + response headers. + +| ``, `Mono` +| Perform the given request and decode the response content to the declared return type. + +| ``, `Flux` +| Perform the given request and decode the response content to a stream of the declared + element type. + +| `ResponseEntity`, `Mono>` +| Perform the given request, and release the response content, if any, and return a + `ResponseEntity` with the status and headers. + +| `ResponseEntity`, `Mono>` +| Perform the given request, decode the response content to the declared return type, and + return a `ResponseEntity` with the status, headers, and the decoded body. + +| `Mono>` +| Perform the given request, decode the response content to a stream of the declared + element type, and return a `ResponseEntity` with the status, headers, and the decoded + response body stream. + +|=== + +TIP: You can also use any other async or reactive types registered in the +`ReactiveAdapterRegistry`. + + +[[rest-http-interface-exceptions]] +=== Exception Handling + +By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status +codes. To customize this, you can register a response status handler that applies to all +responses performed through the client: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + WebClient webClient = WebClient.builder() + .defaultStatusHandler(HttpStatusCode::isError, resp -> ...) + .build(); + + WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient); + HttpServiceProxyFactory factory = HttpServiceProxyFactory + .builder(clientAdapter).build(); +---- + +For more details and options, such as suppressing error status codes, see the Javadoc of +`defaultStatusHandler` in `WebClient.Builder`. diff --git a/framework-docs/src/docs/asciidoc/integration/scheduling.adoc b/framework-docs/src/docs/asciidoc/integration/scheduling.adoc new file mode 100644 index 000000000000..6fd1aeeeb103 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/integration/scheduling.adoc @@ -0,0 +1,968 @@ +[[scheduling]] += Task Execution and Scheduling + +The Spring Framework provides abstractions for the asynchronous execution and scheduling of +tasks with the `TaskExecutor` and `TaskScheduler` interfaces, respectively. Spring also +features implementations of those interfaces that support thread pools or delegation to +CommonJ within an application server environment. Ultimately, the use of these +implementations behind the common interfaces abstracts away the differences between Java +SE 5, Java SE 6, and Jakarta EE environments. + +Spring also features integration classes to support scheduling with the `Timer` +(part of the JDK since 1.3) and the https://www.quartz-scheduler.org/[Quartz Scheduler]. +You can set up both of those schedulers by using a `FactoryBean` with optional references to +`Timer` or `Trigger` instances, respectively. Furthermore, a convenience class for both +the Quartz Scheduler and the `Timer` is available that lets you invoke a method of +an existing target object (analogous to the normal `MethodInvokingFactoryBean` +operation). + + + +[[scheduling-task-executor]] +== The Spring `TaskExecutor` Abstraction + +Executors are the JDK name for the concept of thread pools. The "`executor`" naming is +due to the fact that there is no guarantee that the underlying implementation is +actually a pool. An executor may be single-threaded or even synchronous. Spring's +abstraction hides implementation details between the Java SE and Jakarta EE environments. + +Spring's `TaskExecutor` interface is identical to the `java.util.concurrent.Executor` +interface. In fact, originally, its primary reason for existence was to abstract away +the need for Java 5 when using thread pools. The interface has a single method +(`execute(Runnable task)`) that accepts a task for execution based on the semantics +and configuration of the thread pool. + +The `TaskExecutor` was originally created to give other Spring components an abstraction +for thread pooling where needed. Components such as the `ApplicationEventMulticaster`, +JMS's `AbstractMessageListenerContainer`, and Quartz integration all use the +`TaskExecutor` abstraction to pool threads. However, if your beans need thread pooling +behavior, you can also use this abstraction for your own needs. + + +[[scheduling-task-executor-types]] +=== `TaskExecutor` Types + +Spring includes a number of pre-built implementations of `TaskExecutor`. +In all likelihood, you should never need to implement your own. +The variants that Spring provides are as follows: + +* `SyncTaskExecutor`: + This implementation does not run invocations asynchronously. Instead, each + invocation takes place in the calling thread. It is primarily used in situations + where multi-threading is not necessary, such as in simple test cases. +* `SimpleAsyncTaskExecutor`: + This implementation does not reuse any threads. Rather, it starts up a new thread + for each invocation. However, it does support a concurrency limit that blocks + any invocations that are over the limit until a slot has been freed up. If you + are looking for true pooling, see `ThreadPoolTaskExecutor`, later in this list. +* `ConcurrentTaskExecutor`: + This implementation is an adapter for a `java.util.concurrent.Executor` instance. + There is an alternative (`ThreadPoolTaskExecutor`) that exposes the `Executor` + configuration parameters as bean properties. There is rarely a need to use + `ConcurrentTaskExecutor` directly. However, if the `ThreadPoolTaskExecutor` is not + flexible enough for your needs, `ConcurrentTaskExecutor` is an alternative. +* `ThreadPoolTaskExecutor`: + This implementation is most commonly used. It exposes bean properties for + configuring a `java.util.concurrent.ThreadPoolExecutor` and wraps it in a `TaskExecutor`. + If you need to adapt to a different kind of `java.util.concurrent.Executor`, we + recommend that you use a `ConcurrentTaskExecutor` instead. +* `DefaultManagedTaskExecutor`: + This implementation uses a JNDI-obtained `ManagedExecutorService` in a JSR-236 + compatible runtime environment (such as a Jakarta EE application server), + replacing a CommonJ WorkManager for that purpose. + + +[[scheduling-task-executor-usage]] +=== Using a `TaskExecutor` + +Spring's `TaskExecutor` implementations are used as simple JavaBeans. In the following example, +we define a bean that uses the `ThreadPoolTaskExecutor` to asynchronously print +out a set of messages: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + import org.springframework.core.task.TaskExecutor; + + public class TaskExecutorExample { + + private class MessagePrinterTask implements Runnable { + + private String message; + + public MessagePrinterTask(String message) { + this.message = message; + } + + public void run() { + System.out.println(message); + } + } + + private TaskExecutor taskExecutor; + + public TaskExecutorExample(TaskExecutor taskExecutor) { + this.taskExecutor = taskExecutor; + } + + public void printMessages() { + for(int i = 0; i < 25; i++) { + taskExecutor.execute(new MessagePrinterTask("Message" + i)); + } + } + } +---- + +As you can see, rather than retrieving a thread from the pool and executing it yourself, +you add your `Runnable` to the queue. Then the `TaskExecutor` uses its internal rules to +decide when the task gets run. + +To configure the rules that the `TaskExecutor` uses, we expose simple bean properties: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + +---- + + + +[[scheduling-task-scheduler]] +== The Spring `TaskScheduler` Abstraction + +In addition to the `TaskExecutor` abstraction, Spring 3.0 introduced a `TaskScheduler` +with a variety of methods for scheduling tasks to run at some point in the future. +The following listing shows the `TaskScheduler` interface definition: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public interface TaskScheduler { + + ScheduledFuture schedule(Runnable task, Trigger trigger); + + ScheduledFuture schedule(Runnable task, Instant startTime); + + ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period); + + ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period); + + ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay); + + ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay); + +---- + +The simplest method is the one named `schedule` that takes only a `Runnable` and an `Instant`. +That causes the task to run once after the specified time. All of the other methods +are capable of scheduling tasks to run repeatedly. The fixed-rate and fixed-delay +methods are for simple, periodic execution, but the method that accepts a `Trigger` is +much more flexible. + + +[[scheduling-trigger-interface]] +=== `Trigger` Interface + +The `Trigger` interface is essentially inspired by JSR-236 which, as of Spring 3.0, +was not yet officially implemented. The basic idea of the `Trigger` is that execution +times may be determined based on past execution outcomes or even arbitrary conditions. +If these determinations take into account the outcome of the preceding execution, +that information is available within a `TriggerContext`. The `Trigger` interface itself +is quite simple, as the following listing shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public interface Trigger { + + Instant nextExecution(TriggerContext triggerContext); + } +---- + +The `TriggerContext` is the most important part. It encapsulates all of +the relevant data and is open for extension in the future, if necessary. The +`TriggerContext` is an interface (a `SimpleTriggerContext` implementation is used by +default). The following listing shows the available methods for `Trigger` implementations. + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public interface TriggerContext { + + Instant lastScheduledExecution(); + + Instant lastActualExecution(); + + Instant lastCompletion(); + } +---- + + +[[scheduling-trigger-implementations]] +=== `Trigger` Implementations + +Spring provides two implementations of the `Trigger` interface. The most interesting one +is the `CronTrigger`. It enables the scheduling of tasks based on +<>. +For example, the following task is scheduled to run 15 minutes past each hour but only +during the 9-to-5 "business hours" on weekdays: + +[source,java,indent=0] +[subs="verbatim"] +---- + scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI")); +---- + +The other implementation is a `PeriodicTrigger` that accepts a fixed +period, an optional initial delay value, and a boolean to indicate whether the period +should be interpreted as a fixed-rate or a fixed-delay. Since the `TaskScheduler` +interface already defines methods for scheduling tasks at a fixed rate or with a +fixed delay, those methods should be used directly whenever possible. The value of the +`PeriodicTrigger` implementation is that you can use it within components that rely on +the `Trigger` abstraction. For example, it may be convenient to allow periodic triggers, +cron-based triggers, and even custom trigger implementations to be used interchangeably. +Such a component could take advantage of dependency injection so that you can configure such `Triggers` +externally and, therefore, easily modify or extend them. + + +[[scheduling-task-scheduler-implementations]] +=== `TaskScheduler` implementations + +As with Spring's `TaskExecutor` abstraction, the primary benefit of the `TaskScheduler` +arrangement is that an application's scheduling needs are decoupled from the deployment +environment. This abstraction level is particularly relevant when deploying to an +application server environment where threads should not be created directly by the +application itself. For such scenarios, Spring provides a `TimerManagerTaskScheduler` +that delegates to a CommonJ `TimerManager` on WebLogic or WebSphere as well as a more recent +`DefaultManagedTaskScheduler` that delegates to a JSR-236 `ManagedScheduledExecutorService` +in a Jakarta EE environment. Both are typically configured with a JNDI lookup. + +Whenever external thread management is not a requirement, a simpler alternative is +a local `ScheduledExecutorService` setup within the application, which can be adapted +through Spring's `ConcurrentTaskScheduler`. As a convenience, Spring also provides a +`ThreadPoolTaskScheduler`, which internally delegates to a `ScheduledExecutorService` +to provide common bean-style configuration along the lines of `ThreadPoolTaskExecutor`. +These variants work perfectly fine for locally embedded thread pool setups in lenient +application server environments, as well -- in particular on Tomcat and Jetty. + + + +[[scheduling-annotation-support]] +== Annotation Support for Scheduling and Asynchronous Execution + +Spring provides annotation support for both task scheduling and asynchronous method +execution. + + +[[scheduling-enable-annotation-support]] +=== Enable Scheduling Annotations + +To enable support for `@Scheduled` and `@Async` annotations, you can add `@EnableScheduling` and +`@EnableAsync` to one of your `@Configuration` classes, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Configuration + @EnableAsync + @EnableScheduling + public class AppConfig { + } +---- + +You can pick and choose the relevant annotations for your application. For example, +if you need only support for `@Scheduled`, you can omit `@EnableAsync`. For more +fine-grained control, you can additionally implement the `SchedulingConfigurer` +interface, the `AsyncConfigurer` interface, or both. See the +{api-spring-framework}/scheduling/annotation/SchedulingConfigurer.html[`SchedulingConfigurer`] +and {api-spring-framework}/scheduling/annotation/AsyncConfigurer.html[`AsyncConfigurer`] +javadoc for full details. + +If you prefer XML configuration, you can use the `` element, +as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + +---- + +Note that, with the preceding XML, an executor reference is provided for handling those +tasks that correspond to methods with the `@Async` annotation, and the scheduler +reference is provided for managing those methods annotated with `@Scheduled`. + +NOTE: The default advice mode for processing `@Async` annotations is `proxy` which allows +for interception of calls through the proxy only. Local calls within the same class +cannot get intercepted that way. For a more advanced mode of interception, consider +switching to `aspectj` mode in combination with compile-time or load-time weaving. + + +[[scheduling-annotation-support-scheduled]] +=== The `@Scheduled` annotation + +You can add the `@Scheduled` annotation to a method, along with trigger metadata. For +example, the following method is invoked every five seconds (5000 milliseconds) with a +fixed delay, meaning that the period is measured from the completion time of each +preceding invocation. + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Scheduled(fixedDelay = 5000) + public void doSomething() { + // something that should run periodically + } +---- + +[NOTE] +==== +By default, milliseconds will be used as the time unit for fixed delay, fixed rate, and +initial delay values. If you would like to use a different time unit such as seconds or +minutes, you can configure this via the `timeUnit` attribute in `@Scheduled`. + +For example, the previous example can also be written as follows. + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) + public void doSomething() { + // something that should run periodically + } +---- +==== + +If you need a fixed-rate execution, you can use the `fixedRate` attribute within the +annotation. The following method is invoked every five seconds (measured between the +successive start times of each invocation). + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS) + public void doSomething() { + // something that should run periodically + } +---- + +For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the +amount of time to wait before the first execution of the method, as the following +`fixedRate` example shows. + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Scheduled(initialDelay = 1000, fixedRate = 5000) + public void doSomething() { + // something that should run periodically + } +---- + +If simple periodic scheduling is not expressive enough, you can provide a +<>. +The following example runs only on weekdays: + +[source,java,indent=0] +[subs="verbatim"] +---- + @Scheduled(cron="*/5 * * * * MON-FRI") + public void doSomething() { + // something that should run on weekdays only + } +---- + +TIP: You can also use the `zone` attribute to specify the time zone in which the cron +expression is resolved. + +Notice that the methods to be scheduled must have void returns and must not accept any +arguments. If the method needs to interact with other objects from the application +context, those would typically have been provided through dependency injection. + +[NOTE] +==== +As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope. + +Make sure that you are not initializing multiple instances of the same `@Scheduled` +annotation class at runtime, unless you do want to schedule callbacks to each such +instance. Related to this, make sure that you do not use `@Configurable` on bean +classes that are annotated with `@Scheduled` and registered as regular Spring beans +with the container. Otherwise, you would get double initialization (once through the +container and once through the `@Configurable` aspect), with the consequence of each +`@Scheduled` method being invoked twice. +==== + + +[[scheduling-annotation-support-async]] +=== The `@Async` annotation + +You can provide the `@Async` annotation on a method so that invocation of that method +occurs asynchronously. In other words, the caller returns immediately upon +invocation, while the actual execution of the method occurs in a task that has been +submitted to a Spring `TaskExecutor`. In the simplest case, you can apply the annotation +to a method that returns `void`, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Async + void doSomething() { + // this will be run asynchronously + } +---- + +Unlike the methods annotated with the `@Scheduled` annotation, these methods can expect +arguments, because they are invoked in the "`normal`" way by callers at runtime rather +than from a scheduled task being managed by the container. For example, the following code is +a legitimate application of the `@Async` annotation: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Async + void doSomething(String s) { + // this will be run asynchronously + } +---- + +Even methods that return a value can be invoked asynchronously. However, such methods +are required to have a `Future`-typed return value. This still provides the benefit of +asynchronous execution so that the caller can perform other tasks prior to calling +`get()` on that `Future`. The following example shows how to use `@Async` on a method +that returns a value: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Async + Future returnSomething(int i) { + // this will be run asynchronously + } +---- + +TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return type +but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of Spring +4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with the +asynchronous task and for immediate composition with further processing steps. + +You can not use `@Async` in conjunction with lifecycle callbacks such as +`@PostConstruct`. To asynchronously initialize Spring beans, you currently have to use +a separate initializing Spring bean that then invokes the `@Async` annotated method on the +target, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public class SampleBeanImpl implements SampleBean { + + @Async + void doSomething() { + // ... + } + + } + + public class SampleBeanInitializer { + + private final SampleBean bean; + + public SampleBeanInitializer(SampleBean bean) { + this.bean = bean; + } + + @PostConstruct + public void initialize() { + bean.doSomething(); + } + + } +---- + +NOTE: There is no direct XML equivalent for `@Async`, since such methods should be designed +for asynchronous execution in the first place, not externally re-declared to be asynchronous. +However, you can manually set up Spring's `AsyncExecutionInterceptor` with Spring AOP, +in combination with a custom pointcut. + + +[[scheduling-annotation-support-qualification]] +=== Executor Qualification with `@Async` + +By default, when specifying `@Async` on a method, the executor that is used is the +one <>, +i.e. the "`annotation-driven`" element if you are using XML or your `AsyncConfigurer` +implementation, if any. However, you can use the `value` attribute of the `@Async` +annotation when you need to indicate that an executor other than the default should be +used when executing a given method. The following example shows how to do so: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Async("otherExecutor") + void doSomething(String s) { + // this will be run asynchronously by "otherExecutor" + } +---- + +In this case, `"otherExecutor"` can be the name of any `Executor` bean in the Spring +container, or it may be the name of a qualifier associated with any `Executor` (for example, as +specified with the `` element or Spring's `@Qualifier` annotation). + + +[[scheduling-annotation-support-exception]] +=== Exception Management with `@Async` + +When an `@Async` method has a `Future`-typed return value, it is easy to manage +an exception that was thrown during the method execution, as this exception is +thrown when calling `get` on the `Future` result. With a `void` return type, +however, the exception is uncaught and cannot be transmitted. You can provide an +`AsyncUncaughtExceptionHandler` to handle such exceptions. The following example shows +how to do so: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { + + @Override + public void handleUncaughtException(Throwable ex, Method method, Object... params) { + // handle exception + } + } +---- + +By default, the exception is merely logged. You can define a custom `AsyncUncaughtExceptionHandler` +by using `AsyncConfigurer` or the `` XML element. + + + +[[scheduling-task-namespace]] +== The `task` Namespace + +As of version 3.0, Spring includes an XML namespace for configuring `TaskExecutor` and +`TaskScheduler` instances. It also provides a convenient way to configure tasks to be +scheduled with a trigger. + + +[[scheduling-task-namespace-scheduler]] +=== The 'scheduler' Element + +The following element creates a `ThreadPoolTaskScheduler` instance with the +specified thread pool size: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + +The value provided for the `id` attribute is used as the prefix for thread names +within the pool. The `scheduler` element is relatively straightforward. If you do not +provide a `pool-size` attribute, the default thread pool has only a single thread. +There are no other configuration options for the scheduler. + + +[[scheduling-task-namespace-executor]] +=== The `executor` Element + +The following creates a `ThreadPoolTaskExecutor` instance: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + +As with the scheduler shown in the <>, +the value provided for the `id` attribute is used as the prefix for thread names within +the pool. As far as the pool size is concerned, the `executor` element supports more +configuration options than the `scheduler` element. For one thing, the thread pool for +a `ThreadPoolTaskExecutor` is itself more configurable. Rather than only a single size, +an executor's thread pool can have different values for the core and the max size. +If you provide a single value, the executor has a fixed-size thread pool (the core and +max sizes are the same). However, the `executor` element's `pool-size` attribute also +accepts a range in the form of `min-max`. The following example sets a minimum value of +`5` and a maximum value of `25`: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + +In the preceding configuration, a `queue-capacity` value has also been provided. +The configuration of the thread pool should also be considered in light of the +executor's queue capacity. For the full description of the relationship between pool +size and queue capacity, see the documentation for +https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.html[`ThreadPoolExecutor`]. +The main idea is that, when a task is submitted, the executor first tries to use a +free thread if the number of active threads is currently less than the core size. +If the core size has been reached, the task is added to the queue, as long as its +capacity has not yet been reached. Only then, if the queue's capacity has been +reached, does the executor create a new thread beyond the core size. If the max size +has also been reached, then the executor rejects the task. + +By default, the queue is unbounded, but this is rarely the desired configuration, +because it can lead to `OutOfMemoryErrors` if enough tasks are added to that queue while +all pool threads are busy. Furthermore, if the queue is unbounded, the max size has +no effect at all. Since the executor always tries the queue before creating a new +thread beyond the core size, a queue must have a finite capacity for the thread pool to +grow beyond the core size (this is why a fixed-size pool is the only sensible case +when using an unbounded queue). + +Consider the case, as mentioned above, when a task is rejected. By default, when a +task is rejected, a thread pool executor throws a `TaskRejectedException`. However, +the rejection policy is actually configurable. The exception is thrown when using +the default rejection policy, which is the `AbortPolicy` implementation. +For applications where some tasks can be skipped under heavy load, you can instead +configure either `DiscardPolicy` or `DiscardOldestPolicy`. Another option that works +well for applications that need to throttle the submitted tasks under heavy load is +the `CallerRunsPolicy`. Instead of throwing an exception or discarding tasks, +that policy forces the thread that is calling the submit method to run the task itself. +The idea is that such a caller is busy while running that task and not able to submit +other tasks immediately. Therefore, it provides a simple way to throttle the incoming +load while maintaining the limits of the thread pool and queue. Typically, this allows +the executor to "`catch up`" on the tasks it is handling and thereby frees up some +capacity on the queue, in the pool, or both. You can choose any of these options from an +enumeration of values available for the `rejection-policy` attribute on the `executor` +element. + +The following example shows an `executor` element with a number of attributes to specify +various behaviors: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + +Finally, the `keep-alive` setting determines the time limit (in seconds) for which threads +may remain idle before being stopped. If there are more than the core number of threads +currently in the pool, after waiting this amount of time without processing a task, excess +threads get stopped. A time value of zero causes excess threads to stop +immediately after executing a task without remaining follow-up work in the task queue. +The following example sets the `keep-alive` value to two minutes: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + + +[[scheduling-task-namespace-scheduled-tasks]] +=== The 'scheduled-tasks' Element + +The most powerful feature of Spring's task namespace is the support for configuring +tasks to be scheduled within a Spring Application Context. This follows an approach +similar to other "`method-invokers`" in Spring, such as that provided by the JMS namespace +for configuring message-driven POJOs. Basically, a `ref` attribute can point to any +Spring-managed object, and the `method` attribute provides the name of a method to be +invoked on that object. The following listing shows a simple example: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + +---- + +The scheduler is referenced by the outer element, and each individual +task includes the configuration of its trigger metadata. In the preceding example, that +metadata defines a periodic trigger with a fixed delay indicating the number of +milliseconds to wait after each task execution has completed. Another option is +`fixed-rate`, indicating how often the method should be run regardless of how long +any previous execution takes. Additionally, for both `fixed-delay` and `fixed-rate` tasks, you can specify an +'initial-delay' parameter, indicating the number of milliseconds to wait +before the first execution of the method. For more control, you can instead provide a `cron` attribute +to provide a <>. +The following example shows these other options: + +[source,xml,indent=0] +[subs="verbatim"] +---- + + + + + + + +---- + + + +[[scheduling-cron-expression]] +== Cron Expressions + +All Spring cron expressions have to conform to the same format, whether you are using them in +<>, +<>, +or someplace else. +A well-formed cron expression, such as `* * * * * *`, consists of six space-separated time and date +fields, each with its own range of valid values: + + +.... + ┌───────────── second (0-59) + │ ┌───────────── minute (0 - 59) + │ │ ┌───────────── hour (0 - 23) + │ │ │ ┌───────────── day of the month (1 - 31) + │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC) + │ │ │ │ │ ┌───────────── day of the week (0 - 7) + │ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN) + │ │ │ │ │ │ + * * * * * * +.... + +There are some rules that apply: + +* A field may be an asterisk (`*`), which always stands for "`first-last`". +For the day-of-the-month or day-of-the-week fields, a question mark (`?`) may be used instead of an +asterisk. +* Commas (`,`) are used to separate items of a list. +* Two numbers separated with a hyphen (`-`) express a range of numbers. +The specified range is inclusive. +* Following a range (or `*`) with `/` specifies the interval of the number's value through the range. +* English names can also be used for the month and day-of-week fields. +Use the first three letters of the particular day or month (case does not matter). +* The day-of-month and day-of-week fields can contain an `L` character, which has a different meaning. +** In the day-of-month field, `L` stands for _the last day of the month_. +If followed by a negative offset (that is, `L-n`), it means _``n``th-to-last day of the month_. +** In the day-of-week field, `L` stands for _the last day of the week_. +If prefixed by a number or three-letter name (`dL` or `DDDL`), it means _the last day of week (`d` +or `DDD`) in the month_. +* The day-of-month field can be `nW`, which stands for _the nearest weekday to day of the month ``n``_. +If `n` falls on Saturday, this yields the Friday before it. +If `n` falls on Sunday, this yields the Monday after, which also happens if `n` is `1` and falls on +a Saturday (that is: `1W` stands for _the first weekday of the month_). +* If the day-of-month field is `LW`, it means _the last weekday of the month_. +* The day-of-week field can be `d#n` (or `DDD#n`), which stands for _the ``n``th day of week `d` +(or ``DDD``) in the month_. + +Here are some examples: + +|=== +| Cron Expression | Meaning + +|`0 0 * * * *` | top of every hour of every day +|`*/10 * * * * *` | every ten seconds +| `0 0 8-10 * * *` | 8, 9 and 10 o'clock of every day +| `0 0 6,19 * * *` | 6:00 AM and 7:00 PM every day +| `0 0/30 8-10 * * *` | 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day +| `0 0 9-17 * * MON-FRI`| on the hour nine-to-five weekdays +| `0 0 0 25 DEC ?` | every Christmas Day at midnight +| `0 0 0 L * *` | last day of the month at midnight +| `0 0 0 L-3 * *` | third-to-last day of the month at midnight +| `0 0 0 * * 5L` | last Friday of the month at midnight +| `0 0 0 * * THUL` | last Thursday of the month at midnight +| `0 0 0 1W * *` | first weekday of the month at midnight +| `0 0 0 LW * *` | last weekday of the month at midnight +| `0 0 0 ? * 5#2` | the second Friday in the month at midnight +| `0 0 0 ? * MON#1` | the first Monday in the month at midnight +|=== + +=== Macros + +Expressions such as `0 0 * * * *` are hard for humans to parse and are, therefore, hard to fix in case of bugs. +To improve readability, Spring supports the following macros, which represent commonly used sequences. +You can use these macros instead of the six-digit value, thus: `@Scheduled(cron = "@hourly")`. + +|=== +|Macro | Meaning + +| `@yearly` (or `@annually`) | once a year (`0 0 0 1 1 *`) +| `@monthly` | once a month (`0 0 0 1 * *`) +| `@weekly` | once a week (`0 0 0 * * 0`) +| `@daily` (or `@midnight`) | once a day (`0 0 0 * * *`), or +| `@hourly` | once an hour, (`0 0 * * * *`) +|=== + + + +[[scheduling-quartz]] +== Using the Quartz Scheduler + +Quartz uses `Trigger`, `Job`, and `JobDetail` objects to realize scheduling of all kinds +of jobs. For the basic concepts behind Quartz, see the +https://www.quartz-scheduler.org/[Quartz Web site]. For convenience purposes, Spring +offers a couple of classes that simplify using Quartz within Spring-based applications. + + +[[scheduling-quartz-jobdetail]] +=== Using the `JobDetailFactoryBean` + +Quartz `JobDetail` objects contain all the information needed to run a job. Spring provides a +`JobDetailFactoryBean`, which provides bean-style properties for XML configuration purposes. +Consider the following example: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + +---- + +The job detail configuration has all the information it needs to run the job (`ExampleJob`). +The timeout is specified in the job data map. The job data map is available through the +`JobExecutionContext` (passed to you at execution time), but the `JobDetail` also gets +its properties from the job data mapped to properties of the job instance. So, in the following example, +the `ExampleJob` contains a bean property named `timeout`, and the `JobDetail` +has it applied automatically: + +[source,java,indent=0] +[subs="verbatim"] +---- + package example; + + public class ExampleJob extends QuartzJobBean { + + private int timeout; + + /** + * Setter called after the ExampleJob is instantiated + * with the value from the JobDetailFactoryBean (5) + */ + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException { + // do the actual work + } + } +---- + +All additional properties from the job data map are available to you as well. + +NOTE: By using the `name` and `group` properties, you can modify the name and the group +of the job, respectively. By default, the name of the job matches the bean name +of the `JobDetailFactoryBean` (`exampleJob` in the preceding example above). + + +[[scheduling-quartz-method-invoking-job]] +=== Using the `MethodInvokingJobDetailFactoryBean` + +Often you merely need to invoke a method on a specific object. By using the +`MethodInvokingJobDetailFactoryBean`, you can do exactly this, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + +---- + +The preceding example results in the `doIt` method being called on the +`exampleBusinessObject` method, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + public class ExampleBusinessObject { + + // properties and collaborators + + public void doIt() { + // do the actual work + } + } +---- + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + +---- + +By using the `MethodInvokingJobDetailFactoryBean`, you need not create one-line jobs +that merely invoke a method. You need only create the actual business object and +wire up the detail object. + +By default, Quartz Jobs are stateless, resulting in the possibility of jobs interfering +with each other. If you specify two triggers for the same `JobDetail`, it is possible +that the second one starts before the first job has finished. If `JobDetail` classes +implement the `Stateful` interface, this does not happen: the second job does not start +before the first one has finished. + +To make jobs resulting from the `MethodInvokingJobDetailFactoryBean` be non-concurrent, +set the `concurrent` flag to `false`, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + +---- + +NOTE: By default, jobs will run in a concurrent fashion. + + +[[scheduling-quartz-cron]] +=== Wiring up Jobs by Using Triggers and `SchedulerFactoryBean` + +We have created job details and jobs. We have also reviewed the convenience bean that lets +you invoke a method on a specific object. Of course, we still need to schedule the +jobs themselves. This is done by using triggers and a `SchedulerFactoryBean`. Several +triggers are available within Quartz, and Spring offers two Quartz `FactoryBean` +implementations with convenient defaults: `CronTriggerFactoryBean` and +`SimpleTriggerFactoryBean`. + +Triggers need to be scheduled. Spring offers a `SchedulerFactoryBean` that exposes +triggers to be set as properties. `SchedulerFactoryBean` schedules the actual jobs with +those triggers. + +The following listing uses both a `SimpleTriggerFactoryBean` and a `CronTriggerFactoryBean`: + +[source,xml,indent=0] +[subs="verbatim"] +---- + + + + + + + + + + + + + + +---- + +The preceding example sets up two triggers, one running every 50 seconds with a starting delay of 10 +seconds and one running every morning at 6 AM. To finalize everything, we need to set up the +`SchedulerFactoryBean`, as the following example shows: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + +---- + +More properties are available for the `SchedulerFactoryBean`, such as the calendars used by the +job details, properties to customize Quartz with, and a Spring-provided JDBC DataSource. See +the {api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`] +javadoc for more information. + +NOTE: `SchedulerFactoryBean` also recognizes a `quartz.properties` file in the classpath, +based on Quartz property keys, as with regular Quartz configuration. Please note that many +`SchedulerFactoryBean` settings interact with common Quartz settings in the properties file; +it is therefore not recommended to specify values at both levels. For example, do not set +an "org.quartz.jobStore.class" property if you mean to rely on a Spring-provided DataSource, +or specify an `org.springframework.scheduling.quartz.LocalDataSourceJobStore` variant which +is a full-fledged replacement for the standard `org.quartz.impl.jdbcjobstore.JobStoreTX`. + diff --git a/framework-docs/src/docs/asciidoc/languages.adoc b/framework-docs/src/docs/asciidoc/languages.adoc index f47eba7e9c64..1f37d2217c14 100644 --- a/framework-docs/src/docs/asciidoc/languages.adoc +++ b/framework-docs/src/docs/asciidoc/languages.adoc @@ -1,9 +1,7 @@ [[languages]] = Language Support -:toc: left -:toclevels: 4 -:tabsize: 4 -:docinfo1: +include::attributes.adoc[] +include::page-layout.adoc[] include::languages/kotlin.adoc[leveloffset=+1] diff --git a/framework-docs/src/docs/asciidoc/languages/kotlin.adoc b/framework-docs/src/docs/asciidoc/languages/kotlin.adoc index 90aa0e58778b..92ba9c7f2e43 100644 --- a/framework-docs/src/docs/asciidoc/languages/kotlin.adoc +++ b/framework-docs/src/docs/asciidoc/languages/kotlin.adoc @@ -20,6 +20,9 @@ Feel free to join the #spring channel of https://slack.kotlinlang.org/[Kotlin Sl or ask a question with `spring` and `kotlin` as tags on https://stackoverflow.com/questions/tagged/spring+kotlin[Stackoverflow] if you need support. + + + [[kotlin-requirements]] == Requirements @@ -37,6 +40,9 @@ for serializing or deserializing JSON data for Kotlin classes with Jackson, so m `com.fasterxml.jackson.module:jackson-module-kotlin` dependency to your project if you have such need. It is automatically registered when found in the classpath. + + + [[kotlin-extensions]] == Extensions @@ -80,6 +86,9 @@ With Kotlin and the Spring Framework extensions, you can instead write the follo As in Java, `users` in Kotlin is strongly typed, but Kotlin's clever type inference allows for shorter syntax. + + + [[kotlin-null-safety]] == Null-safety @@ -115,6 +124,9 @@ NOTE: Generic type arguments, varargs, and array elements nullability are not su but should be in an upcoming release. See https://github.com/Kotlin/KEEP/issues/79[this discussion] for up-to-date information. + + + [[kotlin-classes-interfaces]] == Classes and Interfaces @@ -124,12 +136,16 @@ with default values. Kotlin parameter names are recognized through a dedicated `KotlinReflectionParameterNameDiscoverer`, which allows finding interface method parameter names without requiring the Java 8 `-parameters` -compiler flag to be enabled during compilation. +compiler flag to be enabled during compilation. (For completeness, we nevertheless recommend +running the Kotlin compiler with its `-java-parameters` flag for standard Java parameter exposure.) You can declare configuration classes as https://kotlinlang.org/docs/reference/nested-classes.html[top level or nested but not inner], since the later requires a reference to the outer class. + + + [[kotlin-annotations]] == Annotations @@ -156,6 +172,9 @@ https://kotlinlang.org/docs/reference/annotations.html#annotation-use-site-targe such as `@field:NotNull` or `@get:Size(min=5, max=15)`, as described in https://stackoverflow.com/a/35853200/1092077[this Stack Overflow response]. + + + [[kotlin-bean-definition-dsl]] == Bean Definition DSL @@ -263,16 +282,20 @@ as the following example shows: } ---- - NOTE: Spring Boot is based on JavaConfig and https://github.com/spring-projects/spring-boot/issues/8115[does not yet provide specific support for functional bean definition], but you can experimentally use functional bean definitions through Spring Boot's `ApplicationContextInitializer` support. See https://stackoverflow.com/questions/45935931/how-to-use-functional-bean-definition-kotlin-dsl-with-spring-boot-and-spring-w/46033685#46033685[this Stack Overflow answer] for more details and up-to-date information. See also the experimental Kofu DSL developed in https://github.com/spring-projects/spring-fu[Spring Fu incubator]. + + + [[kotlin-web]] == Web + + === Router DSL Spring Framework comes with a Kotlin router DSL available in 3 flavors: @@ -314,6 +337,8 @@ when you need to register routes depending on dynamic data (for example, from a See https://github.com/mixitconf/mixit/[MiXiT project] for a concrete example. + + === MockMvc DSL A Kotlin DSL is provided via `MockMvc` Kotlin extensions in order to provide a more @@ -339,6 +364,8 @@ mockMvc.get("/person/{name}", "Lee") { } ---- + + === Kotlin Script Templates Spring Framework provides a @@ -357,9 +384,7 @@ dependencies { } ---- -Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplateViewResolver` -beans. - +Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplateViewResolver` beans. `KotlinScriptConfiguration.kt` [source,kotlin,indent=0] @@ -386,6 +411,8 @@ class KotlinScriptConfiguration { See the https://github.com/sdeleuze/kotlin-script-templating[kotlin-script-templating] example project for more details. + + === Kotlin multiplatform serialization As of Spring Framework 5.3, https://github.com/Kotlin/kotlinx.serialization[Kotlin multiplatform serialization] is @@ -397,6 +424,9 @@ Kotlin serialization is designed to serialize only Kotlin classes annotated with With Spring Messaging (RSocket), make sure that neither Jackson, GSON or JSONB are in the classpath if you want automatic configuration, if Jackson is needed configure `KotlinSerializationJsonMessageConverter` manually. + + + == Coroutines Kotlin https://kotlinlang.org/docs/reference/coroutines-overview.html[Coroutines] are Kotlin @@ -415,6 +445,8 @@ Spring Framework provides support for Coroutines on the following scope: * Suspending function and `Flow` support in RSocket `@MessageMapping` annotated methods * Extensions for {docs-spring-framework}/kdoc-api/spring-messaging/org.springframework.messaging.rsocket/index.html[`RSocketRequester`] + + === Dependencies Coroutines support is enabled when `kotlinx-coroutines-core` and `kotlinx-coroutines-reactor` @@ -432,6 +464,8 @@ dependencies { Version `1.4.0` and above are supported. + + === How Reactive translates to Coroutines? For return values, the translation from Reactive to Coroutines APIs is the following: @@ -458,6 +492,8 @@ https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coro Read this blog post about https://spring.io/blog/2019/04/12/going-reactive-with-spring-coroutines-and-kotlin-flow[Going Reactive with Spring, Coroutines and Kotlin Flow] for more details, including how to run code concurrently with Coroutines. + + === Controllers Here is an example of a Coroutines `@RestController`. @@ -554,6 +590,8 @@ class CoroutinesViewController(banner: Banner) { } ---- + + === WebFlux.fn Here is an example of Coroutines router defined via the {docs-spring-framework}/kdoc-api/spring-webflux/org.springframework.web.reactive.function.server/co-router.html[coRouter { }] DSL and related handlers. @@ -587,6 +625,8 @@ class UserHandler(builder: WebClient.Builder) { } ---- + + === Transactions Transactions on Coroutines are supported via the programmatic variant of the Reactive @@ -636,6 +676,8 @@ For Kotlin `Flow`, a `Flow.transactional` extension is provided. ---- + + [[kotlin-spring-projects-in-kotlin]] == Spring Projects in Kotlin @@ -683,6 +725,8 @@ NOTE: The Kotlin code samples in Spring Framework documentation do not explicitl `open` on the classes and their member functions. The samples are written for projects using the `kotlin-allopen` plugin, since this is the most commonly used setup. + + === Using Immutable Class Instances for Persistence In Kotlin, it is convenient and considered to be a best practice to declare read-only properties @@ -726,6 +770,8 @@ NOTE: As of the Kay release train, Spring Data supports Kotlin immutable class i does not require the `kotlin-noarg` plugin if the module uses Spring Data object mappings (such as MongoDB, Redis, Cassandra, and others). + + === Injecting Dependencies Our recommendation is to try to favor constructor injection with `val` read-only (and @@ -761,6 +807,8 @@ as the following example shows: } ---- + + === Injecting Configuration Properties In Java, you can inject configuration properties by using annotations (such as pass:q[`@Value("${property}")`)]. @@ -801,6 +849,7 @@ that uses the `${...}` syntax, with configuration beans, as the following exampl ---- + === Checked Exceptions Java and https://kotlinlang.org/docs/reference/exceptions.html[Kotlin exception handling] @@ -813,6 +862,8 @@ To get the original exception thrown like in Java, methods should be annotated w https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-throws/index.html[`@Throws`] to specify explicitly the checked exceptions thrown (for example `@Throws(IOException::class)`). + + === Annotation Array Attributes Kotlin annotations are mostly similar to Java annotations, but array attributes (which are @@ -857,6 +908,8 @@ use a shortcut annotation, such as `@GetMapping`, `@PostMapping`, and others. NOTE: If the `@RequestMapping` `method` attribute is not specified, all HTTP methods will be matched, not only the `GET` method. + + === Testing This section addresses testing with the combination of Kotlin and Spring Framework. @@ -866,6 +919,7 @@ https://mockk.io/[Mockk] for mocking. NOTE: If you are using Spring Boot, see https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-kotlin-testing[this related documentation]. + ==== Constructor injection As described in the <>, @@ -887,6 +941,7 @@ class OrderServiceIntegrationTests(val orderService: OrderService, ---- ==== + ==== `PER_CLASS` Lifecycle Kotlin lets you specify meaningful test function names between backticks (```). @@ -930,6 +985,7 @@ class IntegrationTests { } ---- + ==== Specification-like Tests You can create specification-like tests with JUnit 5 and Kotlin. @@ -959,6 +1015,7 @@ class SpecificationLikeTests { } ---- + [[kotlin-webtestclient-issue]] ==== `WebTestClient` Type Inference Issue in Kotlin @@ -968,17 +1025,24 @@ since it provides a workaround for the Kotlin issue with the Java API. See also the related https://jira.spring.io/browse/SPR-16057[SPR-16057] issue. + + + [[kotlin-getting-started]] == Getting Started The easiest way to learn how to build a Spring application with Kotlin is to follow https://spring.io/guides/tutorials/spring-boot-kotlin/[the dedicated tutorial]. + + === `start.spring.io` The easiest way to start a new Spring Framework project in Kotlin is to create a new Spring Boot 2 project on https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io]. + + === Choosing the Web Flavor Spring Framework now comes with two different web stacks: <> and @@ -991,6 +1055,9 @@ Kotlin DSL. For other use cases, especially if you are using blocking technologies such as JPA, Spring MVC and its annotation-based programming model is the recommended choice. + + + [[kotlin-resources]] == Resources @@ -1004,6 +1071,8 @@ Kotlin and the Spring Framework: * https://blog.jetbrains.com/kotlin/[Kotlin blog] * https://kotlin.link/[Awesome Kotlin] + + === Examples The following Github projects offer examples that you can learn from and possibly even extend: @@ -1016,6 +1085,8 @@ The following Github projects offer examples that you can learn from and possibl * https://github.com/sdeleuze/spring-kotlin-deepdive[spring-kotlin-deepdive]: A step-by-step migration guide for Boot 1.0 and Java to Boot 2.0 and Kotlin * https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample[spring-cloud-gcp-kotlin-app-sample]: Spring Boot with Google Cloud Platform Integrations + + === Issues The following list categorizes the pending issues related to Spring and Kotlin support: diff --git a/framework-docs/src/docs/asciidoc/overview.adoc b/framework-docs/src/docs/asciidoc/overview.adoc index 33afeb22909b..7508ad354957 100644 --- a/framework-docs/src/docs/asciidoc/overview.adoc +++ b/framework-docs/src/docs/asciidoc/overview.adoc @@ -1,5 +1,6 @@ [[overview]] = Spring Framework Overview +include::attributes.adoc[] :toc: left :toclevels: 1 :docinfo1: @@ -7,10 +8,8 @@ Spring makes it easy to create Java enterprise applications. It provides everything you need to embrace the Java language in an enterprise environment, with support for Groovy and Kotlin as alternative languages on the JVM, and with the flexibility to create many -kinds of architectures depending on an application's needs. As of Spring Framework 5.1, -Spring requires JDK 8+ (Java SE 8+) and provides out-of-the-box support for JDK 11 LTS. -Java SE 8 update 60 is suggested as the minimum patch release for Java 8, but it is -generally recommended to use a recent patch release. +kinds of architectures depending on an application's needs. As of Spring Framework 6.0, +Spring requires Java 17+. Spring supports a wide range of application scenarios. In a large enterprise, applications often exist for a long time and have to run on a JDK and application server whose upgrade diff --git a/framework-docs/src/docs/asciidoc/page-layout.adoc b/framework-docs/src/docs/asciidoc/page-layout.adoc new file mode 100644 index 000000000000..d7209280b2a6 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/page-layout.adoc @@ -0,0 +1,4 @@ +:toc: left +:toclevels: 4 +:tabsize: 4 +:docinfo1: \ No newline at end of file diff --git a/framework-docs/src/docs/asciidoc/rsocket.adoc b/framework-docs/src/docs/asciidoc/rsocket.adoc index dbdd05c558db..4da739d73c71 100644 --- a/framework-docs/src/docs/asciidoc/rsocket.adoc +++ b/framework-docs/src/docs/asciidoc/rsocket.adoc @@ -1,8 +1,7 @@ [[rsocket]] = RSocket -:gh-rsocket: https://github.com/rsocket -:gh-rsocket-java: {gh-rsocket}/rsocket-java -:gh-rsocket-extensions: {gh-rsocket}/rsocket/blob/master/Extensions +include::attributes.adoc[] +include::page-layout.adoc[] This section describes Spring Framework's support for the RSocket protocol. diff --git a/framework-docs/src/docs/asciidoc/spring-framework.adocbook b/framework-docs/src/docs/asciidoc/spring-framework.adocbook index a3f83c5f93c3..d05f04254f8d 100644 --- a/framework-docs/src/docs/asciidoc/spring-framework.adocbook +++ b/framework-docs/src/docs/asciidoc/spring-framework.adocbook @@ -1,5 +1,17 @@ :noheader: +:toc: +include::attributes.adoc[] = Spring Framework Documentation +Rod Johnson; Juergen Hoeller; Keith Donald; Colin Sampaleanu; Rob Harrop; Thomas Risberg; Alef Arendsen; Darren Davison; Dmitriy Kopylenko; Mark Pollack; Thierry Templier; Erwin Vervaet; Portia Tung; Ben Hale; Adrian Colyer; John Lewis; Costin Leau; Mark Fisher; Sam Brannen; Ramnivas Laddad; Arjen Poutsma; Chris Beams; Tareq Abedrabbo; Andy Clement; Dave Syer; Oliver Gierke; Rossen Stoyanchev; Phillip Webb; Rob Winch; Brian Clozel; Stephane Nicoll; Sebastien Deleuze; Jay Bryant; Mark Paluch + +NOTE: This documentation is also available in {docs-spring-framework}/reference/html/index.html[HTML] format. + +[[legal]] +== Legal + +Copyright © 2002 - 2022 VMware, Inc. All Rights Reserved. + +Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. include::overview.adoc[leveloffset=+1] include::core.adoc[leveloffset=+1] @@ -10,16 +22,3 @@ include::web-reactive.adoc[leveloffset=+1] include::integration.adoc[leveloffset=+1] include::languages.adoc[leveloffset=+1] include::appendix.adoc[leveloffset=+1] - -Rod Johnson, Juergen Hoeller, Keith Donald, Colin Sampaleanu, Rob Harrop, Thomas Risberg, -Alef Arendsen, Darren Davison, Dmitriy Kopylenko, Mark Pollack, Thierry Templier, Erwin -Vervaet, Portia Tung, Ben Hale, Adrian Colyer, John Lewis, Costin Leau, Mark Fisher, Sam -Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clement, Dave -Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane -Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch - -Copyright © 2002 - 2022 VMware, Inc. All Rights Reserved. - -Copies of this document may be made for your own use and for distribution to others, -provided that you do not charge any fee for such copies and further provided that each -copy contains this Copyright Notice, whether distributed in print or electronically. diff --git a/framework-docs/src/docs/asciidoc/testing.adoc b/framework-docs/src/docs/asciidoc/testing.adoc index 2f52225a9efa..941074726c1c 100644 --- a/framework-docs/src/docs/asciidoc/testing.adoc +++ b/framework-docs/src/docs/asciidoc/testing.adoc @@ -1,10 +1,7 @@ [[testing]] = Testing -:doc-spring-boot: {doc-root}/spring-boot/docs/current/reference -:toc: left -:toclevels: 4 -:tabsize: 4 -:docinfo1: +include::attributes.adoc[] +include::page-layout.adoc[] This chapter covers Spring's support for integration testing and best practices for unit testing. The Spring team advocates test-driven development (TDD). The Spring team has @@ -14,8990 +11,20 @@ constructors on classes makes them easier to wire together in a test without hav set up service locator registries and similar structures). +include::testing/testing-introduction.adoc[leveloffset=+1] +include::testing/unit-testing.adoc[leveloffset=+1] -[[testing-introduction]] -== Introduction to Spring Testing +include::testing/integration-testing.adoc[leveloffset=+1] -Testing is an integral part of enterprise software development. This chapter focuses on -the value added by the IoC principle to <> and on the benefits -of the Spring Framework's support for <>. (A -thorough treatment of testing in the enterprise is beyond the scope of this reference -manual.) +include::testing/testing-support-jdbc.adoc[leveloffset=+1] +include::testing/testcontext-framework.adoc[leveloffset=+1] +include::testing/testing-webtestclient.adoc[leveloffset=+1] +include::testing/spring-mvc-test-framework.adoc[leveloffset=+1] -[[unit-testing]] -== Unit Testing +include::testing/spring-mvc-test-client.adoc[leveloffset=+1] -Dependency injection should make your code less dependent on the container than it would -be with traditional J2EE / Java EE development. The POJOs that make up your application -should be testable in JUnit or TestNG tests, with objects instantiated by using the `new` -operator, without Spring or any other container. You can use <> -(in conjunction with other valuable testing techniques) to test your code in isolation. -If you follow the architecture recommendations for Spring, the resulting clean layering -and componentization of your codebase facilitate easier unit testing. For example, -you can test service layer objects by stubbing or mocking DAO or repository interfaces, -without needing to access persistent data while running unit tests. - -True unit tests typically run extremely quickly, as there is no runtime infrastructure to -set up. Emphasizing true unit tests as part of your development methodology can boost -your productivity. You may not need this section of the testing chapter to help you write -effective unit tests for your IoC-based applications. For certain unit testing scenarios, -however, the Spring Framework provides mock objects and testing support classes, which -are described in this chapter. - - - -[[mock-objects]] -=== Mock Objects - -Spring includes a number of packages dedicated to mocking: - -* <> -* <> -* <> -* <> - - -[[mock-objects-env]] -==== Environment - -The `org.springframework.mock.env` package contains mock implementations of the -`Environment` and `PropertySource` abstractions (see -<> -and <>). -`MockEnvironment` and `MockPropertySource` are useful for developing -out-of-container tests for code that depends on environment-specific properties. - - -[[mock-objects-jndi]] -==== JNDI - -The `org.springframework.mock.jndi` package contains a partial implementation of the JNDI -SPI, which you can use to set up a simple JNDI environment for test suites or stand-alone -applications. If, for example, JDBC `DataSource` instances get bound to the same JNDI -names in test code as they do in a Jakarta EE container, you can reuse both application code -and configuration in testing scenarios without modification. - -WARNING: The mock JNDI support in the `org.springframework.mock.jndi` package is -officially deprecated as of Spring Framework 5.2 in favor of complete solutions from third -parties such as https://github.com/h-thurow/Simple-JNDI[Simple-JNDI]. - - -[[mock-objects-servlet]] -==== Servlet API - -The `org.springframework.mock.web` package contains a comprehensive set of Servlet API -mock objects that are useful for testing web contexts, controllers, and filters. These -mock objects are targeted at usage with Spring's Web MVC framework and are generally more -convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock]) -or alternative Servlet API mock objects (such as http://www.mockobjects.com[MockObjects]). - -TIP: Since Spring Framework 5.0, the mock objects in `org.springframework.mock.web` are -based on the Servlet 4.0 API. - -The Spring MVC Test framework builds on the mock Servlet API objects to provide an -integration testing framework for Spring MVC. See <>. - - -[[mock-objects-web-reactive]] -==== Spring Web Reactive - -The `org.springframework.mock.http.server.reactive` package contains mock implementations -of `ServerHttpRequest` and `ServerHttpResponse` for use in WebFlux applications. The -`org.springframework.mock.web.server` package contains a mock `ServerWebExchange` that -depends on those mock request and response objects. - -Both `MockServerHttpRequest` and `MockServerHttpResponse` extend from the same abstract -base classes as server-specific implementations and share behavior with them. For -example, a mock request is immutable once created, but you can use the `mutate()` method -from `ServerHttpRequest` to create a modified instance. - -In order for the mock response to properly implement the write contract and return a -write completion handle (that is, `Mono`), it by default uses a `Flux` with -`cache().then()`, which buffers the data and makes it available for assertions in tests. -Applications can set a custom write function (for example, to test an infinite stream). - -The <> builds on the mock request and response to provide support for -testing WebFlux applications without an HTTP server. The client can also be used for -end-to-end tests with a running server. - - - -[[unit-testing-support-classes]] -=== Unit Testing Support Classes - -Spring includes a number of classes that can help with unit testing. They fall into two -categories: - -* <> -* <> - - -[[unit-testing-utilities]] -==== General Testing Utilities - -The `org.springframework.test.util` package contains several general purpose utilities -for use in unit and integration testing. - -`ReflectionTestUtils` is a collection of reflection-based utility methods. You can use -these methods in testing scenarios where you need to change the value of a constant, set -a non-`public` field, invoke a non-`public` setter method, or invoke a non-`public` -configuration or lifecycle callback method when testing application code for use cases -such as the following: - -* ORM frameworks (such as JPA and Hibernate) that condone `private` or `protected` field - access as opposed to `public` setter methods for properties in a domain entity. -* Spring's support for annotations (such as `@Autowired`, `@Inject`, and `@Resource`), - that provide dependency injection for `private` or `protected` fields, setter methods, - and configuration methods. -* Use of annotations such as `@PostConstruct` and `@PreDestroy` for lifecycle callback - methods. - -{api-spring-framework}/test/util/AopTestUtils.html[`AopTestUtils`] is a collection of -AOP-related utility methods. You can use these methods to obtain a reference to the -underlying target object hidden behind one or more Spring proxies. For example, if you -have configured a bean as a dynamic mock by using a library such as EasyMock or Mockito, -and the mock is wrapped in a Spring proxy, you may need direct access to the underlying -mock to configure expectations on it and perform verifications. For Spring's core AOP -utilities, see {api-spring-framework}/aop/support/AopUtils.html[`AopUtils`] and -{api-spring-framework}/aop/framework/AopProxyUtils.html[`AopProxyUtils`]. - - -[[unit-testing-spring-mvc]] -==== Spring MVC Testing Utilities - -The `org.springframework.test.web` package contains -{api-spring-framework}/test/web/ModelAndViewAssert.html[`ModelAndViewAssert`], which you -can use in combination with JUnit, TestNG, or any other testing framework for unit tests -that deal with Spring MVC `ModelAndView` objects. - -.Unit testing Spring MVC Controllers -TIP: To unit test your Spring MVC `Controller` classes as POJOs, use `ModelAndViewAssert` -combined with `MockHttpServletRequest`, `MockHttpSession`, and so on from Spring's -<>. For thorough integration testing of your -Spring MVC and REST `Controller` classes in conjunction with your `WebApplicationContext` -configuration for Spring MVC, use the -<> instead. - - - - -[[integration-testing]] -== Integration Testing - -This section (most of the rest of this chapter) covers integration testing for Spring -applications. It includes the following topics: - -* <> -* <> -* <> -* <> -* <> -* <> - - - -[[integration-testing-overview]] -=== Overview - -It is important to be able to perform some integration testing without requiring -deployment to your application server or connecting to other enterprise infrastructure. -Doing so lets you test things such as: - -* The correct wiring of your Spring IoC container contexts. -* Data access using JDBC or an ORM tool. This can include such things as the correctness - of SQL statements, Hibernate queries, JPA entity mappings, and so forth. - -The Spring Framework provides first-class support for integration testing in the -`spring-test` module. The name of the actual JAR file might include the release version -and might also be in the long `org.springframework.test` form, depending on where you get -it from (see the <> -for an explanation). This library includes the `org.springframework.test` package, which -contains valuable classes for integration testing with a Spring container. This testing -does not rely on an application server or other deployment environment. Such tests are -slower to run than unit tests but much faster than the equivalent Selenium tests or -remote tests that rely on deployment to an application server. - -Unit and integration testing support is provided in the form of the annotation-driven -<>. The TestContext framework is -agnostic of the actual testing framework in use, which allows instrumentation of tests -in various environments, including JUnit, TestNG, and others. - - - -[[integration-testing-goals]] -=== Goals of Integration Testing - -Spring's integration testing support has the following primary goals: - -* To manage <> between tests. -* To provide <>. -* To provide <> appropriate to integration testing. -* To supply <> that assist - developers in writing integration tests. - -The next few sections describe each goal and provide links to implementation and -configuration details. - - -[[testing-ctx-management]] -==== Context Management and Caching - -The Spring TestContext Framework provides consistent loading of Spring -`ApplicationContext` instances and `WebApplicationContext` instances as well as caching -of those contexts. Support for the caching of loaded contexts is important, because -startup time can become an issue -- not because of the overhead of Spring itself, but -because the objects instantiated by the Spring container take time to instantiate. For -example, a project with 50 to 100 Hibernate mapping files might take 10 to 20 seconds to -load the mapping files, and incurring that cost before running every test in every test -fixture leads to slower overall test runs that reduce developer productivity. - -Test classes typically declare either an array of resource locations for XML or Groovy -configuration metadata -- often in the classpath -- or an array of component classes that -is used to configure the application. These locations or classes are the same as or -similar to those specified in `web.xml` or other configuration files for production -deployments. - -By default, once loaded, the configured `ApplicationContext` is reused for each test. -Thus, the setup cost is incurred only once per test suite, and subsequent test execution -is much faster. In this context, the term "`test suite`" means all tests run in the same -JVM -- for example, all tests run from an Ant, Maven, or Gradle build for a given project -or module. In the unlikely case that a test corrupts the application context and requires -reloading (for example, by modifying a bean definition or the state of an application -object) the TestContext framework can be configured to reload the configuration and -rebuild the application context before executing the next test. - -See <> and <> with the -TestContext framework. - - -[[testing-fixture-di]] -==== Dependency Injection of Test Fixtures - -When the TestContext framework loads your application context, it can optionally -configure instances of your test classes by using Dependency Injection. This provides a -convenient mechanism for setting up test fixtures by using preconfigured beans from your -application context. A strong benefit here is that you can reuse application contexts -across various testing scenarios (for example, for configuring Spring-managed object -graphs, transactional proxies, `DataSource` instances, and others), thus avoiding the -need to duplicate complex test fixture setup for individual test cases. - -As an example, consider a scenario where we have a class (`HibernateTitleRepository`) -that implements data access logic for a `Title` domain entity. We want to write -integration tests that test the following areas: - -* The Spring configuration: Basically, is everything related to the configuration of the - `HibernateTitleRepository` bean correct and present? -* The Hibernate mapping file configuration: Is everything mapped correctly and are the - correct lazy-loading settings in place? -* The logic of the `HibernateTitleRepository`: Does the configured instance of this class - perform as anticipated? - -See dependency injection of test fixtures with the -<>. - - -[[testing-tx]] -==== Transaction Management - -One common issue in tests that access a real database is their effect on the state of the -persistence store. Even when you use a development database, changes to the state may -affect future tests. Also, many operations -- such as inserting or modifying persistent -data -- cannot be performed (or verified) outside of a transaction. - -The TestContext framework addresses this issue. By default, the framework creates and -rolls back a transaction for each test. You can write code that can assume the existence -of a transaction. If you call transactionally proxied objects in your tests, they behave -correctly, according to their configured transactional semantics. In addition, if a test -method deletes the contents of selected tables while running within the transaction -managed for the test, the transaction rolls back by default, and the database returns to -its state prior to execution of the test. Transactional support is provided to a test by -using a `PlatformTransactionManager` bean defined in the test's application context. - -If you want a transaction to commit (unusual, but occasionally useful when you want a -particular test to populate or modify the database), you can tell the TestContext -framework to cause the transaction to commit instead of roll back by using the -<> annotation. - -See transaction management with the <>. - - -[[testing-support-classes]] -==== Support Classes for Integration Testing - -The Spring TestContext Framework provides several `abstract` support classes that -simplify the writing of integration tests. These base test classes provide well-defined -hooks into the testing framework as well as convenient instance variables and methods, -which let you access: - -* The `ApplicationContext`, for performing explicit bean lookups or testing the state of - the context as a whole. -* A `JdbcTemplate`, for executing SQL statements to query the database. You can use such - queries to confirm database state both before and after execution of database-related - application code, and Spring ensures that such queries run in the scope of the same - transaction as the application code. When used in conjunction with an ORM tool, be sure - to avoid <>. - -In addition, you may want to create your own custom, application-wide superclass with -instance variables and methods specific to your project. - -See support classes for the <>. - - - -[[integration-testing-support-jdbc]] -=== JDBC Testing Support - -The `org.springframework.test.jdbc` package contains `JdbcTestUtils`, which is a -collection of JDBC-related utility functions intended to simplify standard database -testing scenarios. Specifically, `JdbcTestUtils` provides the following static utility -methods. - -* `countRowsInTable(..)`: Counts the number of rows in the given table. -* `countRowsInTableWhere(..)`: Counts the number of rows in the given table by using the - provided `WHERE` clause. -* `deleteFromTables(..)`: Deletes all rows from the specified tables. -* `deleteFromTableWhere(..)`: Deletes rows from the given table by using the provided - `WHERE` clause. -* `dropTables(..)`: Drops the specified tables. - -[TIP] -==== -<> -and <> -provide convenience methods that delegate to the aforementioned methods in -`JdbcTestUtils`. - -The `spring-jdbc` module provides support for configuring and launching an embedded -database, which you can use in integration tests that interact with a database. -For details, see <> and <>. -==== - - - -[[integration-testing-annotations]] -=== Annotations - -This section covers annotations that you can use when you test Spring applications. -It includes the following topics: - -* <> -* <> -* <> -* <> -* <> - - -[[integration-testing-annotations-spring]] -==== Spring Testing Annotations - -The Spring Framework provides the following set of Spring-specific annotations that you -can use in your unit and integration tests in conjunction with the TestContext framework. -See the corresponding javadoc for further information, including default attribute -values, attribute aliases, and other details. - -Spring's testing annotations include the following: - -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> - -[[spring-testing-annotation-bootstrapwith]] -===== `@BootstrapWith` - -`@BootstrapWith` is a class-level annotation that you can use to configure how the Spring -TestContext Framework is bootstrapped. Specifically, you can use `@BootstrapWith` to -specify a custom `TestContextBootstrapper`. See the section on -<> for further details. - -[[spring-testing-annotation-contextconfiguration]] -===== `@ContextConfiguration` - -`@ContextConfiguration` defines class-level metadata that is used to determine how to -load and configure an `ApplicationContext` for integration tests. Specifically, -`@ContextConfiguration` declares the application context resource `locations` or the -component `classes` used to load the context. - -Resource locations are typically XML configuration files or Groovy scripts located in the -classpath, while component classes are typically `@Configuration` classes. However, -resource locations can also refer to files and scripts in the file system, and component -classes can be `@Component` classes, `@Service` classes, and so on. See -<> for further details. - -The following example shows a `@ContextConfiguration` annotation that refers to an XML -file: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration("/test-config.xml") // <1> - class XmlApplicationContextTests { - // class body... - } ----- -<1> Referring to an XML file. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration("/test-config.xml") // <1> - class XmlApplicationContextTests { - // class body... - } ----- -<1> Referring to an XML file. - - -The following example shows a `@ContextConfiguration` annotation that refers to a class: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration(classes = TestConfig.class) // <1> - class ConfigClassApplicationContextTests { - // class body... - } ----- -<1> Referring to a class. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration(classes = [TestConfig::class]) // <1> - class ConfigClassApplicationContextTests { - // class body... - } ----- -<1> Referring to a class. - - -As an alternative or in addition to declaring resource locations or component classes, -you can use `@ContextConfiguration` to declare `ApplicationContextInitializer` classes. -The following example shows such a case: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration(initializers = CustomContextInitializer.class) // <1> - class ContextInitializerTests { - // class body... - } ----- -<1> Declaring an initializer class. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration(initializers = [CustomContextInitializer::class]) // <1> - class ContextInitializerTests { - // class body... - } ----- -<1> Declaring an initializer class. - - -You can optionally use `@ContextConfiguration` to declare the `ContextLoader` strategy as -well. Note, however, that you typically do not need to explicitly configure the loader, -since the default loader supports `initializers` and either resource `locations` or -component `classes`. - -The following example uses both a location and a loader: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) // <1> - class CustomLoaderXmlApplicationContextTests { - // class body... - } ----- -<1> Configuring both a location and a custom loader. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration("/test-context.xml", loader = CustomContextLoader::class) // <1> - class CustomLoaderXmlApplicationContextTests { - // class body... - } ----- -<1> Configuring both a location and a custom loader. - - -NOTE: `@ContextConfiguration` provides support for inheriting resource locations or -configuration classes as well as context initializers that are declared by superclasses -or enclosing classes. - -See <>, -<>, and the `@ContextConfiguration` -javadocs for further details. - -[[spring-testing-annotation-webappconfiguration]] -===== `@WebAppConfiguration` - -`@WebAppConfiguration` is a class-level annotation that you can use to declare that the -`ApplicationContext` loaded for an integration test should be a `WebApplicationContext`. -The mere presence of `@WebAppConfiguration` on a test class ensures that a -`WebApplicationContext` is loaded for the test, using the default value of -`"file:src/main/webapp"` for the path to the root of the web application (that is, the -resource base path). The resource base path is used behind the scenes to create a -`MockServletContext`, which serves as the `ServletContext` for the test's -`WebApplicationContext`. - -The following example shows how to use the `@WebAppConfiguration` annotation: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @WebAppConfiguration // <1> - class WebAppTests { - // class body... - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @WebAppConfiguration // <1> - class WebAppTests { - // class body... - } ----- -<1> The `@WebAppConfiguration` annotation. - - -To override the default, you can specify a different base resource path by using the -implicit `value` attribute. Both `classpath:` and `file:` resource prefixes are -supported. If no resource prefix is supplied, the path is assumed to be a file system -resource. The following example shows how to specify a classpath resource: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @WebAppConfiguration("classpath:test-web-resources") // <1> - class WebAppTests { - // class body... - } ----- -<1> Specifying a classpath resource. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @WebAppConfiguration("classpath:test-web-resources") // <1> - class WebAppTests { - // class body... - } ----- -<1> Specifying a classpath resource. - - -Note that `@WebAppConfiguration` must be used in conjunction with -`@ContextConfiguration`, either within a single test class or within a test class -hierarchy. See the -{api-spring-framework}/test/context/web/WebAppConfiguration.html[`@WebAppConfiguration`] -javadoc for further details. - -[[spring-testing-annotation-contexthierarchy]] -===== `@ContextHierarchy` - -`@ContextHierarchy` is a class-level annotation that is used to define a hierarchy of -`ApplicationContext` instances for integration tests. `@ContextHierarchy` should be -declared with a list of one or more `@ContextConfiguration` instances, each of which -defines a level in the context hierarchy. The following examples demonstrate the use of -`@ContextHierarchy` within a single test class (`@ContextHierarchy` can also be used -within a test class hierarchy): - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextHierarchy({ - @ContextConfiguration("/parent-config.xml"), - @ContextConfiguration("/child-config.xml") - }) - class ContextHierarchyTests { - // class body... - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextHierarchy( - ContextConfiguration("/parent-config.xml"), - ContextConfiguration("/child-config.xml")) - class ContextHierarchyTests { - // class body... - } ----- - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @WebAppConfiguration - @ContextHierarchy({ - @ContextConfiguration(classes = AppConfig.class), - @ContextConfiguration(classes = WebConfig.class) - }) - class WebIntegrationTests { - // class body... - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @WebAppConfiguration - @ContextHierarchy( - ContextConfiguration(classes = [AppConfig::class]), - ContextConfiguration(classes = [WebConfig::class])) - class WebIntegrationTests { - // class body... - } ----- - -If you need to merge or override the configuration for a given level of the context -hierarchy within a test class hierarchy, you must explicitly name that level by supplying -the same value to the `name` attribute in `@ContextConfiguration` at each corresponding -level in the class hierarchy. See <> and the -{api-spring-framework}/test/context/ContextHierarchy.html[`@ContextHierarchy`] javadoc -for further examples. - -[[spring-testing-annotation-activeprofiles]] -===== `@ActiveProfiles` - -`@ActiveProfiles` is a class-level annotation that is used to declare which bean -definition profiles should be active when loading an `ApplicationContext` for an -integration test. - -The following example indicates that the `dev` profile should be active: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @ActiveProfiles("dev") // <1> - class DeveloperTests { - // class body... - } ----- -<1> Indicate that the `dev` profile should be active. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @ActiveProfiles("dev") // <1> - class DeveloperTests { - // class body... - } ----- -<1> Indicate that the `dev` profile should be active. - - -The following example indicates that both the `dev` and the `integration` profiles should -be active: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @ActiveProfiles({"dev", "integration"}) // <1> - class DeveloperIntegrationTests { - // class body... - } ----- -<1> Indicate that the `dev` and `integration` profiles should be active. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @ActiveProfiles(["dev", "integration"]) // <1> - class DeveloperIntegrationTests { - // class body... - } ----- -<1> Indicate that the `dev` and `integration` profiles should be active. - - -NOTE: `@ActiveProfiles` provides support for inheriting active bean definition profiles -declared by superclasses and enclosing classes by default. You can also resolve active -bean definition profiles programmatically by implementing a custom -<> -and registering it by using the `resolver` attribute of `@ActiveProfiles`. - -See <>, -<>, and the -{api-spring-framework}/test/context/ActiveProfiles.html[`@ActiveProfiles`] javadoc for -examples and further details. - -[[spring-testing-annotation-testpropertysource]] -===== `@TestPropertySource` - -`@TestPropertySource` is a class-level annotation that you can use to configure the -locations of properties files and inlined properties to be added to the set of -`PropertySources` in the `Environment` for an `ApplicationContext` loaded for an -integration test. - -The following example demonstrates how to declare a properties file from the classpath: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @TestPropertySource("/test.properties") // <1> - class MyIntegrationTests { - // class body... - } ----- -<1> Get properties from `test.properties` in the root of the classpath. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @TestPropertySource("/test.properties") // <1> - class MyIntegrationTests { - // class body... - } ----- -<1> Get properties from `test.properties` in the root of the classpath. - - -The following example demonstrates how to declare inlined properties: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) // <1> - class MyIntegrationTests { - // class body... - } ----- -<1> Declare `timezone` and `port` properties. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) // <1> - class MyIntegrationTests { - // class body... - } ----- -<1> Declare `timezone` and `port` properties. - -See <> for examples and further details. - -[[spring-testing-annotation-dynamicpropertysource]] -===== `@DynamicPropertySource` - -`@DynamicPropertySource` is a method-level annotation that you can use to register -_dynamic_ properties to be added to the set of `PropertySources` in the `Environment` for -an `ApplicationContext` loaded for an integration test. Dynamic properties are useful -when you do not know the value of the properties upfront – for example, if the properties -are managed by an external resource such as for a container managed by the -https://www.testcontainers.org/[Testcontainers] project. - -The following example demonstrates how to register a dynamic property: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - class MyIntegrationTests { - - static MyExternalServer server = // ... - - @DynamicPropertySource // <1> - static void dynamicProperties(DynamicPropertyRegistry registry) { // <2> - registry.add("server.port", server::getPort); // <3> - } - - // tests ... - } ----- -<1> Annotate a `static` method with `@DynamicPropertySource`. -<2> Accept a `DynamicPropertyRegistry` as an argument. -<3> Register a dynamic `server.port` property to be retrieved lazily from the server. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - class MyIntegrationTests { - - companion object { - - @JvmStatic - val server: MyExternalServer = // ... - - @DynamicPropertySource // <1> - @JvmStatic - fun dynamicProperties(registry: DynamicPropertyRegistry) { // <2> - registry.add("server.port", server::getPort) // <3> - } - } - - // tests ... - } ----- -<1> Annotate a `static` method with `@DynamicPropertySource`. -<2> Accept a `DynamicPropertyRegistry` as an argument. -<3> Register a dynamic `server.port` property to be retrieved lazily from the server. - -See <> for further details. - -[[spring-testing-annotation-dirtiescontext]] -===== `@DirtiesContext` - -`@DirtiesContext` indicates that the underlying Spring `ApplicationContext` has been -dirtied during the execution of a test (that is, the test modified or corrupted it in -some manner -- for example, by changing the state of a singleton bean) and should be -closed. When an application context is marked as dirty, it is removed from the testing -framework's cache and closed. As a consequence, the underlying Spring container is -rebuilt for any subsequent test that requires a context with the same configuration -metadata. - -You can use `@DirtiesContext` as both a class-level and a method-level annotation within -the same class or class hierarchy. In such scenarios, the `ApplicationContext` is marked -as dirty before or after any such annotated method as well as before or after the current -test class, depending on the configured `methodMode` and `classMode`. - -The following examples explain when the context would be dirtied for various -configuration scenarios: - -* Before the current test class, when declared on a class with class mode set to -`BEFORE_CLASS`. -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @DirtiesContext(classMode = BEFORE_CLASS) // <1> - class FreshContextTests { - // some tests that require a new Spring container - } ----- -<1> Dirty the context before the current test class. -+ -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @DirtiesContext(classMode = BEFORE_CLASS) // <1> - class FreshContextTests { - // some tests that require a new Spring container - } ----- -<1> Dirty the context before the current test class. - -* After the current test class, when declared on a class with class mode set to -`AFTER_CLASS` (i.e., the default class mode). -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @DirtiesContext // <1> - class ContextDirtyingTests { - // some tests that result in the Spring container being dirtied - } ----- -<1> Dirty the context after the current test class. -+ -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @DirtiesContext // <1> - class ContextDirtyingTests { - // some tests that result in the Spring container being dirtied - } ----- -<1> Dirty the context after the current test class. - - -* Before each test method in the current test class, when declared on a class with class -mode set to `BEFORE_EACH_TEST_METHOD.` -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) // <1> - class FreshContextTests { - // some tests that require a new Spring container - } ----- -<1> Dirty the context before each test method. -+ -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) // <1> - class FreshContextTests { - // some tests that require a new Spring container - } ----- -<1> Dirty the context before each test method. - - -* After each test method in the current test class, when declared on a class with class -mode set to `AFTER_EACH_TEST_METHOD.` -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) // <1> - class ContextDirtyingTests { - // some tests that result in the Spring container being dirtied - } ----- -<1> Dirty the context after each test method. -+ -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) // <1> - class ContextDirtyingTests { - // some tests that result in the Spring container being dirtied - } ----- -<1> Dirty the context after each test method. - - -* Before the current test, when declared on a method with the method mode set to -`BEFORE_METHOD`. -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @DirtiesContext(methodMode = BEFORE_METHOD) // <1> - @Test - void testProcessWhichRequiresFreshAppCtx() { - // some logic that requires a new Spring container - } ----- -<1> Dirty the context before the current test method. -+ -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @DirtiesContext(methodMode = BEFORE_METHOD) // <1> - @Test - fun testProcessWhichRequiresFreshAppCtx() { - // some logic that requires a new Spring container - } ----- -<1> Dirty the context before the current test method. - -* After the current test, when declared on a method with the method mode set to -`AFTER_METHOD` (i.e., the default method mode). -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @DirtiesContext // <1> - @Test - void testProcessWhichDirtiesAppCtx() { - // some logic that results in the Spring container being dirtied - } ----- -<1> Dirty the context after the current test method. -+ -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @DirtiesContext // <1> - @Test - fun testProcessWhichDirtiesAppCtx() { - // some logic that results in the Spring container being dirtied - } ----- -<1> Dirty the context after the current test method. - - -If you use `@DirtiesContext` in a test whose context is configured as part of a context -hierarchy with `@ContextHierarchy`, you can use the `hierarchyMode` flag to control how -the context cache is cleared. By default, an exhaustive algorithm is used to clear the -context cache, including not only the current level but also all other context -hierarchies that share an ancestor context common to the current test. All -`ApplicationContext` instances that reside in a sub-hierarchy of the common ancestor -context are removed from the context cache and closed. If the exhaustive algorithm is -overkill for a particular use case, you can specify the simpler current level algorithm, -as the following example shows. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextHierarchy({ - @ContextConfiguration("/parent-config.xml"), - @ContextConfiguration("/child-config.xml") - }) - class BaseTests { - // class body... - } - - class ExtendedTests extends BaseTests { - - @Test - @DirtiesContext(hierarchyMode = CURRENT_LEVEL) // <1> - void test() { - // some logic that results in the child context being dirtied - } - } ----- -<1> Use the current-level algorithm. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextHierarchy( - ContextConfiguration("/parent-config.xml"), - ContextConfiguration("/child-config.xml")) - open class BaseTests { - // class body... - } - - class ExtendedTests : BaseTests() { - - @Test - @DirtiesContext(hierarchyMode = CURRENT_LEVEL) // <1> - fun test() { - // some logic that results in the child context being dirtied - } - } ----- -<1> Use the current-level algorithm. - - -For further details regarding the `EXHAUSTIVE` and `CURRENT_LEVEL` algorithms, see the -{api-spring-framework}/test/annotation/DirtiesContext.HierarchyMode.html[`DirtiesContext.HierarchyMode`] -javadoc. - -[[spring-testing-annotation-testexecutionlisteners]] -===== `@TestExecutionListeners` - -`@TestExecutionListeners` is used to register listeners for a particular test class, its -subclasses, and its nested classes. If you wish to register a listener globally, you -should register it via the automatic discovery mechanism described in -<>. - -The following example shows how to register two `TestExecutionListener` implementations: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) // <1> - class CustomTestExecutionListenerTests { - // class body... - } ----- -<1> Register two `TestExecutionListener` implementations. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) // <1> - class CustomTestExecutionListenerTests { - // class body... - } ----- -<1> Register two `TestExecutionListener` implementations. - - -By default, `@TestExecutionListeners` provides support for inheriting listeners from -superclasses or enclosing classes. See -<> and the -{api-spring-framework}/test/context/TestExecutionListeners.html[`@TestExecutionListeners` -javadoc] for an example and further details. If you discover that you need to switch -back to using the default `TestExecutionListener` implementations, see the note -in <>. - -[[spring-testing-annotation-recordapplicationevents]] -===== `@RecordApplicationEvents` - -`@RecordApplicationEvents` is a class-level annotation that is used to instruct the -_Spring TestContext Framework_ to record all application events that are published in the -`ApplicationContext` during the execution of a single test. - -The recorded events can be accessed via the `ApplicationEvents` API within tests. - -See <> and the -{api-spring-framework}/test/context/event/RecordApplicationEvents.html[`@RecordApplicationEvents` -javadoc] for an example and further details. - -[[spring-testing-annotation-commit]] -===== `@Commit` - -`@Commit` indicates that the transaction for a transactional test method should be -committed after the test method has completed. You can use `@Commit` as a direct -replacement for `@Rollback(false)` to more explicitly convey the intent of the code. -Analogous to `@Rollback`, `@Commit` can also be declared as a class-level or method-level -annotation. - -The following example shows how to use the `@Commit` annotation: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Commit // <1> - @Test - void testProcessWithoutRollback() { - // ... - } ----- -<1> Commit the result of the test to the database. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Commit // <1> - @Test - fun testProcessWithoutRollback() { - // ... - } ----- -<1> Commit the result of the test to the database. - - -[[spring-testing-annotation-rollback]] -===== `@Rollback` - -`@Rollback` indicates whether the transaction for a transactional test method should be -rolled back after the test method has completed. If `true`, the transaction is rolled -back. Otherwise, the transaction is committed (see also -<>). Rollback for integration tests in the Spring -TestContext Framework defaults to `true` even if `@Rollback` is not explicitly declared. - -When declared as a class-level annotation, `@Rollback` defines the default rollback -semantics for all test methods within the test class hierarchy. When declared as a -method-level annotation, `@Rollback` defines rollback semantics for the specific test -method, potentially overriding class-level `@Rollback` or `@Commit` semantics. - -The following example causes a test method's result to not be rolled back (that is, the -result is committed to the database): - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Rollback(false) // <1> - @Test - void testProcessWithoutRollback() { - // ... - } ----- -<1> Do not roll back the result. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Rollback(false) // <1> - @Test - fun testProcessWithoutRollback() { - // ... - } ----- -<1> Do not roll back the result. - - -[[spring-testing-annotation-beforetransaction]] -===== `@BeforeTransaction` - -`@BeforeTransaction` indicates that the annotated `void` method should be run before a -transaction is started, for test methods that have been configured to run within a -transaction by using Spring's `@Transactional` annotation. `@BeforeTransaction` methods -are not required to be `public` and may be declared on Java 8-based interface default -methods. - -The following example shows how to use the `@BeforeTransaction` annotation: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @BeforeTransaction // <1> - void beforeTransaction() { - // logic to be run before a transaction is started - } ----- -<1> Run this method before a transaction. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @BeforeTransaction // <1> - fun beforeTransaction() { - // logic to be run before a transaction is started - } ----- -<1> Run this method before a transaction. - - -[[spring-testing-annotation-aftertransaction]] -===== `@AfterTransaction` - -`@AfterTransaction` indicates that the annotated `void` method should be run after a -transaction is ended, for test methods that have been configured to run within a -transaction by using Spring's `@Transactional` annotation. `@AfterTransaction` methods -are not required to be `public` and may be declared on Java 8-based interface default -methods. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @AfterTransaction // <1> - void afterTransaction() { - // logic to be run after a transaction has ended - } ----- -<1> Run this method after a transaction. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @AfterTransaction // <1> - fun afterTransaction() { - // logic to be run after a transaction has ended - } ----- -<1> Run this method after a transaction. - - -[[spring-testing-annotation-sql]] -===== `@Sql` - -`@Sql` is used to annotate a test class or test method to configure SQL scripts to be run -against a given database during integration tests. The following example shows how to use -it: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Test - @Sql({"/test-schema.sql", "/test-user-data.sql"}) // <1> - void userTest() { - // run code that relies on the test schema and test data - } ----- -<1> Run two scripts for this test. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Test - @Sql("/test-schema.sql", "/test-user-data.sql") // <1> - fun userTest() { - // run code that relies on the test schema and test data - } ----- -<1> Run two scripts for this test. - -See <> for further details. - - -[[spring-testing-annotation-sqlconfig]] -===== `@SqlConfig` - -`@SqlConfig` defines metadata that is used to determine how to parse and run SQL scripts -configured with the `@Sql` annotation. The following example shows how to use it: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Test - @Sql( - scripts = "/test-user-data.sql", - config = @SqlConfig(commentPrefix = "`", separator = "@@") // <1> - ) - void userTest() { - // run code that relies on the test data - } ----- -<1> Set the comment prefix and the separator in SQL scripts. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Test - @Sql("/test-user-data.sql", config = SqlConfig(commentPrefix = "`", separator = "@@")) // <1> - fun userTest() { - // run code that relies on the test data - } ----- -<1> Set the comment prefix and the separator in SQL scripts. - -[[spring-testing-annotation-sqlmergemode]] -===== `@SqlMergeMode` - -`@SqlMergeMode` is used to annotate a test class or test method to configure whether -method-level `@Sql` declarations are merged with class-level `@Sql` declarations. If -`@SqlMergeMode` is not declared on a test class or test method, the `OVERRIDE` merge mode -will be used by default. With the `OVERRIDE` mode, method-level `@Sql` declarations will -effectively override class-level `@Sql` declarations. - -Note that a method-level `@SqlMergeMode` declaration overrides a class-level declaration. - -The following example shows how to use `@SqlMergeMode` at the class level. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(TestConfig.class) - @Sql("/test-schema.sql") - @SqlMergeMode(MERGE) // <1> - class UserTests { - - @Test - @Sql("/user-test-data-001.sql") - void standardUserProfile() { - // run code that relies on test data set 001 - } - } ----- -<1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(TestConfig::class) - @Sql("/test-schema.sql") - @SqlMergeMode(MERGE) // <1> - class UserTests { - - @Test - @Sql("/user-test-data-001.sql") - fun standardUserProfile() { - // run code that relies on test data set 001 - } - } ----- -<1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class. - -The following example shows how to use `@SqlMergeMode` at the method level. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(TestConfig.class) - @Sql("/test-schema.sql") - class UserTests { - - @Test - @Sql("/user-test-data-001.sql") - @SqlMergeMode(MERGE) // <1> - void standardUserProfile() { - // run code that relies on test data set 001 - } - } ----- -<1> Set the `@Sql` merge mode to `MERGE` for a specific test method. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(TestConfig::class) - @Sql("/test-schema.sql") - class UserTests { - - @Test - @Sql("/user-test-data-001.sql") - @SqlMergeMode(MERGE) // <1> - fun standardUserProfile() { - // run code that relies on test data set 001 - } - } ----- -<1> Set the `@Sql` merge mode to `MERGE` for a specific test method. - - -[[spring-testing-annotation-sqlgroup]] -===== `@SqlGroup` - -`@SqlGroup` is a container annotation that aggregates several `@Sql` annotations. You can -use `@SqlGroup` natively to declare several nested `@Sql` annotations, or you can use it -in conjunction with Java 8's support for repeatable annotations, where `@Sql` can be -declared several times on the same class or method, implicitly generating this container -annotation. The following example shows how to declare an SQL group: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Test - @SqlGroup({ // <1> - @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")), - @Sql("/test-user-data.sql") - )} - void userTest() { - // run code that uses the test schema and test data - } ----- -<1> Declare a group of SQL scripts. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Test - @SqlGroup( // <1> - Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")), - Sql("/test-user-data.sql")) - fun userTest() { - // run code that uses the test schema and test data - } ----- -<1> Declare a group of SQL scripts. - -[[integration-testing-annotations-standard]] -==== Standard Annotation Support - -The following annotations are supported with standard semantics for all configurations of -the Spring TestContext Framework. Note that these annotations are not specific to tests -and can be used anywhere in the Spring Framework. - -* `@Autowired` -* `@Qualifier` -* `@Value` -* `@Resource` (jakarta.annotation) if JSR-250 is present -* `@ManagedBean` (jakarta.annotation) if JSR-250 is present -* `@Inject` (jakarta.inject) if JSR-330 is present -* `@Named` (jakarta.inject) if JSR-330 is present -* `@PersistenceContext` (jakarta.persistence) if JPA is present -* `@PersistenceUnit` (jakarta.persistence) if JPA is present -* `@Transactional` (org.springframework.transaction.annotation) - _with <>_ - -.JSR-250 Lifecycle Annotations -[NOTE] -==== -In the Spring TestContext Framework, you can use `@PostConstruct` and `@PreDestroy` with -standard semantics on any application components configured in the `ApplicationContext`. -However, these lifecycle annotations have limited usage within an actual test class. - -If a method within a test class is annotated with `@PostConstruct`, that method runs -before any before methods of the underlying test framework (for example, methods -annotated with JUnit Jupiter's `@BeforeEach`), and that applies for every test method in -the test class. On the other hand, if a method within a test class is annotated with -`@PreDestroy`, that method never runs. Therefore, within a test class, we recommend that -you use test lifecycle callbacks from the underlying test framework instead of -`@PostConstruct` and `@PreDestroy`. -==== - - -[[integration-testing-annotations-junit4]] -==== Spring JUnit 4 Testing Annotations - -The following annotations are supported only when used in conjunction with the -<>, <>, or <>: - -* <> -* <> -* <> -* <> - -[[integration-testing-annotations-junit4-ifprofilevalue]] -===== `@IfProfileValue` - -`@IfProfileValue` indicates that the annotated test is enabled for a specific testing -environment. If the configured `ProfileValueSource` returns a matching `value` for the -provided `name`, the test is enabled. Otherwise, the test is disabled and, effectively, -ignored. - -You can apply `@IfProfileValue` at the class level, the method level, or both. -Class-level usage of `@IfProfileValue` takes precedence over method-level usage for any -methods within that class or its subclasses. Specifically, a test is enabled if it is -enabled both at the class level and at the method level. The absence of `@IfProfileValue` -means the test is implicitly enabled. This is analogous to the semantics of JUnit 4's -`@Ignore` annotation, except that the presence of `@Ignore` always disables a test. - -The following example shows a test that has an `@IfProfileValue` annotation: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @IfProfileValue(name="java.vendor", value="Oracle Corporation") // <1> - @Test - public void testProcessWhichRunsOnlyOnOracleJvm() { - // some logic that should run only on Java VMs from Oracle Corporation - } ----- -<1> Run this test only when the Java vendor is "Oracle Corporation". - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @IfProfileValue(name="java.vendor", value="Oracle Corporation") // <1> - @Test - fun testProcessWhichRunsOnlyOnOracleJvm() { - // some logic that should run only on Java VMs from Oracle Corporation - } ----- -<1> Run this test only when the Java vendor is "Oracle Corporation". - - -Alternatively, you can configure `@IfProfileValue` with a list of `values` (with `OR` -semantics) to achieve TestNG-like support for test groups in a JUnit 4 environment. -Consider the following example: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) // <1> - @Test - public void testProcessWhichRunsForUnitOrIntegrationTestGroups() { - // some logic that should run only for unit and integration test groups - } ----- -<1> Run this test for unit tests and integration tests. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @IfProfileValue(name="test-groups", values=["unit-tests", "integration-tests"]) // <1> - @Test - fun testProcessWhichRunsForUnitOrIntegrationTestGroups() { - // some logic that should run only for unit and integration test groups - } ----- -<1> Run this test for unit tests and integration tests. - - -[[integration-testing-annotations-junit4-profilevaluesourceconfiguration]] -===== `@ProfileValueSourceConfiguration` - -`@ProfileValueSourceConfiguration` is a class-level annotation that specifies what type -of `ProfileValueSource` to use when retrieving profile values configured through the -`@IfProfileValue` annotation. If `@ProfileValueSourceConfiguration` is not declared for a -test, `SystemProfileValueSource` is used by default. The following example shows how to -use `@ProfileValueSourceConfiguration`: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ProfileValueSourceConfiguration(CustomProfileValueSource.class) // <1> - public class CustomProfileValueSourceTests { - // class body... - } ----- -<1> Use a custom profile value source. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ProfileValueSourceConfiguration(CustomProfileValueSource::class) // <1> - class CustomProfileValueSourceTests { - // class body... - } ----- -<1> Use a custom profile value source. - - -[[integration-testing-annotations-junit4-timed]] -===== `@Timed` - -`@Timed` indicates that the annotated test method must finish execution in a specified -time period (in milliseconds). If the text execution time exceeds the specified time -period, the test fails. - -The time period includes running the test method itself, any repetitions of the test (see -`@Repeat`), as well as any setting up or tearing down of the test fixture. The following -example shows how to use it: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Timed(millis = 1000) // <1> - public void testProcessWithOneSecondTimeout() { - // some logic that should not take longer than 1 second to run - } ----- -<1> Set the time period for the test to one second. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Timed(millis = 1000) // <1> - fun testProcessWithOneSecondTimeout() { - // some logic that should not take longer than 1 second to run - } ----- -<1> Set the time period for the test to one second. - - -Spring's `@Timed` annotation has different semantics than JUnit 4's `@Test(timeout=...)` -support. Specifically, due to the manner in which JUnit 4 handles test execution timeouts -(that is, by executing the test method in a separate `Thread`), `@Test(timeout=...)` -preemptively fails the test if the test takes too long. Spring's `@Timed`, on the other -hand, does not preemptively fail the test but rather waits for the test to complete -before failing. - -[[integration-testing-annotations-junit4-repeat]] -===== `@Repeat` - -`@Repeat` indicates that the annotated test method must be run repeatedly. The number of -times that the test method is to be run is specified in the annotation. - -The scope of execution to be repeated includes execution of the test method itself as -well as any setting up or tearing down of the test fixture. When used with the -<>, the scope additionally includes -preparation of the test instance by `TestExecutionListener` implementations. The -following example shows how to use the `@Repeat` annotation: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Repeat(10) // <1> - @Test - public void testProcessRepeatedly() { - // ... - } ----- -<1> Repeat this test ten times. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Repeat(10) // <1> - @Test - fun testProcessRepeatedly() { - // ... - } ----- -<1> Repeat this test ten times. - - - -[[integration-testing-annotations-junit-jupiter]] -==== Spring JUnit Jupiter Testing Annotations - -The following annotations are supported when used in conjunction with the -<> and JUnit Jupiter -(that is, the programming model in JUnit 5): - -* <> -* <> -* <> -* <> -* <> -* <> - -[[integration-testing-annotations-junit-jupiter-springjunitconfig]] -===== `@SpringJUnitConfig` - -`@SpringJUnitConfig` is a composed annotation that combines -`@ExtendWith(SpringExtension.class)` from JUnit Jupiter with `@ContextConfiguration` from -the Spring TestContext Framework. It can be used at the class level as a drop-in -replacement for `@ContextConfiguration`. With regard to configuration options, the only -difference between `@ContextConfiguration` and `@SpringJUnitConfig` is that component -classes may be declared with the `value` attribute in `@SpringJUnitConfig`. - -The following example shows how to use the `@SpringJUnitConfig` annotation to specify a -configuration class: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(TestConfig.class) // <1> - class ConfigurationClassJUnitJupiterSpringTests { - // class body... - } ----- -<1> Specify the configuration class. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(TestConfig::class) // <1> - class ConfigurationClassJUnitJupiterSpringTests { - // class body... - } ----- -<1> Specify the configuration class. - - -The following example shows how to use the `@SpringJUnitConfig` annotation to specify the -location of a configuration file: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(locations = "/test-config.xml") // <1> - class XmlJUnitJupiterSpringTests { - // class body... - } ----- -<1> Specify the location of a configuration file. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(locations = ["/test-config.xml"]) // <1> - class XmlJUnitJupiterSpringTests { - // class body... - } ----- -<1> Specify the location of a configuration file. - - -See <> as well as the javadoc for -{api-spring-framework}/test/context/junit/jupiter/SpringJUnitConfig.html[`@SpringJUnitConfig`] -and `@ContextConfiguration` for further details. - -[[integration-testing-annotations-junit-jupiter-springjunitwebconfig]] -===== `@SpringJUnitWebConfig` - -`@SpringJUnitWebConfig` is a composed annotation that combines -`@ExtendWith(SpringExtension.class)` from JUnit Jupiter with `@ContextConfiguration` and -`@WebAppConfiguration` from the Spring TestContext Framework. You can use it at the class -level as a drop-in replacement for `@ContextConfiguration` and `@WebAppConfiguration`. -With regard to configuration options, the only difference between `@ContextConfiguration` -and `@SpringJUnitWebConfig` is that you can declare component classes by using the -`value` attribute in `@SpringJUnitWebConfig`. In addition, you can override the `value` -attribute from `@WebAppConfiguration` only by using the `resourcePath` attribute in -`@SpringJUnitWebConfig`. - -The following example shows how to use the `@SpringJUnitWebConfig` annotation to specify -a configuration class: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitWebConfig(TestConfig.class) // <1> - class ConfigurationClassJUnitJupiterSpringWebTests { - // class body... - } ----- -<1> Specify the configuration class. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitWebConfig(TestConfig::class) // <1> - class ConfigurationClassJUnitJupiterSpringWebTests { - // class body... - } ----- -<1> Specify the configuration class. - - -The following example shows how to use the `@SpringJUnitWebConfig` annotation to specify the -location of a configuration file: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitWebConfig(locations = "/test-config.xml") // <1> - class XmlJUnitJupiterSpringWebTests { - // class body... - } ----- -<1> Specify the location of a configuration file. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitWebConfig(locations = ["/test-config.xml"]) // <1> - class XmlJUnitJupiterSpringWebTests { - // class body... - } ----- -<1> Specify the location of a configuration file. - - -See <> as well as the javadoc for -{api-spring-framework}/test/context/junit/jupiter/web/SpringJUnitWebConfig.html[`@SpringJUnitWebConfig`], -{api-spring-framework}/test/context/ContextConfiguration.html[`@ContextConfiguration`], and -{api-spring-framework}/test/context/web/WebAppConfiguration.html[`@WebAppConfiguration`] -for further details. - -[[integration-testing-annotations-testconstructor]] -===== `@TestConstructor` - -`@TestConstructor` is a type-level annotation that is used to configure how the parameters -of a test class constructor are autowired from components in the test's -`ApplicationContext`. - -If `@TestConstructor` is not present or meta-present on a test class, the default _test -constructor autowire mode_ will be used. See the tip below for details on how to change -the default mode. Note, however, that a local declaration of `@Autowired` on a -constructor takes precedence over both `@TestConstructor` and the default mode. - -.Changing the default test constructor autowire mode -[TIP] -===== -The default _test constructor autowire mode_ can be changed by setting the -`spring.test.constructor.autowire.mode` JVM system property to `all`. Alternatively, the -default mode may be set via the -<> mechanism. - -As of Spring Framework 5.3, the default mode may also be configured as a -https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter]. - -If the `spring.test.constructor.autowire.mode` property is not set, test class -constructors will not be automatically autowired. -===== - -NOTE: As of Spring Framework 5.2, `@TestConstructor` is only supported in conjunction -with the `SpringExtension` for use with JUnit Jupiter. Note that the `SpringExtension` is -often automatically registered for you – for example, when using annotations such as -`@SpringJUnitConfig` and `@SpringJUnitWebConfig` or various test-related annotations from -Spring Boot Test. - -[[integration-testing-annotations-nestedtestconfiguration]] -===== `@NestedTestConfiguration` - -`@NestedTestConfiguration` is a type-level annotation that is used to configure how -Spring test configuration annotations are processed within enclosing class hierarchies -for inner test classes. - -If `@NestedTestConfiguration` is not present or meta-present on a test class, in its -supertype hierarchy, or in its enclosing class hierarchy, the default _enclosing -configuration inheritance mode_ will be used. See the tip below for details on how to -change the default mode. - -.Changing the default enclosing configuration inheritance mode -[TIP] -===== -The default _enclosing configuration inheritance mode_ is `INHERIT`, but it can be -changed by setting the `spring.test.enclosing.configuration` JVM system property to -`OVERRIDE`. Alternatively, the default mode may be set via the -<> mechanism. -===== - -The <> honors `@NestedTestConfiguration` semantics for the -following annotations. - -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> - -NOTE: The use of `@NestedTestConfiguration` typically only makes sense in conjunction -with `@Nested` test classes in JUnit Jupiter; however, there may be other testing -frameworks with support for Spring and nested test classes that make use of this -annotation. - -See <> for an example and further -details. - -[[integration-testing-annotations-junit-jupiter-enabledif]] -===== `@EnabledIf` - -`@EnabledIf` is used to signal that the annotated JUnit Jupiter test class or test method -is enabled and should be run if the supplied `expression` evaluates to `true`. -Specifically, if the expression evaluates to `Boolean.TRUE` or a `String` equal to `true` -(ignoring case), the test is enabled. When applied at the class level, all test methods -within that class are automatically enabled by default as well. - -Expressions can be any of the following: - -* <> (SpEL) expression. For example: - `@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")` -* Placeholder for a property available in the Spring <>. - For example: `@EnabledIf("${smoke.tests.enabled}")` -* Text literal. For example: `@EnabledIf("true")` - -Note, however, that a text literal that is not the result of dynamic resolution of a -property placeholder is of zero practical value, since `@EnabledIf("false")` is -equivalent to `@Disabled` and `@EnabledIf("true")` is logically meaningless. - -You can use `@EnabledIf` as a meta-annotation to create custom composed annotations. For -example, you can create a custom `@EnabledOnMac` annotation as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Target({ElementType.TYPE, ElementType.METHOD}) - @Retention(RetentionPolicy.RUNTIME) - @EnabledIf( - expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", - reason = "Enabled on Mac OS" - ) - public @interface EnabledOnMac {} ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) - @Retention(AnnotationRetention.RUNTIME) - @EnabledIf( - expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", - reason = "Enabled on Mac OS" - ) - annotation class EnabledOnMac {} ----- - -[NOTE] -==== -`@EnabledOnMac` is meant only as an example of what is possible. If you have that exact -use case, please use the built-in `@EnabledOnOs(MAC)` support in JUnit Jupiter. -==== - -[WARNING] -==== -Since JUnit 5.7, JUnit Jupiter also has a condition annotation named `@EnabledIf`. Thus, -if you wish to use Spring's `@EnabledIf` support make sure you import the annotation type -from the correct package. -==== - -[[integration-testing-annotations-junit-jupiter-disabledif]] -===== `@DisabledIf` - -`@DisabledIf` is used to signal that the annotated JUnit Jupiter test class or test -method is disabled and should not be run if the supplied `expression` evaluates to -`true`. Specifically, if the expression evaluates to `Boolean.TRUE` or a `String` equal -to `true` (ignoring case), the test is disabled. When applied at the class level, all -test methods within that class are automatically disabled as well. - -Expressions can be any of the following: - -* <> (SpEL) expression. For example: - `@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")` -* Placeholder for a property available in the Spring <>. - For example: `@DisabledIf("${smoke.tests.disabled}")` -* Text literal. For example: `@DisabledIf("true")` - -Note, however, that a text literal that is not the result of dynamic resolution of a -property placeholder is of zero practical value, since `@DisabledIf("true")` is -equivalent to `@Disabled` and `@DisabledIf("false")` is logically meaningless. - -You can use `@DisabledIf` as a meta-annotation to create custom composed annotations. For -example, you can create a custom `@DisabledOnMac` annotation as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Target({ElementType.TYPE, ElementType.METHOD}) - @Retention(RetentionPolicy.RUNTIME) - @DisabledIf( - expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", - reason = "Disabled on Mac OS" - ) - public @interface DisabledOnMac {} ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) - @Retention(AnnotationRetention.RUNTIME) - @DisabledIf( - expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", - reason = "Disabled on Mac OS" - ) - annotation class DisabledOnMac {} ----- - -[NOTE] -==== -`@DisabledOnMac` is meant only as an example of what is possible. If you have that exact -use case, please use the built-in `@DisabledOnOs(MAC)` support in JUnit Jupiter. -==== - -[WARNING] -==== -Since JUnit 5.7, JUnit Jupiter also has a condition annotation named `@DisabledIf`. Thus, -if you wish to use Spring's `@DisabledIf` support make sure you import the annotation type -from the correct package. -==== - -[[integration-testing-annotations-meta]] -==== Meta-Annotation Support for Testing - -You can use most test-related annotations as -<> to create custom composed -annotations and reduce configuration duplication across a test suite. - -You can use each of the following as a meta-annotation in conjunction with the -<>. - -* `@BootstrapWith` -* `@ContextConfiguration` -* `@ContextHierarchy` -* `@ActiveProfiles` -* `@TestPropertySource` -* `@DirtiesContext` -* `@WebAppConfiguration` -* `@TestExecutionListeners` -* `@Transactional` -* `@BeforeTransaction` -* `@AfterTransaction` -* `@Commit` -* `@Rollback` -* `@Sql` -* `@SqlConfig` -* `@SqlMergeMode` -* `@SqlGroup` -* `@Repeat` _(only supported on JUnit 4)_ -* `@Timed` _(only supported on JUnit 4)_ -* `@IfProfileValue` _(only supported on JUnit 4)_ -* `@ProfileValueSourceConfiguration` _(only supported on JUnit 4)_ -* `@SpringJUnitConfig` _(only supported on JUnit Jupiter)_ -* `@SpringJUnitWebConfig` _(only supported on JUnit Jupiter)_ -* `@TestConstructor` _(only supported on JUnit Jupiter)_ -* `@NestedTestConfiguration` _(only supported on JUnit Jupiter)_ -* `@EnabledIf` _(only supported on JUnit Jupiter)_ -* `@DisabledIf` _(only supported on JUnit Jupiter)_ - -Consider the following example: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @RunWith(SpringRunner.class) - @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) - @ActiveProfiles("dev") - @Transactional - public class OrderRepositoryTests { } - - @RunWith(SpringRunner.class) - @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) - @ActiveProfiles("dev") - @Transactional - public class UserRepositoryTests { } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @RunWith(SpringRunner::class) - @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") - @ActiveProfiles("dev") - @Transactional - class OrderRepositoryTests { } - - @RunWith(SpringRunner::class) - @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") - @ActiveProfiles("dev") - @Transactional - class UserRepositoryTests { } ----- - -If we discover that we are repeating the preceding configuration across our JUnit 4-based -test suite, we can reduce the duplication by introducing a custom composed annotation -that centralizes the common test configuration for Spring, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Target(ElementType.TYPE) - @Retention(RetentionPolicy.RUNTIME) - @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) - @ActiveProfiles("dev") - @Transactional - public @interface TransactionalDevTestConfig { } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Target(AnnotationTarget.TYPE) - @Retention(AnnotationRetention.RUNTIME) - @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") - @ActiveProfiles("dev") - @Transactional - annotation class TransactionalDevTestConfig { } ----- - -Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the -configuration of individual JUnit 4 based test classes, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @RunWith(SpringRunner.class) - @TransactionalDevTestConfig - public class OrderRepositoryTests { } - - @RunWith(SpringRunner.class) - @TransactionalDevTestConfig - public class UserRepositoryTests { } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @RunWith(SpringRunner::class) - @TransactionalDevTestConfig - class OrderRepositoryTests - - @RunWith(SpringRunner::class) - @TransactionalDevTestConfig - class UserRepositoryTests ----- - -If we write tests that use JUnit Jupiter, we can reduce code duplication even further, -since annotations in JUnit 5 can also be used as meta-annotations. Consider the following -example: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) - @ActiveProfiles("dev") - @Transactional - class OrderRepositoryTests { } - - @ExtendWith(SpringExtension.class) - @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) - @ActiveProfiles("dev") - @Transactional - class UserRepositoryTests { } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") - @ActiveProfiles("dev") - @Transactional - class OrderRepositoryTests { } - - @ExtendWith(SpringExtension::class) - @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") - @ActiveProfiles("dev") - @Transactional - class UserRepositoryTests { } ----- - -If we discover that we are repeating the preceding configuration across our JUnit -Jupiter-based test suite, we can reduce the duplication by introducing a custom composed -annotation that centralizes the common test configuration for Spring and JUnit Jupiter, -as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Target(ElementType.TYPE) - @Retention(RetentionPolicy.RUNTIME) - @ExtendWith(SpringExtension.class) - @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) - @ActiveProfiles("dev") - @Transactional - public @interface TransactionalDevTestConfig { } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Target(AnnotationTarget.TYPE) - @Retention(AnnotationRetention.RUNTIME) - @ExtendWith(SpringExtension::class) - @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") - @ActiveProfiles("dev") - @Transactional - annotation class TransactionalDevTestConfig { } ----- - -Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the -configuration of individual JUnit Jupiter based test classes, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @TransactionalDevTestConfig - class OrderRepositoryTests { } - - @TransactionalDevTestConfig - class UserRepositoryTests { } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @TransactionalDevTestConfig - class OrderRepositoryTests { } - - @TransactionalDevTestConfig - class UserRepositoryTests { } ----- - -Since JUnit Jupiter supports the use of `@Test`, `@RepeatedTest`, `ParameterizedTest`, -and others as meta-annotations, you can also create custom composed annotations at the -test method level. For example, if we wish to create a composed annotation that combines -the `@Test` and `@Tag` annotations from JUnit Jupiter with the `@Transactional` -annotation from Spring, we could create an `@TransactionalIntegrationTest` annotation, as -follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @Transactional - @Tag("integration-test") // org.junit.jupiter.api.Tag - @Test // org.junit.jupiter.api.Test - public @interface TransactionalIntegrationTest { } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Target(AnnotationTarget.TYPE) - @Retention(AnnotationRetention.RUNTIME) - @Transactional - @Tag("integration-test") // org.junit.jupiter.api.Tag - @Test // org.junit.jupiter.api.Test - annotation class TransactionalIntegrationTest { } ----- - -Then we can use our custom `@TransactionalIntegrationTest` annotation to simplify the -configuration of individual JUnit Jupiter based test methods, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @TransactionalIntegrationTest - void saveOrder() { } - - @TransactionalIntegrationTest - void deleteOrder() { } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @TransactionalIntegrationTest - fun saveOrder() { } - - @TransactionalIntegrationTest - fun deleteOrder() { } ----- - -For further details, see the -https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model[Spring Annotation Programming Model] -wiki page. - - - -[[testcontext-framework]] -=== Spring TestContext Framework - -The Spring TestContext Framework (located in the `org.springframework.test.context` -package) provides generic, annotation-driven unit and integration testing support that is -agnostic of the testing framework in use. The TestContext framework also places a great -deal of importance on convention over configuration, with reasonable defaults that you -can override through annotation-based configuration. - -In addition to generic testing infrastructure, the TestContext framework provides -explicit support for JUnit 4, JUnit Jupiter (AKA JUnit 5), and TestNG. For JUnit 4 and -TestNG, Spring provides `abstract` support classes. Furthermore, Spring provides a custom -JUnit `Runner` and custom JUnit `Rules` for JUnit 4 and a custom `Extension` for JUnit -Jupiter that let you write so-called POJO test classes. POJO test classes are not -required to extend a particular class hierarchy, such as the `abstract` support classes. - -The following section provides an overview of the internals of the TestContext framework. -If you are interested only in using the framework and are not interested in extending it -with your own custom listeners or custom loaders, feel free to go directly to the -configuration (<>, -<>, <>), <>, and -<> sections. - - -[[testcontext-key-abstractions]] -==== Key Abstractions - -The core of the framework consists of the `TestContextManager` class and the -`TestContext`, `TestExecutionListener`, and `SmartContextLoader` interfaces. A -`TestContextManager` is created for each test class (for example, for the execution of -all test methods within a single test class in JUnit Jupiter). The `TestContextManager`, -in turn, manages a `TestContext` that holds the context of the current test. The -`TestContextManager` also updates the state of the `TestContext` as the test progresses -and delegates to `TestExecutionListener` implementations, which instrument the actual -test execution by providing dependency injection, managing transactions, and so on. A -`SmartContextLoader` is responsible for loading an `ApplicationContext` for a given test -class. See the {api-spring-framework}/test/context/package-summary.html[javadoc] and the -Spring test suite for further information and examples of various implementations. - -===== `TestContext` - -`TestContext` encapsulates the context in which a test is run (agnostic of the -actual testing framework in use) and provides context management and caching support for -the test instance for which it is responsible. The `TestContext` also delegates to a -`SmartContextLoader` to load an `ApplicationContext` if requested. - -===== `TestContextManager` - -`TestContextManager` is the main entry point into the Spring TestContext Framework and is -responsible for managing a single `TestContext` and signaling events to each registered -`TestExecutionListener` at well-defined test execution points: - -* Prior to any "`before class`" or "`before all`" methods of a particular testing framework. -* Test instance post-processing. -* Prior to any "`before`" or "`before each`" methods of a particular testing framework. -* Immediately before execution of the test method but after test setup. -* Immediately after execution of the test method but before test tear down. -* After any "`after`" or "`after each`" methods of a particular testing framework. -* After any "`after class`" or "`after all`" methods of a particular testing framework. - -===== `TestExecutionListener` - -`TestExecutionListener` defines the API for reacting to test-execution events published by -the `TestContextManager` with which the listener is registered. See <>. - -===== Context Loaders - -`ContextLoader` is a strategy interface for loading an `ApplicationContext` for an -integration test managed by the Spring TestContext Framework. You should implement -`SmartContextLoader` instead of this interface to provide support for component classes, -active bean definition profiles, test property sources, context hierarchies, and -`WebApplicationContext` support. - -`SmartContextLoader` is an extension of the `ContextLoader` interface that supersedes the -original minimal `ContextLoader` SPI. Specifically, a `SmartContextLoader` can choose to -process resource locations, component classes, or context initializers. Furthermore, a -`SmartContextLoader` can set active bean definition profiles and test property sources in -the context that it loads. - -Spring provides the following implementations: - -* `DelegatingSmartContextLoader`: One of two default loaders, it delegates internally to - an `AnnotationConfigContextLoader`, a `GenericXmlContextLoader`, or a - `GenericGroovyXmlContextLoader`, depending either on the configuration declared for the - test class or on the presence of default locations or default configuration classes. - Groovy support is enabled only if Groovy is on the classpath. -* `WebDelegatingSmartContextLoader`: One of two default loaders, it delegates internally - to an `AnnotationConfigWebContextLoader`, a `GenericXmlWebContextLoader`, or a - `GenericGroovyXmlWebContextLoader`, depending either on the configuration declared for - the test class or on the presence of default locations or default configuration - classes. A web `ContextLoader` is used only if `@WebAppConfiguration` is present on the - test class. Groovy support is enabled only if Groovy is on the classpath. -* `AnnotationConfigContextLoader`: Loads a standard `ApplicationContext` from component - classes. -* `AnnotationConfigWebContextLoader`: Loads a `WebApplicationContext` from component - classes. -* `GenericGroovyXmlContextLoader`: Loads a standard `ApplicationContext` from resource - locations that are either Groovy scripts or XML configuration files. -* `GenericGroovyXmlWebContextLoader`: Loads a `WebApplicationContext` from resource - locations that are either Groovy scripts or XML configuration files. -* `GenericXmlContextLoader`: Loads a standard `ApplicationContext` from XML resource - locations. -* `GenericXmlWebContextLoader`: Loads a `WebApplicationContext` from XML resource - locations. - - -[[testcontext-bootstrapping]] -==== Bootstrapping the TestContext Framework - -The default configuration for the internals of the Spring TestContext Framework is -sufficient for all common use cases. However, there are times when a development team or -third party framework would like to change the default `ContextLoader`, implement a -custom `TestContext` or `ContextCache`, augment the default sets of -`ContextCustomizerFactory` and `TestExecutionListener` implementations, and so on. For -such low-level control over how the TestContext framework operates, Spring provides a -bootstrapping strategy. - -`TestContextBootstrapper` defines the SPI for bootstrapping the TestContext framework. A -`TestContextBootstrapper` is used by the `TestContextManager` to load the -`TestExecutionListener` implementations for the current test and to build the -`TestContext` that it manages. You can configure a custom bootstrapping strategy for a -test class (or test class hierarchy) by using `@BootstrapWith`, either directly or as a -meta-annotation. If a bootstrapper is not explicitly configured by using -`@BootstrapWith`, either the `DefaultTestContextBootstrapper` or the -`WebTestContextBootstrapper` is used, depending on the presence of `@WebAppConfiguration`. - -Since the `TestContextBootstrapper` SPI is likely to change in the future (to accommodate -new requirements), we strongly encourage implementers not to implement this interface -directly but rather to extend `AbstractTestContextBootstrapper` or one of its concrete -subclasses instead. - - -[[testcontext-tel-config]] -==== `TestExecutionListener` Configuration - -Spring provides the following `TestExecutionListener` implementations that are registered -by default, exactly in the following order: - -* `ServletTestExecutionListener`: Configures Servlet API mocks for a - `WebApplicationContext`. -* `DirtiesContextBeforeModesTestExecutionListener`: Handles the `@DirtiesContext` - annotation for "`before`" modes. -* `ApplicationEventsTestExecutionListener`: Provides support for - <>. -* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test - instance. -* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for - "`after`" modes. -* `TransactionalTestExecutionListener`: Provides transactional test execution with - default rollback semantics. -* `SqlScriptsTestExecutionListener`: Runs SQL scripts configured by using the `@Sql` - annotation. -* `EventPublishingTestExecutionListener`: Publishes test execution events to the test's - `ApplicationContext` (see <>). - -[[testcontext-tel-config-registering-tels]] -===== Registering `TestExecutionListener` Implementations - -You can register `TestExecutionListener` implementations explicitly for a test class, its -subclasses, and its nested classes by using the `@TestExecutionListeners` annotation. See -<> and the javadoc for -{api-spring-framework}/test/context/TestExecutionListeners.html[`@TestExecutionListeners`] -for details and examples. - -.Switching to default `TestExecutionListener` implementations -[NOTE] -==== -If you extend a class that is annotated with `@TestExecutionListeners` and you need to -switch to using the default set of listeners, you can annotate your class with the -following. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // Switch to default listeners - @TestExecutionListeners( - listeners = {}, - inheritListeners = false, - mergeMode = MERGE_WITH_DEFAULTS) - class MyTest extends BaseTest { - // class body... - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Switch to default listeners - @TestExecutionListeners( - listeners = [], - inheritListeners = false, - mergeMode = MERGE_WITH_DEFAULTS) - class MyTest : BaseTest { - // class body... - } ----- -==== - -[[testcontext-tel-config-automatic-discovery]] -===== Automatic Discovery of Default `TestExecutionListener` Implementations - -Registering `TestExecutionListener` implementations by using `@TestExecutionListeners` is -suitable for custom listeners that are used in limited testing scenarios. However, it can -become cumbersome if a custom listener needs to be used across an entire test suite. This -issue is addressed through support for automatic discovery of default -`TestExecutionListener` implementations through the `SpringFactoriesLoader` mechanism. - -Specifically, the `spring-test` module declares all core default `TestExecutionListener` -implementations under the `org.springframework.test.context.TestExecutionListener` key in -its `META-INF/spring.factories` properties file. Third-party frameworks and developers -can contribute their own `TestExecutionListener` implementations to the list of default -listeners in the same manner through their own `META-INF/spring.factories` properties -file. - -[[testcontext-tel-config-ordering]] -===== Ordering `TestExecutionListener` Implementations - -When the TestContext framework discovers default `TestExecutionListener` implementations -through the <> -`SpringFactoriesLoader` mechanism, the instantiated listeners are sorted by using -Spring's `AnnotationAwareOrderComparator`, which honors Spring's `Ordered` interface and -`@Order` annotation for ordering. `AbstractTestExecutionListener` and all default -`TestExecutionListener` implementations provided by Spring implement `Ordered` with -appropriate values. Third-party frameworks and developers should therefore make sure that -their default `TestExecutionListener` implementations are registered in the proper order -by implementing `Ordered` or declaring `@Order`. See the javadoc for the `getOrder()` -methods of the core default `TestExecutionListener` implementations for details on what -values are assigned to each core listener. - -[[testcontext-tel-config-merging]] -===== Merging `TestExecutionListener` Implementations - -If a custom `TestExecutionListener` is registered via `@TestExecutionListeners`, the -default listeners are not registered. In most common testing scenarios, this effectively -forces the developer to manually declare all default listeners in addition to any custom -listeners. The following listing demonstrates this style of configuration: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @TestExecutionListeners({ - MyCustomTestExecutionListener.class, - ServletTestExecutionListener.class, - DirtiesContextBeforeModesTestExecutionListener.class, - DependencyInjectionTestExecutionListener.class, - DirtiesContextTestExecutionListener.class, - TransactionalTestExecutionListener.class, - SqlScriptsTestExecutionListener.class - }) - class MyTest { - // class body... - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @TestExecutionListeners( - MyCustomTestExecutionListener::class, - ServletTestExecutionListener::class, - DirtiesContextBeforeModesTestExecutionListener::class, - DependencyInjectionTestExecutionListener::class, - DirtiesContextTestExecutionListener::class, - TransactionalTestExecutionListener::class, - SqlScriptsTestExecutionListener::class - ) - class MyTest { - // class body... - } ----- - -The challenge with this approach is that it requires that the developer know exactly -which listeners are registered by default. Moreover, the set of default listeners can -change from release to release -- for example, `SqlScriptsTestExecutionListener` was -introduced in Spring Framework 4.1, and `DirtiesContextBeforeModesTestExecutionListener` -was introduced in Spring Framework 4.2. Furthermore, third-party frameworks like Spring -Boot and Spring Security register their own default `TestExecutionListener` -implementations by using the aforementioned <>. - -To avoid having to be aware of and re-declare all default listeners, you can set the -`mergeMode` attribute of `@TestExecutionListeners` to `MergeMode.MERGE_WITH_DEFAULTS`. -`MERGE_WITH_DEFAULTS` indicates that locally declared listeners should be merged with the -default listeners. The merging algorithm ensures that duplicates are removed from the -list and that the resulting set of merged listeners is sorted according to the semantics -of `AnnotationAwareOrderComparator`, as described in <>. -If a listener implements `Ordered` or is annotated with `@Order`, it can influence the -position in which it is merged with the defaults. Otherwise, locally declared listeners -are appended to the list of default listeners when merged. - -For example, if the `MyCustomTestExecutionListener` class in the previous example -configures its `order` value (for example, `500`) to be less than the order of the -`ServletTestExecutionListener` (which happens to be `1000`), the -`MyCustomTestExecutionListener` can then be automatically merged with the list of -defaults in front of the `ServletTestExecutionListener`, and the previous example could -be replaced with the following: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @TestExecutionListeners( - listeners = MyCustomTestExecutionListener.class, - mergeMode = MERGE_WITH_DEFAULTS - ) - class MyTest { - // class body... - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @TestExecutionListeners( - listeners = [MyCustomTestExecutionListener::class], - mergeMode = MERGE_WITH_DEFAULTS - ) - class MyTest { - // class body... - } ----- - -[[testcontext-application-events]] -==== Application Events - -Since Spring Framework 5.3.3, the TestContext framework provides support for recording -<> published in the -`ApplicationContext` so that assertions can be performed against those events within -tests. All events published during the execution of a single test are made available via -the `ApplicationEvents` API which allows you to process the events as a -`java.util.Stream`. - -To use `ApplicationEvents` in your tests, do the following. - -* Ensure that your test class is annotated or meta-annotated with - <>. -* Ensure that the `ApplicationEventsTestExecutionListener` is registered. Note, however, - that `ApplicationEventsTestExecutionListener` is registered by default and only needs - to be manually registered if you have custom configuration via - `@TestExecutionListeners` that does not include the default listeners. -* Annotate a field of type `ApplicationEvents` with `@Autowired` and use that instance of - `ApplicationEvents` in your test and lifecycle methods (such as `@BeforeEach` and - `@AfterEach` methods in JUnit Jupiter). -** When using the <>, you may declare a method - parameter of type `ApplicationEvents` in a test or lifecycle method as an alternative - to an `@Autowired` field in the test class. - -The following test class uses the `SpringExtension` for JUnit Jupiter and -https://assertj.github.io/doc/[AssertJ] to assert the types of application events -published while invoking a method in a Spring-managed component: - -// Don't use "quotes" in the "subs" section because of the asterisks in /* ... */ -[source,java,indent=0,subs="verbatim",role="primary"] -.Java ----- - @SpringJUnitConfig(/* ... */) - @RecordApplicationEvents // <1> - class OrderServiceTests { - - @Autowired - OrderService orderService; - - @Autowired - ApplicationEvents events; // <2> - - @Test - void submitOrder() { - // Invoke method in OrderService that publishes an event - orderService.submitOrder(new Order(/* ... */)); - // Verify that an OrderSubmitted event was published - long numEvents = events.stream(OrderSubmitted.class).count(); // <3> - assertThat(numEvents).isEqualTo(1); - } - } ----- -<1> Annotate the test class with `@RecordApplicationEvents`. -<2> Inject the `ApplicationEvents` instance for the current test. -<3> Use the `ApplicationEvents` API to count how many `OrderSubmitted` events were published. - -// Don't use "quotes" in the "subs" section because of the asterisks in /* ... */ -[source,kotlin,indent=0,subs="verbatim",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(/* ... */) - @RecordApplicationEvents // <1> - class OrderServiceTests { - - @Autowired - lateinit var orderService: OrderService - - @Autowired - lateinit var events: ApplicationEvents // <2> - - @Test - fun submitOrder() { - // Invoke method in OrderService that publishes an event - orderService.submitOrder(Order(/* ... */)) - // Verify that an OrderSubmitted event was published - val numEvents = events.stream(OrderSubmitted::class).count() // <3> - assertThat(numEvents).isEqualTo(1) - } - } ----- -<1> Annotate the test class with `@RecordApplicationEvents`. -<2> Inject the `ApplicationEvents` instance for the current test. -<3> Use the `ApplicationEvents` API to count how many `OrderSubmitted` events were published. - -See the -{api-spring-framework}/test/context/event/ApplicationEvents.html[`ApplicationEvents` -javadoc] for further details regarding the `ApplicationEvents` API. - -[[testcontext-test-execution-events]] -==== Test Execution Events - -The `EventPublishingTestExecutionListener` introduced in Spring Framework 5.2 offers an -alternative approach to implementing a custom `TestExecutionListener`. Components in the -test's `ApplicationContext` can listen to the following events published by the -`EventPublishingTestExecutionListener`, each of which corresponds to a method in the -`TestExecutionListener` API. - -* `BeforeTestClassEvent` -* `PrepareTestInstanceEvent` -* `BeforeTestMethodEvent` -* `BeforeTestExecutionEvent` -* `AfterTestExecutionEvent` -* `AfterTestMethodEvent` -* `AfterTestClassEvent` - -These events may be consumed for various reasons, such as resetting mock beans or tracing -test execution. One advantage of consuming test execution events rather than implementing -a custom `TestExecutionListener` is that test execution events may be consumed by any -Spring bean registered in the test `ApplicationContext`, and such beans may benefit -directly from dependency injection and other features of the `ApplicationContext`. In -contrast, a `TestExecutionListener` is not a bean in the `ApplicationContext`. - -[NOTE] -==== -The `EventPublishingTestExecutionListener` is registered by default; however, it only -publishes events if the `ApplicationContext` has _already been loaded_. This prevents the -`ApplicationContext` from being loaded unnecessarily or too early. - -Consequently, a `BeforeTestClassEvent` will not be published until after the -`ApplicationContext` has been loaded by another `TestExecutionListener`. For example, with -the default set of `TestExecutionListener` implementations registered, a -`BeforeTestClassEvent` will not be published for the first test class that uses a -particular test `ApplicationContext`, but a `BeforeTestClassEvent` _will_ be published for -any subsequent test class in the same test suite that uses the same test -`ApplicationContext` since the context will already have been loaded when subsequent test -classes run (as long as the context has not been removed from the `ContextCache` via -`@DirtiesContext` or the max-size eviction policy). - -If you wish to ensure that a `BeforeTestClassEvent` is always published for every test -class, you need to register a `TestExecutionListener` that loads the `ApplicationContext` -in the `beforeTestClass` callback, and that `TestExecutionListener` must be registered -_before_ the `EventPublishingTestExecutionListener`. - -Similarly, if `@DirtiesContext` is used to remove the `ApplicationContext` from the -context cache after the last test method in a given test class, the `AfterTestClassEvent` -will not be published for that test class. -==== - -In order to listen to test execution events, a Spring bean may choose to implement the -`org.springframework.context.ApplicationListener` interface. Alternatively, listener -methods can be annotated with `@EventListener` and configured to listen to one of the -particular event types listed above (see -<>). -Due to the popularity of this approach, Spring provides the following dedicated -`@EventListener` annotations to simplify registration of test execution event listeners. -These annotations reside in the `org.springframework.test.context.event.annotation` -package. - -* `@BeforeTestClass` -* `@PrepareTestInstance` -* `@BeforeTestMethod` -* `@BeforeTestExecution` -* `@AfterTestExecution` -* `@AfterTestMethod` -* `@AfterTestClass` - -[[testcontext-test-execution-events-exception-handling]] -===== Exception Handling - -By default, if a test execution event listener throws an exception while consuming an -event, that exception will propagate to the underlying testing framework in use (such as -JUnit or TestNG). For example, if the consumption of a `BeforeTestMethodEvent` results in -an exception, the corresponding test method will fail as a result of the exception. In -contrast, if an asynchronous test execution event listener throws an exception, the -exception will not propagate to the underlying testing framework. For further details on -asynchronous exception handling, consult the class-level javadoc for `@EventListener`. - -[[testcontext-test-execution-events-async]] -===== Asynchronous Listeners - -If you want a particular test execution event listener to process events asynchronously, -you can use Spring's <>. For further details, consult the class-level javadoc for -`@EventListener`. - - -[[testcontext-ctx-management]] -==== Context Management - -Each `TestContext` provides context management and caching support for the test instance -for which it is responsible. Test instances do not automatically receive access to the -configured `ApplicationContext`. However, if a test class implements the -`ApplicationContextAware` interface, a reference to the `ApplicationContext` is supplied -to the test instance. Note that `AbstractJUnit4SpringContextTests` and -`AbstractTestNGSpringContextTests` implement `ApplicationContextAware` and, therefore, -provide access to the `ApplicationContext` automatically. - -.@Autowired ApplicationContext -[TIP] -===== -As an alternative to implementing the `ApplicationContextAware` interface, you can inject -the application context for your test class through the `@Autowired` annotation on either -a field or setter method, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig - class MyTest { - - @Autowired // <1> - ApplicationContext applicationContext; - - // class body... - } ----- -<1> Injecting the `ApplicationContext`. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig - class MyTest { - - @Autowired // <1> - lateinit var applicationContext: ApplicationContext - - // class body... - } ----- -<1> Injecting the `ApplicationContext`. - - -Similarly, if your test is configured to load a `WebApplicationContext`, you can inject -the web application context into your test, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitWebConfig // <1> - class MyWebAppTest { - - @Autowired // <2> - WebApplicationContext wac; - - // class body... - } ----- -<1> Configuring the `WebApplicationContext`. -<2> Injecting the `WebApplicationContext`. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitWebConfig // <1> - class MyWebAppTest { - - @Autowired // <2> - lateinit var wac: WebApplicationContext - // class body... - } ----- -<1> Configuring the `WebApplicationContext`. -<2> Injecting the `WebApplicationContext`. - - -Dependency injection by using `@Autowired` is provided by the -`DependencyInjectionTestExecutionListener`, which is configured by default -(see <>). -===== - -Test classes that use the TestContext framework do not need to extend any particular -class or implement a specific interface to configure their application context. Instead, -configuration is achieved by declaring the `@ContextConfiguration` annotation at the -class level. If your test class does not explicitly declare application context resource -locations or component classes, the configured `ContextLoader` determines how to load a -context from a default location or default configuration classes. In addition to context -resource locations and component classes, an application context can also be configured -through application context initializers. - -The following sections explain how to use Spring's `@ContextConfiguration` annotation to -configure a test `ApplicationContext` by using XML configuration files, Groovy scripts, -component classes (typically `@Configuration` classes), or context initializers. -Alternatively, you can implement and configure your own custom `SmartContextLoader` for -advanced use cases. - -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> - -[[testcontext-ctx-management-xml]] -===== Context Configuration with XML resources - -To load an `ApplicationContext` for your tests by using XML configuration files, annotate -your test class with `@ContextConfiguration` and configure the `locations` attribute with -an array that contains the resource locations of XML configuration metadata. A plain or -relative path (for example, `context.xml`) is treated as a classpath resource that is -relative to the package in which the test class is defined. A path starting with a slash -is treated as an absolute classpath location (for example, `/org/example/config.xml`). A -path that represents a resource URL (i.e., a path prefixed with `classpath:`, `file:`, -`http:`, etc.) is used _as is_. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // ApplicationContext will be loaded from "/app-config.xml" and - // "/test-config.xml" in the root of the classpath - @ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) // <1> - class MyTest { - // class body... - } ----- -<1> Setting the locations attribute to a list of XML files. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // ApplicationContext will be loaded from "/app-config.xml" and - // "/test-config.xml" in the root of the classpath - @ContextConfiguration("/app-config.xml", "/test-config.xml") // <1> - class MyTest { - // class body... - } ----- -<1> Setting the locations attribute to a list of XML files. - - -`@ContextConfiguration` supports an alias for the `locations` attribute through the -standard Java `value` attribute. Thus, if you do not need to declare additional -attributes in `@ContextConfiguration`, you can omit the declaration of the `locations` -attribute name and declare the resource locations by using the shorthand format -demonstrated in the following example: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - @ContextConfiguration({"/app-config.xml", "/test-config.xml"}) <1> - class MyTest { - // class body... - } ----- -<1> Specifying XML files without using the `location` attribute. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - @ContextConfiguration("/app-config.xml", "/test-config.xml") // <1> - class MyTest { - // class body... - } ----- -<1> Specifying XML files without using the `location` attribute. - - -If you omit both the `locations` and the `value` attributes from the -`@ContextConfiguration` annotation, the TestContext framework tries to detect a default -XML resource location. Specifically, `GenericXmlContextLoader` and -`GenericXmlWebContextLoader` detect a default location based on the name of the test -class. If your class is named `com.example.MyTest`, `GenericXmlContextLoader` loads your -application context from `"classpath:com/example/MyTest-context.xml"`. The following -example shows how to do so: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // ApplicationContext will be loaded from - // "classpath:com/example/MyTest-context.xml" - @ContextConfiguration // <1> - class MyTest { - // class body... - } ----- -<1> Loading configuration from the default location. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // ApplicationContext will be loaded from - // "classpath:com/example/MyTest-context.xml" - @ContextConfiguration // <1> - class MyTest { - // class body... - } ----- -<1> Loading configuration from the default location. - - -[[testcontext-ctx-management-groovy]] -===== Context Configuration with Groovy Scripts - -To load an `ApplicationContext` for your tests by using Groovy scripts that use the -<>, you can annotate -your test class with `@ContextConfiguration` and configure the `locations` or `value` -attribute with an array that contains the resource locations of Groovy scripts. Resource -lookup semantics for Groovy scripts are the same as those described for -<>. - -.Enabling Groovy script support -TIP: Support for using Groovy scripts to load an `ApplicationContext` in the Spring -TestContext Framework is enabled automatically if Groovy is on the classpath. - -The following example shows how to specify Groovy configuration files: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // ApplicationContext will be loaded from "/AppConfig.groovy" and - // "/TestConfig.groovy" in the root of the classpath - @ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) <1> - class MyTest { - // class body... - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // ApplicationContext will be loaded from "/AppConfig.groovy" and - // "/TestConfig.groovy" in the root of the classpath - @ContextConfiguration("/AppConfig.groovy", "/TestConfig.Groovy") // <1> - class MyTest { - // class body... - } ----- -<1> Specifying the location of Groovy configuration files. - - -If you omit both the `locations` and `value` attributes from the `@ContextConfiguration` -annotation, the TestContext framework tries to detect a default Groovy script. -Specifically, `GenericGroovyXmlContextLoader` and `GenericGroovyXmlWebContextLoader` -detect a default location based on the name of the test class. If your class is named -`com.example.MyTest`, the Groovy context loader loads your application context from -`"classpath:com/example/MyTestContext.groovy"`. The following example shows how to use -the default: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // ApplicationContext will be loaded from - // "classpath:com/example/MyTestContext.groovy" - @ContextConfiguration // <1> - class MyTest { - // class body... - } ----- -<1> Loading configuration from the default location. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // ApplicationContext will be loaded from - // "classpath:com/example/MyTestContext.groovy" - @ContextConfiguration // <1> - class MyTest { - // class body... - } ----- -<1> Loading configuration from the default location. - - -.Declaring XML configuration and Groovy scripts simultaneously -[TIP] -===== -You can declare both XML configuration files and Groovy scripts simultaneously by using -the `locations` or `value` attribute of `@ContextConfiguration`. If the path to a -configured resource location ends with `.xml`, it is loaded by using an -`XmlBeanDefinitionReader`. Otherwise, it is loaded by using a -`GroovyBeanDefinitionReader`. - -The following listing shows how to combine both in an integration test: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // ApplicationContext will be loaded from - // "/app-config.xml" and "/TestConfig.groovy" - @ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" }) - class MyTest { - // class body... - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // ApplicationContext will be loaded from - // "/app-config.xml" and "/TestConfig.groovy" - @ContextConfiguration("/app-config.xml", "/TestConfig.groovy") - class MyTest { - // class body... - } ----- -===== - -[[testcontext-ctx-management-javaconfig]] -===== Context Configuration with Component Classes - -To load an `ApplicationContext` for your tests by using component classes (see -<>), you can annotate your test -class with `@ContextConfiguration` and configure the `classes` attribute with an array -that contains references to component classes. The following example shows how to do so: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // ApplicationContext will be loaded from AppConfig and TestConfig - @ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) // <1> - class MyTest { - // class body... - } ----- -<1> Specifying component classes. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // ApplicationContext will be loaded from AppConfig and TestConfig - @ContextConfiguration(classes = [AppConfig::class, TestConfig::class]) // <1> - class MyTest { - // class body... - } ----- -<1> Specifying component classes. - - -[[testcontext-ctx-management-javaconfig-component-classes]] -.Component Classes -[TIP] -==== -The term "`component class`" can refer to any of the following: - -* A class annotated with `@Configuration`. -* A component (that is, a class annotated with `@Component`, `@Service`, `@Repository`, or other stereotype annotations). -* A JSR-330 compliant class that is annotated with `jakarta.inject` annotations. -* Any class that contains `@Bean`-methods. -* Any other class that is intended to be registered as a Spring component (i.e., a Spring - bean in the `ApplicationContext`), potentially taking advantage of automatic autowiring - of a single constructor without the use of Spring annotations. - -See the javadoc of -{api-spring-framework}/context/annotation/Configuration.html[`@Configuration`] and -{api-spring-framework}/context/annotation/Bean.html[`@Bean`] for further information -regarding the configuration and semantics of component classes, paying special attention -to the discussion of `@Bean` Lite Mode. -==== - -If you omit the `classes` attribute from the `@ContextConfiguration` annotation, the -TestContext framework tries to detect the presence of default configuration classes. -Specifically, `AnnotationConfigContextLoader` and `AnnotationConfigWebContextLoader` -detect all `static` nested classes of the test class that meet the requirements for -configuration class implementations, as specified in the -{api-spring-framework}/context/annotation/Configuration.html[`@Configuration`] javadoc. -Note that the name of the configuration class is arbitrary. In addition, a test class can -contain more than one `static` nested configuration class if desired. In the following -example, the `OrderServiceTest` class declares a `static` nested configuration class -named `Config` that is automatically used to load the `ApplicationContext` for the test -class: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig <1> - // ApplicationContext will be loaded from the - // static nested Config class - class OrderServiceTest { - - @Configuration - static class Config { - - // this bean will be injected into the OrderServiceTest class - @Bean - OrderService orderService() { - OrderService orderService = new OrderServiceImpl(); - // set properties, etc. - return orderService; - } - } - - @Autowired - OrderService orderService; - - @Test - void testOrderService() { - // test the orderService - } - - } ----- -<1> Loading configuration information from the nested `Config` class. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig <1> - // ApplicationContext will be loaded from the nested Config class - class OrderServiceTest { - - @Autowired - lateinit var orderService: OrderService - - @Configuration - class Config { - - // this bean will be injected into the OrderServiceTest class - @Bean - fun orderService(): OrderService { - // set properties, etc. - return OrderServiceImpl() - } - } - - @Test - fun testOrderService() { - // test the orderService - } - } ----- -<1> Loading configuration information from the nested `Config` class. - - -[[testcontext-ctx-management-mixed-config]] -===== Mixing XML, Groovy Scripts, and Component Classes - -It may sometimes be desirable to mix XML configuration files, Groovy scripts, and -component classes (typically `@Configuration` classes) to configure an -`ApplicationContext` for your tests. For example, if you use XML configuration in -production, you may decide that you want to use `@Configuration` classes to configure -specific Spring-managed components for your tests, or vice versa. - -Furthermore, some third-party frameworks (such as Spring Boot) provide first-class -support for loading an `ApplicationContext` from different types of resources -simultaneously (for example, XML configuration files, Groovy scripts, and -`@Configuration` classes). The Spring Framework, historically, has not supported this for -standard deployments. Consequently, most of the `SmartContextLoader` implementations that -the Spring Framework delivers in the `spring-test` module support only one resource type -for each test context. However, this does not mean that you cannot use both. One -exception to the general rule is that the `GenericGroovyXmlContextLoader` and -`GenericGroovyXmlWebContextLoader` support both XML configuration files and Groovy -scripts simultaneously. Furthermore, third-party frameworks may choose to support the -declaration of both `locations` and `classes` through `@ContextConfiguration`, and, with -the standard testing support in the TestContext framework, you have the following options. - -If you want to use resource locations (for example, XML or Groovy) and `@Configuration` -classes to configure your tests, you must pick one as the entry point, and that one must -include or import the other. For example, in XML or Groovy scripts, you can include -`@Configuration` classes by using component scanning or defining them as normal Spring -beans, whereas, in a `@Configuration` class, you can use `@ImportResource` to import XML -configuration files or Groovy scripts. Note that this behavior is semantically equivalent -to how you configure your application in production: In production configuration, you -define either a set of XML or Groovy resource locations or a set of `@Configuration` -classes from which your production `ApplicationContext` is loaded, but you still have the -freedom to include or import the other type of configuration. - -[[testcontext-ctx-management-initializers]] -===== Context Configuration with Context Initializers - -To configure an `ApplicationContext` for your tests by using context initializers, -annotate your test class with `@ContextConfiguration` and configure the `initializers` -attribute with an array that contains references to classes that implement -`ApplicationContextInitializer`. The declared context initializers are then used to -initialize the `ConfigurableApplicationContext` that is loaded for your tests. Note that -the concrete `ConfigurableApplicationContext` type supported by each declared initializer -must be compatible with the type of `ApplicationContext` created by the -`SmartContextLoader` in use (typically a `GenericApplicationContext`). Furthermore, the -order in which the initializers are invoked depends on whether they implement Spring's -`Ordered` interface or are annotated with Spring's `@Order` annotation or the standard -`@Priority` annotation. The following example shows how to use initializers: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // ApplicationContext will be loaded from TestConfig - // and initialized by TestAppCtxInitializer - @ContextConfiguration( - classes = TestConfig.class, - initializers = TestAppCtxInitializer.class) // <1> - class MyTest { - // class body... - } ----- -<1> Specifying configuration by using a configuration class and an initializer. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // ApplicationContext will be loaded from TestConfig - // and initialized by TestAppCtxInitializer - @ContextConfiguration( - classes = [TestConfig::class], - initializers = [TestAppCtxInitializer::class]) // <1> - class MyTest { - // class body... - } ----- -<1> Specifying configuration by using a configuration class and an initializer. - - -You can also omit the declaration of XML configuration files, Groovy scripts, or -component classes in `@ContextConfiguration` entirely and instead declare only -`ApplicationContextInitializer` classes, which are then responsible for registering beans -in the context -- for example, by programmatically loading bean definitions from XML -files or configuration classes. The following example shows how to do so: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // ApplicationContext will be initialized by EntireAppInitializer - // which presumably registers beans in the context - @ContextConfiguration(initializers = EntireAppInitializer.class) <1> - class MyTest { - // class body... - } ----- -<1> Specifying configuration by using only an initializer. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // ApplicationContext will be initialized by EntireAppInitializer - // which presumably registers beans in the context - @ContextConfiguration(initializers = [EntireAppInitializer::class]) // <1> - class MyTest { - // class body... - } ----- -<1> Specifying configuration by using only an initializer. - - -[[testcontext-ctx-management-inheritance]] -===== Context Configuration Inheritance - -`@ContextConfiguration` supports boolean `inheritLocations` and `inheritInitializers` -attributes that denote whether resource locations or component classes and context -initializers declared by superclasses should be inherited. The default value for both -flags is `true`. This means that a test class inherits the resource locations or -component classes as well as the context initializers declared by any superclasses. -Specifically, the resource locations or component classes for a test class are appended -to the list of resource locations or annotated classes declared by superclasses. -Similarly, the initializers for a given test class are added to the set of initializers -defined by test superclasses. Thus, subclasses have the option of extending the resource -locations, component classes, or context initializers. - -If the `inheritLocations` or `inheritInitializers` attribute in `@ContextConfiguration` -is set to `false`, the resource locations or component classes and the context -initializers, respectively, for the test class shadow and effectively replace the -configuration defined by superclasses. - -NOTE: As of Spring Framework 5.3, test configuration may also be inherited from enclosing -classes. See <> for details. - -In the next example, which uses XML resource locations, the `ApplicationContext` for -`ExtendedTest` is loaded from `base-config.xml` and `extended-config.xml`, in that order. -Beans defined in `extended-config.xml` can, therefore, override (that is, replace) those -defined in `base-config.xml`. The following example shows how one class can extend -another and use both its own configuration file and the superclass's configuration file: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // ApplicationContext will be loaded from "/base-config.xml" - // in the root of the classpath - @ContextConfiguration("/base-config.xml") <1> - class BaseTest { - // class body... - } - - // ApplicationContext will be loaded from "/base-config.xml" and - // "/extended-config.xml" in the root of the classpath - @ContextConfiguration("/extended-config.xml") <2> - class ExtendedTest extends BaseTest { - // class body... - } ----- -<1> Configuration file defined in the superclass. -<2> Configuration file defined in the subclass. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // ApplicationContext will be loaded from "/base-config.xml" - // in the root of the classpath - @ContextConfiguration("/base-config.xml") // <1> - open class BaseTest { - // class body... - } - - // ApplicationContext will be loaded from "/base-config.xml" and - // "/extended-config.xml" in the root of the classpath - @ContextConfiguration("/extended-config.xml") // <2> - class ExtendedTest : BaseTest() { - // class body... - } ----- -<1> Configuration file defined in the superclass. -<2> Configuration file defined in the subclass. - - -Similarly, in the next example, which uses component classes, the `ApplicationContext` -for `ExtendedTest` is loaded from the `BaseConfig` and `ExtendedConfig` classes, in that -order. Beans defined in `ExtendedConfig` can, therefore, override (that is, replace) -those defined in `BaseConfig`. The following example shows how one class can extend -another and use both its own configuration class and the superclass's configuration class: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // ApplicationContext will be loaded from BaseConfig - @SpringJUnitConfig(BaseConfig.class) // <1> - class BaseTest { - // class body... - } - - // ApplicationContext will be loaded from BaseConfig and ExtendedConfig - @SpringJUnitConfig(ExtendedConfig.class) // <2> - class ExtendedTest extends BaseTest { - // class body... - } ----- -<1> Configuration class defined in the superclass. -<2> Configuration class defined in the subclass. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // ApplicationContext will be loaded from BaseConfig - @SpringJUnitConfig(BaseConfig::class) // <1> - open class BaseTest { - // class body... - } - - // ApplicationContext will be loaded from BaseConfig and ExtendedConfig - @SpringJUnitConfig(ExtendedConfig::class) // <2> - class ExtendedTest : BaseTest() { - // class body... - } ----- -<1> Configuration class defined in the superclass. -<2> Configuration class defined in the subclass. - - -In the next example, which uses context initializers, the `ApplicationContext` for -`ExtendedTest` is initialized by using `BaseInitializer` and `ExtendedInitializer`. Note, -however, that the order in which the initializers are invoked depends on whether they -implement Spring's `Ordered` interface or are annotated with Spring's `@Order` annotation -or the standard `@Priority` annotation. The following example shows how one class can -extend another and use both its own initializer and the superclass's initializer: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // ApplicationContext will be initialized by BaseInitializer - @SpringJUnitConfig(initializers = BaseInitializer.class) // <1> - class BaseTest { - // class body... - } - - // ApplicationContext will be initialized by BaseInitializer - // and ExtendedInitializer - @SpringJUnitConfig(initializers = ExtendedInitializer.class) // <2> - class ExtendedTest extends BaseTest { - // class body... - } ----- -<1> Initializer defined in the superclass. -<2> Initializer defined in the subclass. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // ApplicationContext will be initialized by BaseInitializer - @SpringJUnitConfig(initializers = [BaseInitializer::class]) // <1> - open class BaseTest { - // class body... - } - - // ApplicationContext will be initialized by BaseInitializer - // and ExtendedInitializer - @SpringJUnitConfig(initializers = [ExtendedInitializer::class]) // <2> - class ExtendedTest : BaseTest() { - // class body... - } ----- -<1> Initializer defined in the superclass. -<2> Initializer defined in the subclass. - - -[[testcontext-ctx-management-env-profiles]] -===== Context Configuration with Environment Profiles - -The Spring Framework has first-class support for the notion of environments and profiles -(AKA "bean definition profiles"), and integration tests can be configured to activate -particular bean definition profiles for various testing scenarios. This is achieved by -annotating a test class with the `@ActiveProfiles` annotation and supplying a list of -profiles that should be activated when loading the `ApplicationContext` for the test. - -NOTE: You can use `@ActiveProfiles` with any implementation of the `SmartContextLoader` -SPI, but `@ActiveProfiles` is not supported with implementations of the older -`ContextLoader` SPI. - -Consider two examples with XML configuration and `@Configuration` classes: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ----- - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // ApplicationContext will be loaded from "classpath:/app-config.xml" - @ContextConfiguration("/app-config.xml") - @ActiveProfiles("dev") - class TransferServiceTest { - - @Autowired - TransferService transferService; - - @Test - void testTransferService() { - // test the transferService - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // ApplicationContext will be loaded from "classpath:/app-config.xml" - @ContextConfiguration("/app-config.xml") - @ActiveProfiles("dev") - class TransferServiceTest { - - @Autowired - lateinit var transferService: TransferService - - @Test - fun testTransferService() { - // test the transferService - } - } ----- - -When `TransferServiceTest` is run, its `ApplicationContext` is loaded from the -`app-config.xml` configuration file in the root of the classpath. If you inspect -`app-config.xml`, you can see that the `accountRepository` bean has a dependency on a -`dataSource` bean. However, `dataSource` is not defined as a top-level bean. Instead, -`dataSource` is defined three times: in the `production` profile, in the `dev` profile, -and in the `default` profile. - -By annotating `TransferServiceTest` with `@ActiveProfiles("dev")`, we instruct the Spring -TestContext Framework to load the `ApplicationContext` with the active profiles set to -`{"dev"}`. As a result, an embedded database is created and populated with test data, and -the `accountRepository` bean is wired with a reference to the development `DataSource`. -That is likely what we want in an integration test. - -It is sometimes useful to assign beans to a `default` profile. Beans within the default -profile are included only when no other profile is specifically activated. You can use -this to define "`fallback`" beans to be used in the application's default state. For -example, you may explicitly provide a data source for `dev` and `production` profiles, -but define an in-memory data source as a default when neither of these is active. - -The following code listings demonstrate how to implement the same configuration and -integration test with `@Configuration` classes instead of XML: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Configuration - @Profile("dev") - public class StandaloneDataConfig { - - @Bean - public DataSource dataSource() { - return new EmbeddedDatabaseBuilder() - .setType(EmbeddedDatabaseType.HSQL) - .addScript("classpath:com/bank/config/sql/schema.sql") - .addScript("classpath:com/bank/config/sql/test-data.sql") - .build(); - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Configuration - @Profile("dev") - class StandaloneDataConfig { - - @Bean - fun dataSource(): DataSource { - return EmbeddedDatabaseBuilder() - .setType(EmbeddedDatabaseType.HSQL) - .addScript("classpath:com/bank/config/sql/schema.sql") - .addScript("classpath:com/bank/config/sql/test-data.sql") - .build() - } - } ----- - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Configuration - @Profile("production") - public class JndiDataConfig { - - @Bean(destroyMethod="") - public DataSource dataSource() throws Exception { - Context ctx = new InitialContext(); - return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Configuration - @Profile("production") - class JndiDataConfig { - - @Bean(destroyMethod = "") - fun dataSource(): DataSource { - val ctx = InitialContext() - return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource - } - } ----- - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Configuration - @Profile("default") - public class DefaultDataConfig { - - @Bean - public DataSource dataSource() { - return new EmbeddedDatabaseBuilder() - .setType(EmbeddedDatabaseType.HSQL) - .addScript("classpath:com/bank/config/sql/schema.sql") - .build(); - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Configuration - @Profile("default") - class DefaultDataConfig { - - @Bean - fun dataSource(): DataSource { - return EmbeddedDatabaseBuilder() - .setType(EmbeddedDatabaseType.HSQL) - .addScript("classpath:com/bank/config/sql/schema.sql") - .build() - } - } ----- - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Configuration - public class TransferServiceConfig { - - @Autowired DataSource dataSource; - - @Bean - public TransferService transferService() { - return new DefaultTransferService(accountRepository(), feePolicy()); - } - - @Bean - public AccountRepository accountRepository() { - return new JdbcAccountRepository(dataSource); - } - - @Bean - public FeePolicy feePolicy() { - return new ZeroFeePolicy(); - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Configuration - class TransferServiceConfig { - - @Autowired - lateinit var dataSource: DataSource - - @Bean - fun transferService(): TransferService { - return DefaultTransferService(accountRepository(), feePolicy()) - } - - @Bean - fun accountRepository(): AccountRepository { - return JdbcAccountRepository(dataSource) - } - - @Bean - fun feePolicy(): FeePolicy { - return ZeroFeePolicy() - } - } ----- - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig({ - TransferServiceConfig.class, - StandaloneDataConfig.class, - JndiDataConfig.class, - DefaultDataConfig.class}) - @ActiveProfiles("dev") - class TransferServiceTest { - - @Autowired - TransferService transferService; - - @Test - void testTransferService() { - // test the transferService - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig( - TransferServiceConfig::class, - StandaloneDataConfig::class, - JndiDataConfig::class, - DefaultDataConfig::class) - @ActiveProfiles("dev") - class TransferServiceTest { - - @Autowired - lateinit var transferService: TransferService - - @Test - fun testTransferService() { - // test the transferService - } - } ----- - -In this variation, we have split the XML configuration into four independent -`@Configuration` classes: - -* `TransferServiceConfig`: Acquires a `dataSource` through dependency injection by using - `@Autowired`. -* `StandaloneDataConfig`: Defines a `dataSource` for an embedded database suitable for - developer tests. -* `JndiDataConfig`: Defines a `dataSource` that is retrieved from JNDI in a production - environment. -* `DefaultDataConfig`: Defines a `dataSource` for a default embedded database, in case no - profile is active. - -As with the XML-based configuration example, we still annotate `TransferServiceTest` with -`@ActiveProfiles("dev")`, but this time we specify all four configuration classes by -using the `@ContextConfiguration` annotation. The body of the test class itself remains -completely unchanged. - -It is often the case that a single set of profiles is used across multiple test classes -within a given project. Thus, to avoid duplicate declarations of the `@ActiveProfiles` -annotation, you can declare `@ActiveProfiles` once on a base class, and subclasses -automatically inherit the `@ActiveProfiles` configuration from the base class. In the -following example, the declaration of `@ActiveProfiles` (as well as other annotations) -has been moved to an abstract superclass, `AbstractIntegrationTest`: - -NOTE: As of Spring Framework 5.3, test configuration may also be inherited from enclosing -classes. See <> for details. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig({ - TransferServiceConfig.class, - StandaloneDataConfig.class, - JndiDataConfig.class, - DefaultDataConfig.class}) - @ActiveProfiles("dev") - abstract class AbstractIntegrationTest { - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig( - TransferServiceConfig::class, - StandaloneDataConfig::class, - JndiDataConfig::class, - DefaultDataConfig::class) - @ActiveProfiles("dev") - abstract class AbstractIntegrationTest { - } ----- - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // "dev" profile inherited from superclass - class TransferServiceTest extends AbstractIntegrationTest { - - @Autowired - TransferService transferService; - - @Test - void testTransferService() { - // test the transferService - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // "dev" profile inherited from superclass - class TransferServiceTest : AbstractIntegrationTest() { - - @Autowired - lateinit var transferService: TransferService - - @Test - fun testTransferService() { - // test the transferService - } - } ----- - -`@ActiveProfiles` also supports an `inheritProfiles` attribute that can be used to -disable the inheritance of active profiles, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // "dev" profile overridden with "production" - @ActiveProfiles(profiles = "production", inheritProfiles = false) - class ProductionTransferServiceTest extends AbstractIntegrationTest { - // test body - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // "dev" profile overridden with "production" - @ActiveProfiles("production", inheritProfiles = false) - class ProductionTransferServiceTest : AbstractIntegrationTest() { - // test body - } ----- - -[[testcontext-ctx-management-env-profiles-ActiveProfilesResolver]] -Furthermore, it is sometimes necessary to resolve active profiles for tests -programmatically instead of declaratively -- for example, based on: - -* The current operating system. -* Whether tests are being run on a continuous integration build server. -* The presence of certain environment variables. -* The presence of custom class-level annotations. -* Other concerns. - -To resolve active bean definition profiles programmatically, you can implement -a custom `ActiveProfilesResolver` and register it by using the `resolver` -attribute of `@ActiveProfiles`. For further information, see the corresponding -{api-spring-framework}/test/context/ActiveProfilesResolver.html[javadoc]. -The following example demonstrates how to implement and register a custom -`OperatingSystemActiveProfilesResolver`: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // "dev" profile overridden programmatically via a custom resolver - @ActiveProfiles( - resolver = OperatingSystemActiveProfilesResolver.class, - inheritProfiles = false) - class TransferServiceTest extends AbstractIntegrationTest { - // test body - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // "dev" profile overridden programmatically via a custom resolver - @ActiveProfiles( - resolver = OperatingSystemActiveProfilesResolver::class, - inheritProfiles = false) - class TransferServiceTest : AbstractIntegrationTest() { - // test body - } ----- - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver { - - @Override - public String[] resolve(Class testClass) { - String profile = ...; - // determine the value of profile based on the operating system - return new String[] {profile}; - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver { - - override fun resolve(testClass: Class<*>): Array { - val profile: String = ... - // determine the value of profile based on the operating system - return arrayOf(profile) - } - } ----- - -[[testcontext-ctx-management-property-sources]] -===== Context Configuration with Test Property Sources - -The Spring Framework has first-class support for the notion of an environment with a -hierarchy of property sources, and you can configure integration tests with test-specific -property sources. In contrast to the `@PropertySource` annotation used on -`@Configuration` classes, you can declare the `@TestPropertySource` annotation on a test -class to declare resource locations for test properties files or inlined properties. -These test property sources are added to the set of `PropertySources` in the -`Environment` for the `ApplicationContext` loaded for the annotated integration test. - -[NOTE] -==== -You can use `@TestPropertySource` with any implementation of the `SmartContextLoader` -SPI, but `@TestPropertySource` is not supported with implementations of the older -`ContextLoader` SPI. - -Implementations of `SmartContextLoader` gain access to merged test property source values -through the `getPropertySourceLocations()` and `getPropertySourceProperties()` methods in -`MergedContextConfiguration`. -==== - -====== Declaring Test Property Sources - -You can configure test properties files by using the `locations` or `value` attribute of -`@TestPropertySource`. - -Both traditional and XML-based properties file formats are supported -- for example, -`"classpath:/com/example/test.properties"` or `"file:///path/to/file.xml"`. - -Each path is interpreted as a Spring `Resource`. A plain path (for example, -`"test.properties"`) is treated as a classpath resource that is relative to the package -in which the test class is defined. A path starting with a slash is treated as an -absolute classpath resource (for example: `"/org/example/test.xml"`). A path that -references a URL (for example, a path prefixed with `classpath:`, `file:`, or `http:`) is -loaded by using the specified resource protocol. Resource location wildcards (such as -`**/*.properties`) are not permitted: Each location must evaluate to exactly one -`.properties` or `.xml` resource. - -The following example uses a test properties file: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @TestPropertySource("/test.properties") // <1> - class MyIntegrationTests { - // class body... - } ----- -<1> Specifying a properties file with an absolute path. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @TestPropertySource("/test.properties") // <1> - class MyIntegrationTests { - // class body... - } ----- -<1> Specifying a properties file with an absolute path. - - -You can configure inlined properties in the form of key-value pairs by using the -`properties` attribute of `@TestPropertySource`, as shown in the next example. All -key-value pairs are added to the enclosing `Environment` as a single test -`PropertySource` with the highest precedence. - -The supported syntax for key-value pairs is the same as the syntax defined for entries in -a Java properties file: - -* `key=value` -* `key:value` -* `key value` - -The following example sets two inlined properties: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) // <1> - class MyIntegrationTests { - // class body... - } ----- -<1> Setting two properties by using two variations of the key-value syntax. - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) // <1> - class MyIntegrationTests { - // class body... - } ----- -<1> Setting two properties by using two variations of the key-value syntax. - -[NOTE] -==== -As of Spring Framework 5.2, `@TestPropertySource` can be used as _repeatable annotation_. -That means that you can have multiple declarations of `@TestPropertySource` on a single -test class, with the `locations` and `properties` from later `@TestPropertySource` -annotations overriding those from previous `@TestPropertySource` annotations. - -In addition, you may declare multiple composed annotations on a test class that are each -meta-annotated with `@TestPropertySource`, and all of those `@TestPropertySource` -declarations will contribute to your test property sources. - -Directly present `@TestPropertySource` annotations always take precedence over -meta-present `@TestPropertySource` annotations. In other words, `locations` and -`properties` from a directly present `@TestPropertySource` annotation will override the -`locations` and `properties` from a `@TestPropertySource` annotation used as a -meta-annotation. -==== - - -====== Default Properties File Detection - -If `@TestPropertySource` is declared as an empty annotation (that is, without explicit -values for the `locations` or `properties` attributes), an attempt is made to detect a -default properties file relative to the class that declared the annotation. For example, -if the annotated test class is `com.example.MyTest`, the corresponding default properties -file is `classpath:com/example/MyTest.properties`. If the default cannot be detected, an -`IllegalStateException` is thrown. - -====== Precedence - -Test properties have higher precedence than those defined in the operating system's -environment, Java system properties, or property sources added by the application -declaratively by using `@PropertySource` or programmatically. Thus, test properties can -be used to selectively override properties loaded from system and application property -sources. Furthermore, inlined properties have higher precedence than properties loaded -from resource locations. Note, however, that properties registered via -<> have -higher precedence than those loaded via `@TestPropertySource`. - -In the next example, the `timezone` and `port` properties and any properties defined in -`"/test.properties"` override any properties of the same name that are defined in system -and application property sources. Furthermore, if the `"/test.properties"` file defines -entries for the `timezone` and `port` properties those are overridden by the inlined -properties declared by using the `properties` attribute. The following example shows how -to specify properties both in a file and inline: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration - @TestPropertySource( - locations = "/test.properties", - properties = {"timezone = GMT", "port: 4242"} - ) - class MyIntegrationTests { - // class body... - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration - @TestPropertySource("/test.properties", - properties = ["timezone = GMT", "port: 4242"] - ) - class MyIntegrationTests { - // class body... - } ----- - -====== Inheriting and Overriding Test Property Sources - -`@TestPropertySource` supports boolean `inheritLocations` and `inheritProperties` -attributes that denote whether resource locations for properties files and inlined -properties declared by superclasses should be inherited. The default value for both flags -is `true`. This means that a test class inherits the locations and inlined properties -declared by any superclasses. Specifically, the locations and inlined properties for a -test class are appended to the locations and inlined properties declared by superclasses. -Thus, subclasses have the option of extending the locations and inlined properties. Note -that properties that appear later shadow (that is, override) properties of the same name -that appear earlier. In addition, the aforementioned precedence rules apply for inherited -test property sources as well. - -If the `inheritLocations` or `inheritProperties` attribute in `@TestPropertySource` is -set to `false`, the locations or inlined properties, respectively, for the test class -shadow and effectively replace the configuration defined by superclasses. - -NOTE: As of Spring Framework 5.3, test configuration may also be inherited from enclosing -classes. See <> for details. - -In the next example, the `ApplicationContext` for `BaseTest` is loaded by using only the -`base.properties` file as a test property source. In contrast, the `ApplicationContext` -for `ExtendedTest` is loaded by using the `base.properties` and `extended.properties` -files as test property source locations. The following example shows how to define -properties in both a subclass and its superclass by using `properties` files: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @TestPropertySource("base.properties") - @ContextConfiguration - class BaseTest { - // ... - } - - @TestPropertySource("extended.properties") - @ContextConfiguration - class ExtendedTest extends BaseTest { - // ... - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @TestPropertySource("base.properties") - @ContextConfiguration - open class BaseTest { - // ... - } - - @TestPropertySource("extended.properties") - @ContextConfiguration - class ExtendedTest : BaseTest() { - // ... - } ----- - -In the next example, the `ApplicationContext` for `BaseTest` is loaded by using only the -inlined `key1` property. In contrast, the `ApplicationContext` for `ExtendedTest` is -loaded by using the inlined `key1` and `key2` properties. The following example shows how -to define properties in both a subclass and its superclass by using inline properties: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @TestPropertySource(properties = "key1 = value1") - @ContextConfiguration - class BaseTest { - // ... - } - - @TestPropertySource(properties = "key2 = value2") - @ContextConfiguration - class ExtendedTest extends BaseTest { - // ... - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @TestPropertySource(properties = ["key1 = value1"]) - @ContextConfiguration - open class BaseTest { - // ... - } - - @TestPropertySource(properties = ["key2 = value2"]) - @ContextConfiguration - class ExtendedTest : BaseTest() { - // ... - } ----- - -[[testcontext-ctx-management-dynamic-property-sources]] -===== Context Configuration with Dynamic Property Sources - -As of Spring Framework 5.2.5, the TestContext framework provides support for _dynamic_ -properties via the `@DynamicPropertySource` annotation. This annotation can be used in -integration tests that need to add properties with dynamic values to the set of -`PropertySources` in the `Environment` for the `ApplicationContext` loaded for the -integration test. - -[NOTE] -==== -The `@DynamicPropertySource` annotation and its supporting infrastructure were -originally designed to allow properties from -https://www.testcontainers.org/[Testcontainers] based tests to be exposed easily to -Spring integration tests. However, this feature may also be used with any form of -external resource whose lifecycle is maintained outside the test's `ApplicationContext`. -==== - -In contrast to the <> -annotation that is applied at the class level, `@DynamicPropertySource` must be applied -to a `static` method that accepts a single `DynamicPropertyRegistry` argument which is -used to add _name-value_ pairs to the `Environment`. Values are dynamic and provided via -a `Supplier` which is only invoked when the property is resolved. Typically, method -references are used to supply values, as can be seen in the following example which uses -the Testcontainers project to manage a Redis container outside of the Spring -`ApplicationContext`. The IP address and port of the managed Redis container are made -available to components within the test's `ApplicationContext` via the `redis.host` and -`redis.port` properties. These properties can be accessed via Spring's `Environment` -abstraction or injected directly into Spring-managed components – for example, via -`@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively. - -[TIP] -==== -If you use `@DynamicPropertySource` in a base class and discover that tests in subclasses -fail because the dynamic properties change between subclasses, you may need to annotate -your base class with <> to -ensure that each subclass gets its own `ApplicationContext` with the correct dynamic -properties. -==== - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(/* ... */) - @Testcontainers - class ExampleIntegrationTests { - - @Container - static RedisContainer redis = new RedisContainer(); - - @DynamicPropertySource - static void redisProperties(DynamicPropertyRegistry registry) { - registry.add("redis.host", redis::getHost); - registry.add("redis.port", redis::getMappedPort); - } - - // tests ... - - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(/* ... */) - @Testcontainers - class ExampleIntegrationTests { - - companion object { - - @Container - @JvmStatic - val redis: RedisContainer = RedisContainer() - - @DynamicPropertySource - @JvmStatic - fun redisProperties(registry: DynamicPropertyRegistry) { - registry.add("redis.host", redis::getHost) - registry.add("redis.port", redis::getMappedPort) - } - } - - // tests ... - - } ----- - -====== Precedence - -Dynamic properties have higher precedence than those loaded from `@TestPropertySource`, -the operating system's environment, Java system properties, or property sources added by -the application declaratively by using `@PropertySource` or programmatically. Thus, -dynamic properties can be used to selectively override properties loaded via -`@TestPropertySource`, system property sources, and application property sources. - -[[testcontext-ctx-management-web]] -===== Loading a `WebApplicationContext` - -To instruct the TestContext framework to load a `WebApplicationContext` instead of a -standard `ApplicationContext`, you can annotate the respective test class with -`@WebAppConfiguration`. - -The presence of `@WebAppConfiguration` on your test class instructs the TestContext -framework (TCF) that a `WebApplicationContext` (WAC) should be loaded for your -integration tests. In the background, the TCF makes sure that a `MockServletContext` is -created and supplied to your test's WAC. By default, the base resource path for your -`MockServletContext` is set to `src/main/webapp`. This is interpreted as a path relative -to the root of your JVM (normally the path to your project). If you are familiar with the -directory structure of a web application in a Maven project, you know that -`src/main/webapp` is the default location for the root of your WAR. If you need to -override this default, you can provide an alternate path to the `@WebAppConfiguration` -annotation (for example, `@WebAppConfiguration("src/test/webapp")`). If you wish to -reference a base resource path from the classpath instead of the file system, you can use -Spring's `classpath:` prefix. - -Note that Spring's testing support for `WebApplicationContext` implementations is on par -with its support for standard `ApplicationContext` implementations. When testing with a -`WebApplicationContext`, you are free to declare XML configuration files, Groovy scripts, -or `@Configuration` classes by using `@ContextConfiguration`. You are also free to use -any other test annotations, such as `@ActiveProfiles`, `@TestExecutionListeners`, `@Sql`, -`@Rollback`, and others. - -The remaining examples in this section show some of the various configuration options for -loading a `WebApplicationContext`. The following example shows the TestContext -framework's support for convention over configuration: - -.Conventions -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - - // defaults to "file:src/main/webapp" - @WebAppConfiguration - - // detects "WacTests-context.xml" in the same package - // or static nested @Configuration classes - @ContextConfiguration - class WacTests { - //... - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - - // defaults to "file:src/main/webapp" - @WebAppConfiguration - - // detects "WacTests-context.xml" in the same package - // or static nested @Configuration classes - @ContextConfiguration - class WacTests { - //... - } ----- - -If you annotate a test class with `@WebAppConfiguration` without specifying a resource -base path, the resource path effectively defaults to `file:src/main/webapp`. Similarly, -if you declare `@ContextConfiguration` without specifying resource `locations`, component -`classes`, or context `initializers`, Spring tries to detect the presence of your -configuration by using conventions (that is, `WacTests-context.xml` in the same package -as the `WacTests` class or static nested `@Configuration` classes). - -The following example shows how to explicitly declare a resource base path with -`@WebAppConfiguration` and an XML resource location with `@ContextConfiguration`: - -.Default resource semantics -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - - // file system resource - @WebAppConfiguration("webapp") - - // classpath resource - @ContextConfiguration("/spring/test-servlet-config.xml") - class WacTests { - //... - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - - // file system resource - @WebAppConfiguration("webapp") - - // classpath resource - @ContextConfiguration("/spring/test-servlet-config.xml") - class WacTests { - //... - } ----- - -The important thing to note here is the different semantics for paths with these two -annotations. By default, `@WebAppConfiguration` resource paths are file system based, -whereas `@ContextConfiguration` resource locations are classpath based. - -The following example shows that we can override the default resource semantics for both -annotations by specifying a Spring resource prefix: - -.Explicit resource semantics -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - - // classpath resource - @WebAppConfiguration("classpath:test-web-resources") - - // file system resource - @ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml") - class WacTests { - //... - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - - // classpath resource - @WebAppConfiguration("classpath:test-web-resources") - - // file system resource - @ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml") - class WacTests { - //... - } ----- - -Contrast the comments in this example with the previous example. - -.[[testcontext-ctx-management-web-mocks]]Working with Web Mocks --- -To provide comprehensive web testing support, the TestContext framework has a -`ServletTestExecutionListener` that is enabled by default. When testing against a -`WebApplicationContext`, this <> -sets up default thread-local state by using Spring Web's `RequestContextHolder` before -each test method and creates a `MockHttpServletRequest`, a `MockHttpServletResponse`, and -a `ServletWebRequest` based on the base resource path configured with -`@WebAppConfiguration`. `ServletTestExecutionListener` also ensures that the -`MockHttpServletResponse` and `ServletWebRequest` can be injected into the test instance, -and, once the test is complete, it cleans up thread-local state. - -Once you have a `WebApplicationContext` loaded for your test, you might find that you -need to interact with the web mocks -- for example, to set up your test fixture or to -perform assertions after invoking your web component. The following example shows which -mocks can be autowired into your test instance. Note that the `WebApplicationContext` and -`MockServletContext` are both cached across the test suite, whereas the other mocks are -managed per test method by the `ServletTestExecutionListener`. - -.Injecting mocks -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitWebConfig - class WacTests { - - @Autowired - WebApplicationContext wac; // cached - - @Autowired - MockServletContext servletContext; // cached - - @Autowired - MockHttpSession session; - - @Autowired - MockHttpServletRequest request; - - @Autowired - MockHttpServletResponse response; - - @Autowired - ServletWebRequest webRequest; - - //... - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitWebConfig - class WacTests { - - @Autowired - lateinit var wac: WebApplicationContext // cached - - @Autowired - lateinit var servletContext: MockServletContext // cached - - @Autowired - lateinit var session: MockHttpSession - - @Autowired - lateinit var request: MockHttpServletRequest - - @Autowired - lateinit var response: MockHttpServletResponse - - @Autowired - lateinit var webRequest: ServletWebRequest - - //... - } ----- --- - -[[testcontext-ctx-management-caching]] -===== Context Caching - -Once the TestContext framework loads an `ApplicationContext` (or `WebApplicationContext`) -for a test, that context is cached and reused for all subsequent tests that declare the -same unique context configuration within the same test suite. To understand how caching -works, it is important to understand what is meant by "`unique`" and "`test suite.`" - -An `ApplicationContext` can be uniquely identified by the combination of configuration -parameters that is used to load it. Consequently, the unique combination of configuration -parameters is used to generate a key under which the context is cached. The TestContext -framework uses the following configuration parameters to build the context cache key: - -* `locations` (from `@ContextConfiguration`) -* `classes` (from `@ContextConfiguration`) -* `contextInitializerClasses` (from `@ContextConfiguration`) -* `contextCustomizers` (from `ContextCustomizerFactory`) – this includes - `@DynamicPropertySource` methods as well as various features from Spring Boot's - testing support such as `@MockBean` and `@SpyBean`. -* `contextLoader` (from `@ContextConfiguration`) -* `parent` (from `@ContextHierarchy`) -* `activeProfiles` (from `@ActiveProfiles`) -* `propertySourceLocations` (from `@TestPropertySource`) -* `propertySourceProperties` (from `@TestPropertySource`) -* `resourceBasePath` (from `@WebAppConfiguration`) - -For example, if `TestClassA` specifies `{"app-config.xml", "test-config.xml"}` for the -`locations` (or `value`) attribute of `@ContextConfiguration`, the TestContext framework -loads the corresponding `ApplicationContext` and stores it in a `static` context cache -under a key that is based solely on those locations. So, if `TestClassB` also defines -`{"app-config.xml", "test-config.xml"}` for its locations (either explicitly or -implicitly through inheritance) but does not define `@WebAppConfiguration`, a different -`ContextLoader`, different active profiles, different context initializers, different -test property sources, or a different parent context, then the same `ApplicationContext` -is shared by both test classes. This means that the setup cost for loading an application -context is incurred only once (per test suite), and subsequent test execution is much -faster. - -.Test suites and forked processes -[NOTE] -==== -The Spring TestContext framework stores application contexts in a static cache. This -means that the context is literally stored in a `static` variable. In other words, if -tests run in separate processes, the static cache is cleared between each test -execution, which effectively disables the caching mechanism. - -To benefit from the caching mechanism, all tests must run within the same process or test -suite. This can be achieved by executing all tests as a group within an IDE. Similarly, -when executing tests with a build framework such as Ant, Maven, or Gradle, it is -important to make sure that the build framework does not fork between tests. For example, -if the -https://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html#forkMode[`forkMode`] -for the Maven Surefire plug-in is set to `always` or `pertest`, the TestContext framework -cannot cache application contexts between test classes, and the build process runs -significantly more slowly as a result. -==== - -The size of the context cache is bounded with a default maximum size of 32. Whenever the -maximum size is reached, a least recently used (LRU) eviction policy is used to evict and -close stale contexts. You can configure the maximum size from the command line or a build -script by setting a JVM system property named `spring.test.context.cache.maxSize`. As an -alternative, you can set the same property via the -<> mechanism. - -Since having a large number of application contexts loaded within a given test suite can -cause the suite to take an unnecessarily long time to run, it is often beneficial to -know exactly how many contexts have been loaded and cached. To view the statistics for -the underlying context cache, you can set the log level for the -`org.springframework.test.context.cache` logging category to `DEBUG`. - -In the unlikely case that a test corrupts the application context and requires reloading -(for example, by modifying a bean definition or the state of an application object), you -can annotate your test class or test method with `@DirtiesContext` (see the discussion of -`@DirtiesContext` in <>). This instructs Spring to remove the context from the cache and rebuild -the application context before running the next test that requires the same application -context. Note that support for the `@DirtiesContext` annotation is provided by the -`DirtiesContextBeforeModesTestExecutionListener` and the -`DirtiesContextTestExecutionListener`, which are enabled by default. - -.ApplicationContext lifecycle and console logging -[NOTE] -==== -When you need to debug a test executed with the Spring TestContext Framework, it can be -useful to analyze the console output (that is, output to the `SYSOUT` and `SYSERR` -streams). Some build tools and IDEs are able to associate console output with a given -test; however, some console output cannot be easily associated with a given test. - -With regard to console logging triggered by the Spring Framework itself or by components -registered in the `ApplicationContext`, it is important to understand the lifecycle of an -`ApplicationContext` that has been loaded by the Spring TestContext Framework within a -test suite. - -The `ApplicationContext` for a test is typically loaded when an instance of the test -class is being prepared -- for example, to perform dependency injection into `@Autowired` -fields of the test instance. This means that any console logging triggered during the -initialization of the `ApplicationContext` typically cannot be associated with an -individual test method. However, if the context is closed immediately before the -execution of a test method according to <> -semantics, a new instance of the context will be loaded just prior to execution of the -test method. In the latter scenario, an IDE or build tool may potentially associate -console logging with the individual test method. - -The `ApplicationContext` for a test can be closed via one of the following scenarios. - -* The context is closed according to `@DirtiesContext` semantics. -* The context is closed because it has been automatically evicted from the cache - according to the LRU eviction policy. -* The context is closed via a JVM shutdown hook when the JVM for the test suite - terminates. - -If the context is closed according to `@DirtiesContext` semantics after a particular test -method, an IDE or build tool may potentially associate console logging with the -individual test method. If the context is closed according to `@DirtiesContext` semantics -after a test class, any console logging triggered during the shutdown of the -`ApplicationContext` cannot be associated with an individual test method. Similarly, any -console logging triggered during the shutdown phase via a JVM shutdown hook cannot be -associated with an individual test method. - -When a Spring `ApplicationContext` is closed via a JVM shutdown hook, callbacks executed -during the shutdown phase are executed on a thread named `SpringContextShutdownHook`. So, -if you wish to disable console logging triggered when the `ApplicationContext` is closed -via a JVM shutdown hook, you may be able to register a custom filter with your logging -framework that allows you to ignore any logging initiated by that thread. -==== - -[[testcontext-ctx-management-ctx-hierarchies]] -===== Context Hierarchies - -When writing integration tests that rely on a loaded Spring `ApplicationContext`, it is -often sufficient to test against a single context. However, there are times when it is -beneficial or even necessary to test against a hierarchy of `ApplicationContext` -instances. For example, if you are developing a Spring MVC web application, you typically -have a root `WebApplicationContext` loaded by Spring's `ContextLoaderListener` and a -child `WebApplicationContext` loaded by Spring's `DispatcherServlet`. This results in a -parent-child context hierarchy where shared components and infrastructure configuration -are declared in the root context and consumed in the child context by web-specific -components. Another use case can be found in Spring Batch applications, where you often -have a parent context that provides configuration for shared batch infrastructure and a -child context for the configuration of a specific batch job. - -You can write integration tests that use context hierarchies by declaring context -configuration with the `@ContextHierarchy` annotation, either on an individual test class -or within a test class hierarchy. If a context hierarchy is declared on multiple classes -within a test class hierarchy, you can also merge or override the context configuration -for a specific, named level in the context hierarchy. When merging configuration for a -given level in the hierarchy, the configuration resource type (that is, XML configuration -files or component classes) must be consistent. Otherwise, it is perfectly acceptable to -have different levels in a context hierarchy configured using different resource types. - -The remaining JUnit Jupiter based examples in this section show common configuration -scenarios for integration tests that require the use of context hierarchies. - -.Single test class with context hierarchy --- -`ControllerIntegrationTests` represents a typical integration testing scenario for a -Spring MVC web application by declaring a context hierarchy that consists of two levels, -one for the root `WebApplicationContext` (loaded by using the `TestAppConfig` -`@Configuration` class) and one for the dispatcher servlet `WebApplicationContext` -(loaded by using the `WebConfig` `@Configuration` class). The `WebApplicationContext` -that is autowired into the test instance is the one for the child context (that is, the -lowest context in the hierarchy). The following listing shows this configuration scenario: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - @WebAppConfiguration - @ContextHierarchy({ - @ContextConfiguration(classes = TestAppConfig.class), - @ContextConfiguration(classes = WebConfig.class) - }) - class ControllerIntegrationTests { - - @Autowired - WebApplicationContext wac; - - // ... - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - @WebAppConfiguration - @ContextHierarchy( - ContextConfiguration(classes = [TestAppConfig::class]), - ContextConfiguration(classes = [WebConfig::class])) - class ControllerIntegrationTests { - - @Autowired - lateinit var wac: WebApplicationContext - - // ... - } ----- --- - - -.Class hierarchy with implicit parent context --- -The test classes in this example define a context hierarchy within a test class -hierarchy. `AbstractWebTests` declares the configuration for a root -`WebApplicationContext` in a Spring-powered web application. Note, however, that -`AbstractWebTests` does not declare `@ContextHierarchy`. Consequently, subclasses of -`AbstractWebTests` can optionally participate in a context hierarchy or follow the -standard semantics for `@ContextConfiguration`. `SoapWebServiceTests` and -`RestWebServiceTests` both extend `AbstractWebTests` and define a context hierarchy by -using `@ContextHierarchy`. The result is that three application contexts are loaded (one -for each declaration of `@ContextConfiguration`), and the application context loaded -based on the configuration in `AbstractWebTests` is set as the parent context for each of -the contexts loaded for the concrete subclasses. The following listing shows this -configuration scenario: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - @WebAppConfiguration - @ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml") - public abstract class AbstractWebTests {} - - @ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml")) - public class SoapWebServiceTests extends AbstractWebTests {} - - @ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml")) - public class RestWebServiceTests extends AbstractWebTests {} ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - @WebAppConfiguration - @ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml") - abstract class AbstractWebTests - - @ContextHierarchy(ContextConfiguration("/spring/soap-ws-config.xml")) - class SoapWebServiceTests : AbstractWebTests() - - @ContextHierarchy(ContextConfiguration("/spring/rest-ws-config.xml")) - class RestWebServiceTests : AbstractWebTests() - ----- --- - -.Class hierarchy with merged context hierarchy configuration --- -The classes in this example show the use of named hierarchy levels in order to merge the -configuration for specific levels in a context hierarchy. `BaseTests` defines two levels -in the hierarchy, `parent` and `child`. `ExtendedTests` extends `BaseTests` and instructs -the Spring TestContext Framework to merge the context configuration for the `child` -hierarchy level, by ensuring that the names declared in the `name` attribute in -`@ContextConfiguration` are both `child`. The result is that three application contexts -are loaded: one for `/app-config.xml`, one for `/user-config.xml`, and one for -`{"/user-config.xml", "/order-config.xml"}`. As with the previous example, the -application context loaded from `/app-config.xml` is set as the parent context for the -contexts loaded from `/user-config.xml` and `{"/user-config.xml", "/order-config.xml"}`. -The following listing shows this configuration scenario: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - @ContextHierarchy({ - @ContextConfiguration(name = "parent", locations = "/app-config.xml"), - @ContextConfiguration(name = "child", locations = "/user-config.xml") - }) - class BaseTests {} - - @ContextHierarchy( - @ContextConfiguration(name = "child", locations = "/order-config.xml") - ) - class ExtendedTests extends BaseTests {} ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - @ContextHierarchy( - ContextConfiguration(name = "parent", locations = ["/app-config.xml"]), - ContextConfiguration(name = "child", locations = ["/user-config.xml"])) - open class BaseTests {} - - @ContextHierarchy( - ContextConfiguration(name = "child", locations = ["/order-config.xml"]) - ) - class ExtendedTests : BaseTests() {} ----- --- - -.Class hierarchy with overridden context hierarchy configuration --- -In contrast to the previous example, this example demonstrates how to override the -configuration for a given named level in a context hierarchy by setting the -`inheritLocations` flag in `@ContextConfiguration` to `false`. Consequently, the -application context for `ExtendedTests` is loaded only from `/test-user-config.xml` and -has its parent set to the context loaded from `/app-config.xml`. The following listing -shows this configuration scenario: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - @ContextHierarchy({ - @ContextConfiguration(name = "parent", locations = "/app-config.xml"), - @ContextConfiguration(name = "child", locations = "/user-config.xml") - }) - class BaseTests {} - - @ContextHierarchy( - @ContextConfiguration( - name = "child", - locations = "/test-user-config.xml", - inheritLocations = false - )) - class ExtendedTests extends BaseTests {} ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - @ContextHierarchy( - ContextConfiguration(name = "parent", locations = ["/app-config.xml"]), - ContextConfiguration(name = "child", locations = ["/user-config.xml"])) - open class BaseTests {} - - @ContextHierarchy( - ContextConfiguration( - name = "child", - locations = ["/test-user-config.xml"], - inheritLocations = false - )) - class ExtendedTests : BaseTests() {} ----- - -.Dirtying a context within a context hierarchy -NOTE: If you use `@DirtiesContext` in a test whose context is configured as part of a -context hierarchy, you can use the `hierarchyMode` flag to control how the context cache -is cleared. For further details, see the discussion of `@DirtiesContext` in -<> and the -{api-spring-framework}/test/annotation/DirtiesContext.html[`@DirtiesContext`] javadoc. --- - -[[testcontext-fixture-di]] -==== Dependency Injection of Test Fixtures - -When you use the `DependencyInjectionTestExecutionListener` (which is configured by -default), the dependencies of your test instances are injected from beans in the -application context that you configured with `@ContextConfiguration` or related -annotations. You may use setter injection, field injection, or both, depending on -which annotations you choose and whether you place them on setter methods or fields. -If you are using JUnit Jupiter you may also optionally use constructor injection -(see <>). For consistency with Spring's annotation-based -injection support, you may also use Spring's `@Autowired` annotation or the `@Inject` -annotation from JSR-330 for field and setter injection. - -TIP: For testing frameworks other than JUnit Jupiter, the TestContext framework does not -participate in instantiation of the test class. Thus, the use of `@Autowired` or -`@Inject` for constructors has no effect for test classes. - -NOTE: Although field injection is discouraged in production code, field injection is -actually quite natural in test code. The rationale for the difference is that you will -never instantiate your test class directly. Consequently, there is no need to be able to -invoke a `public` constructor or setter method on your test class. - -Because `@Autowired` is used to perform <>, if you have multiple bean definitions of the same type, you cannot rely on this -approach for those particular beans. In that case, you can use `@Autowired` in -conjunction with `@Qualifier`. You can also choose to use `@Inject` in conjunction with -`@Named`. Alternatively, if your test class has access to its `ApplicationContext`, you -can perform an explicit lookup by using (for example) a call to -`applicationContext.getBean("titleRepository", TitleRepository.class)`. - -If you do not want dependency injection applied to your test instances, do not annotate -fields or setter methods with `@Autowired` or `@Inject`. Alternatively, you can disable -dependency injection altogether by explicitly configuring your class with -`@TestExecutionListeners` and omitting `DependencyInjectionTestExecutionListener.class` -from the list of listeners. - -Consider the scenario of testing a `HibernateTitleRepository` class, as outlined in the -<> section. The next two code listings demonstrate the -use of `@Autowired` on fields and setter methods. The application context configuration -is presented after all sample code listings. - -[NOTE] -==== -The dependency injection behavior in the following code listings is not specific to JUnit -Jupiter. The same DI techniques can be used in conjunction with any supported testing -framework. - -The following examples make calls to static assertion methods, such as `assertNotNull()`, -but without prepending the call with `Assertions`. In such cases, assume that the method -was properly imported through an `import static` declaration that is not shown in the -example. -==== - -The first code listing shows a JUnit Jupiter based implementation of the test class that -uses `@Autowired` for field injection: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // specifies the Spring configuration to load for this test fixture - @ContextConfiguration("repository-config.xml") - class HibernateTitleRepositoryTests { - - // this instance will be dependency injected by type - @Autowired - HibernateTitleRepository titleRepository; - - @Test - void findById() { - Title title = titleRepository.findById(new Long(10)); - assertNotNull(title); - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // specifies the Spring configuration to load for this test fixture - @ContextConfiguration("repository-config.xml") - class HibernateTitleRepositoryTests { - - // this instance will be dependency injected by type - @Autowired - lateinit var titleRepository: HibernateTitleRepository - - @Test - fun findById() { - val title = titleRepository.findById(10) - assertNotNull(title) - } - } ----- - -Alternatively, you can configure the class to use `@Autowired` for setter injection, as -follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ExtendWith(SpringExtension.class) - // specifies the Spring configuration to load for this test fixture - @ContextConfiguration("repository-config.xml") - class HibernateTitleRepositoryTests { - - // this instance will be dependency injected by type - HibernateTitleRepository titleRepository; - - @Autowired - void setTitleRepository(HibernateTitleRepository titleRepository) { - this.titleRepository = titleRepository; - } - - @Test - void findById() { - Title title = titleRepository.findById(new Long(10)); - assertNotNull(title); - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ExtendWith(SpringExtension::class) - // specifies the Spring configuration to load for this test fixture - @ContextConfiguration("repository-config.xml") - class HibernateTitleRepositoryTests { - - // this instance will be dependency injected by type - lateinit var titleRepository: HibernateTitleRepository - - @Autowired - fun setTitleRepository(titleRepository: HibernateTitleRepository) { - this.titleRepository = titleRepository - } - - @Test - fun findById() { - val title = titleRepository.findById(10) - assertNotNull(title) - } - } ----- - -The preceding code listings use the same XML context file referenced by the -`@ContextConfiguration` annotation (that is, `repository-config.xml`). The following -shows this configuration: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - - - - - ----- - -[NOTE] -===== -If you are extending from a Spring-provided test base class that happens to use -`@Autowired` on one of its setter methods, you might have multiple beans of the affected -type defined in your application context (for example, multiple `DataSource` beans). In -such a case, you can override the setter method and use the `@Qualifier` annotation to -indicate a specific target bean, as follows (but make sure to delegate to the overridden -method in the superclass as well): - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // ... - - @Autowired - @Override - public void setDataSource(@Qualifier("myDataSource") DataSource dataSource) { - super.setDataSource(dataSource); - } - - // ... ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // ... - - @Autowired - override fun setDataSource(@Qualifier("myDataSource") dataSource: DataSource) { - super.setDataSource(dataSource) - } - - // ... ----- - -The specified qualifier value indicates the specific `DataSource` bean to inject, -narrowing the set of type matches to a specific bean. Its value is matched against -`` declarations within the corresponding `` definitions. The bean name -is used as a fallback qualifier value, so you can effectively also point to a specific -bean by name there (as shown earlier, assuming that `myDataSource` is the bean `id`). -===== - - -[[testcontext-web-scoped-beans]] -==== Testing Request- and Session-scoped Beans - -Spring has supported <> since the early years, and you can test your request-scoped and session-scoped -beans by following these steps: - -* Ensure that a `WebApplicationContext` is loaded for your test by annotating your test - class with `@WebAppConfiguration`. -* Inject the mock request or session into your test instance and prepare your test - fixture as appropriate. -* Invoke your web component that you retrieved from the configured - `WebApplicationContext` (with dependency injection). -* Perform assertions against the mocks. - -The next code snippet shows the XML configuration for a login use case. Note that the -`userService` bean has a dependency on a request-scoped `loginAction` bean. Also, the -`LoginAction` is instantiated by using <> that -retrieve the username and password from the current HTTP request. In our test, we want to -configure these request parameters through the mock managed by the TestContext framework. -The following listing shows the configuration for this use case: - -.Request-scoped bean configuration -[source,xml,indent=0] ----- - - - - - - - - - ----- - -In `RequestScopedBeanTests`, we inject both the `UserService` (that is, the subject under -test) and the `MockHttpServletRequest` into our test instance. Within our -`requestScope()` test method, we set up our test fixture by setting request parameters in -the provided `MockHttpServletRequest`. When the `loginUser()` method is invoked on our -`userService`, we are assured that the user service has access to the request-scoped -`loginAction` for the current `MockHttpServletRequest` (that is, the one in which we just -set parameters). We can then perform assertions against the results based on the known -inputs for the username and password. The following listing shows how to do so: - -.Request-scoped bean test -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitWebConfig - class RequestScopedBeanTests { - - @Autowired UserService userService; - @Autowired MockHttpServletRequest request; - - @Test - void requestScope() { - request.setParameter("user", "enigma"); - request.setParameter("pswd", "$pr!ng"); - - LoginResults results = userService.loginUser(); - // assert results - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitWebConfig - class RequestScopedBeanTests { - - @Autowired lateinit var userService: UserService - @Autowired lateinit var request: MockHttpServletRequest - - @Test - fun requestScope() { - request.setParameter("user", "enigma") - request.setParameter("pswd", "\$pr!ng") - - val results = userService.loginUser() - // assert results - } - } ----- - -The following code snippet is similar to the one we saw earlier for a request-scoped -bean. However, this time, the `userService` bean has a dependency on a session-scoped -`userPreferences` bean. Note that the `UserPreferences` bean is instantiated by using a -SpEL expression that retrieves the theme from the current HTTP session. In our test, we -need to configure a theme in the mock session managed by the TestContext framework. The -following example shows how to do so: - -.Session-scoped bean configuration -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - - - - - - - ----- - -In `SessionScopedBeanTests`, we inject the `UserService` and the `MockHttpSession` into -our test instance. Within our `sessionScope()` test method, we set up our test fixture by -setting the expected `theme` attribute in the provided `MockHttpSession`. When the -`processUserPreferences()` method is invoked on our `userService`, we are assured that -the user service has access to the session-scoped `userPreferences` for the current -`MockHttpSession`, and we can perform assertions against the results based on the -configured theme. The following example shows how to do so: - -.Session-scoped bean test -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitWebConfig - class SessionScopedBeanTests { - - @Autowired UserService userService; - @Autowired MockHttpSession session; - - @Test - void sessionScope() throws Exception { - session.setAttribute("theme", "blue"); - - Results results = userService.processUserPreferences(); - // assert results - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitWebConfig - class SessionScopedBeanTests { - - @Autowired lateinit var userService: UserService - @Autowired lateinit var session: MockHttpSession - - @Test - fun sessionScope() { - session.setAttribute("theme", "blue") - - val results = userService.processUserPreferences() - // assert results - } - } ----- - -[[testcontext-tx]] -==== Transaction Management - -In the TestContext framework, transactions are managed by the -`TransactionalTestExecutionListener`, which is configured by default, even if you do not -explicitly declare `@TestExecutionListeners` on your test class. To enable support for -transactions, however, you must configure a `PlatformTransactionManager` bean in the -`ApplicationContext` that is loaded with `@ContextConfiguration` semantics (further -details are provided later). In addition, you must declare Spring's `@Transactional` -annotation either at the class or the method level for your tests. - -[[testcontext-tx-test-managed-transactions]] -===== Test-managed Transactions - -Test-managed transactions are transactions that are managed declaratively by using the -`TransactionalTestExecutionListener` or programmatically by using `TestTransaction` -(described later). You should not confuse such transactions with Spring-managed -transactions (those managed directly by Spring within the `ApplicationContext` loaded for -tests) or application-managed transactions (those managed programmatically within -application code that is invoked by tests). Spring-managed and application-managed -transactions typically participate in test-managed transactions. However, you should use -caution if Spring-managed or application-managed transactions are configured with any -propagation type other than `REQUIRED` or `SUPPORTS` (see the discussion on -<> for details). - -.Preemptive timeouts and test-managed transactions -[WARNING] -==== -Caution must be taken when using any form of preemptive timeouts from a testing framework -in conjunction with Spring's test-managed transactions. - -Specifically, Spring’s testing support binds transaction state to the current thread (via -a `java.lang.ThreadLocal` variable) _before_ the current test method is invoked. If a -testing framework invokes the current test method in a new thread in order to support a -preemptive timeout, any actions performed within the current test method will _not_ be -invoked within the test-managed transaction. Consequently, the result of any such actions -will not be rolled back with the test-managed transaction. On the contrary, such actions -will be committed to the persistent store -- for example, a relational database -- even -though the test-managed transaction is properly rolled back by Spring. - -Situations in which this can occur include but are not limited to the following. - -* JUnit 4's `@Test(timeout = ...)` support and `TimeOut` rule -* JUnit Jupiter's `assertTimeoutPreemptively(...)` methods in the - `org.junit.jupiter.api.Assertions` class -* TestNG's `@Test(timeOut = ...)` support -==== - -[[testcontext-tx-enabling-transactions]] -===== Enabling and Disabling Transactions - -Annotating a test method with `@Transactional` causes the test to be run within a -transaction that is, by default, automatically rolled back after completion of the test. -If a test class is annotated with `@Transactional`, each test method within that class -hierarchy runs within a transaction. Test methods that are not annotated with -`@Transactional` (at the class or method level) are not run within a transaction. Note -that `@Transactional` is not supported on test lifecycle methods — for example, methods -annotated with JUnit Jupiter's `@BeforeAll`, `@BeforeEach`, etc. Furthermore, tests that -are annotated with `@Transactional` but have the `propagation` attribute set to -`NOT_SUPPORTED` or `NEVER` are not run within a transaction. - -[[testcontext-tx-attribute-support]] -.`@Transactional` attribute support -|=== -|Attribute |Supported for test-managed transactions - -|`value` and `transactionManager` |yes - -|`propagation` |only `Propagation.NOT_SUPPORTED` and `Propagation.NEVER` are supported - -|`isolation` |no - -|`timeout` |no - -|`readOnly` |no - -|`rollbackFor` and `rollbackForClassName` |no: use `TestTransaction.flagForRollback()` instead - -|`noRollbackFor` and `noRollbackForClassName` |no: use `TestTransaction.flagForCommit()` instead -|=== - -[TIP] -==== -Method-level lifecycle methods — for example, methods annotated with JUnit Jupiter's -`@BeforeEach` or `@AfterEach` — are run within a test-managed transaction. On the other -hand, suite-level and class-level lifecycle methods — for example, methods annotated with -JUnit Jupiter's `@BeforeAll` or `@AfterAll` and methods annotated with TestNG's -`@BeforeSuite`, `@AfterSuite`, `@BeforeClass`, or `@AfterClass` — are _not_ run within a -test-managed transaction. - -If you need to run code in a suite-level or class-level lifecycle method within a -transaction, you may wish to inject a corresponding `PlatformTransactionManager` into -your test class and then use that with a `TransactionTemplate` for programmatic -transaction management. -==== - -Note that <> and -<> -are preconfigured for transactional support at the class level. - -The following example demonstrates a common scenario for writing an integration test for -a Hibernate-based `UserRepository`: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(TestConfig.class) - @Transactional - class HibernateUserRepositoryTests { - - @Autowired - HibernateUserRepository repository; - - @Autowired - SessionFactory sessionFactory; - - JdbcTemplate jdbcTemplate; - - @Autowired - void setDataSource(DataSource dataSource) { - this.jdbcTemplate = new JdbcTemplate(dataSource); - } - - @Test - void createUser() { - // track initial state in test database: - final int count = countRowsInTable("user"); - - User user = new User(...); - repository.save(user); - - // Manual flush is required to avoid false positive in test - sessionFactory.getCurrentSession().flush(); - assertNumUsers(count + 1); - } - - private int countRowsInTable(String tableName) { - return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); - } - - private void assertNumUsers(int expected) { - assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")); - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(TestConfig::class) - @Transactional - class HibernateUserRepositoryTests { - - @Autowired - lateinit var repository: HibernateUserRepository - - @Autowired - lateinit var sessionFactory: SessionFactory - - lateinit var jdbcTemplate: JdbcTemplate - - @Autowired - fun setDataSource(dataSource: DataSource) { - this.jdbcTemplate = JdbcTemplate(dataSource) - } - - @Test - fun createUser() { - // track initial state in test database: - val count = countRowsInTable("user") - - val user = User() - repository.save(user) - - // Manual flush is required to avoid false positive in test - sessionFactory.getCurrentSession().flush() - assertNumUsers(count + 1) - } - - private fun countRowsInTable(tableName: String): Int { - return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName) - } - - private fun assertNumUsers(expected: Int) { - assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")) - } - } ----- - -As explained in <>, there is no need to -clean up the database after the `createUser()` method runs, since any changes made to the -database are automatically rolled back by the `TransactionalTestExecutionListener`. - -[[testcontext-tx-rollback-and-commit-behavior]] -===== Transaction Rollback and Commit Behavior - -By default, test transactions will be automatically rolled back after completion of the -test; however, transactional commit and rollback behavior can be configured declaratively -via the `@Commit` and `@Rollback` annotations. See the corresponding entries in the -<> section for further details. - -[[testcontext-tx-programmatic-tx-mgt]] -===== Programmatic Transaction Management - -You can interact with test-managed transactions programmatically by using the static -methods in `TestTransaction`. For example, you can use `TestTransaction` within test -methods, before methods, and after methods to start or end the current test-managed -transaction or to configure the current test-managed transaction for rollback or commit. -Support for `TestTransaction` is automatically available whenever the -`TransactionalTestExecutionListener` is enabled. - -The following example demonstrates some of the features of `TestTransaction`. See the -javadoc for {api-spring-framework}/test/context/transaction/TestTransaction.html[`TestTransaction`] -for further details. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @ContextConfiguration(classes = TestConfig.class) - public class ProgrammaticTransactionManagementTests extends - AbstractTransactionalJUnit4SpringContextTests { - - @Test - public void transactionalTest() { - // assert initial state in test database: - assertNumUsers(2); - - deleteFromTables("user"); - - // changes to the database will be committed! - TestTransaction.flagForCommit(); - TestTransaction.end(); - assertFalse(TestTransaction.isActive()); - assertNumUsers(0); - - TestTransaction.start(); - // perform other actions against the database that will - // be automatically rolled back after the test completes... - } - - protected void assertNumUsers(int expected) { - assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")); - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @ContextConfiguration(classes = [TestConfig::class]) - class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() { - - @Test - fun transactionalTest() { - // assert initial state in test database: - assertNumUsers(2) - - deleteFromTables("user") - - // changes to the database will be committed! - TestTransaction.flagForCommit() - TestTransaction.end() - assertFalse(TestTransaction.isActive()) - assertNumUsers(0) - - TestTransaction.start() - // perform other actions against the database that will - // be automatically rolled back after the test completes... - } - - protected fun assertNumUsers(expected: Int) { - assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")) - } - } ----- - -[[testcontext-tx-before-and-after-tx]] -===== Running Code Outside of a Transaction - -Occasionally, you may need to run certain code before or after a transactional test -method but outside the transactional context -- for example, to verify the initial -database state prior to running your test or to verify expected transactional commit -behavior after your test runs (if the test was configured to commit the transaction). -`TransactionalTestExecutionListener` supports the `@BeforeTransaction` and -`@AfterTransaction` annotations for exactly such scenarios. You can annotate any `void` -method in a test class or any `void` default method in a test interface with one of these -annotations, and the `TransactionalTestExecutionListener` ensures that your before -transaction method or after transaction method runs at the appropriate time. - -TIP: Any before methods (such as methods annotated with JUnit Jupiter's `@BeforeEach`) -and any after methods (such as methods annotated with JUnit Jupiter's `@AfterEach`) are -run within a transaction. In addition, methods annotated with `@BeforeTransaction` or -`@AfterTransaction` are not run for test methods that are not configured to run within a -transaction. - -[[testcontext-tx-mgr-config]] -===== Configuring a Transaction Manager - -`TransactionalTestExecutionListener` expects a `PlatformTransactionManager` bean to be -defined in the Spring `ApplicationContext` for the test. If there are multiple instances -of `PlatformTransactionManager` within the test's `ApplicationContext`, you can declare a -qualifier by using `@Transactional("myTxMgr")` or `@Transactional(transactionManager = -"myTxMgr")`, or `TransactionManagementConfigurer` can be implemented by an -`@Configuration` class. Consult the -{api-spring-framework}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager-org.springframework.test.context.TestContext-java.lang.String-[javadoc -for `TestContextTransactionUtils.retrieveTransactionManager()`] for details on the -algorithm used to look up a transaction manager in the test's `ApplicationContext`. - -[[testcontext-tx-annotation-demo]] -===== Demonstration of All Transaction-related Annotations - -The following JUnit Jupiter based example displays a fictitious integration testing -scenario that highlights all transaction-related annotations. The example is not intended -to demonstrate best practices but rather to demonstrate how these annotations can be -used. See the <> section for further -information and configuration examples. <> contains an additional example that uses `@Sql` for -declarative SQL script execution with default transaction rollback semantics. The -following example shows the relevant annotations: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig - @Transactional(transactionManager = "txMgr") - @Commit - class FictitiousTransactionalTest { - - @BeforeTransaction - void verifyInitialDatabaseState() { - // logic to verify the initial state before a transaction is started - } - - @BeforeEach - void setUpTestDataWithinTransaction() { - // set up test data within the transaction - } - - @Test - // overrides the class-level @Commit setting - @Rollback - void modifyDatabaseWithinTransaction() { - // logic which uses the test data and modifies database state - } - - @AfterEach - void tearDownWithinTransaction() { - // run "tear down" logic within the transaction - } - - @AfterTransaction - void verifyFinalDatabaseState() { - // logic to verify the final state after transaction has rolled back - } - - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig - @Transactional(transactionManager = "txMgr") - @Commit - class FictitiousTransactionalTest { - - @BeforeTransaction - fun verifyInitialDatabaseState() { - // logic to verify the initial state before a transaction is started - } - - @BeforeEach - fun setUpTestDataWithinTransaction() { - // set up test data within the transaction - } - - @Test - // overrides the class-level @Commit setting - @Rollback - fun modifyDatabaseWithinTransaction() { - // logic which uses the test data and modifies database state - } - - @AfterEach - fun tearDownWithinTransaction() { - // run "tear down" logic within the transaction - } - - @AfterTransaction - fun verifyFinalDatabaseState() { - // logic to verify the final state after transaction has rolled back - } - - } ----- - -[[testcontext-tx-false-positives]] -.Avoid false positives when testing ORM code -[NOTE] -===== -When you test application code that manipulates the state of a Hibernate session or JPA -persistence context, make sure to flush the underlying unit of work within test methods -that run that code. Failing to flush the underlying unit of work can produce false -positives: Your test passes, but the same code throws an exception in a live, production -environment. Note that this applies to any ORM framework that maintains an in-memory unit -of work. In the following Hibernate-based example test case, one method demonstrates a -false positive, and the other method correctly exposes the results of flushing the -session: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // ... - - @Autowired - SessionFactory sessionFactory; - - @Transactional - @Test // no expected exception! - public void falsePositive() { - updateEntityInHibernateSession(); - // False positive: an exception will be thrown once the Hibernate - // Session is finally flushed (i.e., in production code) - } - - @Transactional - @Test(expected = ...) - public void updateWithSessionFlush() { - updateEntityInHibernateSession(); - // Manual flush is required to avoid false positive in test - sessionFactory.getCurrentSession().flush(); - } - - // ... ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // ... - - @Autowired - lateinit var sessionFactory: SessionFactory - - @Transactional - @Test // no expected exception! - fun falsePositive() { - updateEntityInHibernateSession() - // False positive: an exception will be thrown once the Hibernate - // Session is finally flushed (i.e., in production code) - } - - @Transactional - @Test(expected = ...) - fun updateWithSessionFlush() { - updateEntityInHibernateSession() - // Manual flush is required to avoid false positive in test - sessionFactory.getCurrentSession().flush() - } - - // ... ----- - -The following example shows matching methods for JPA: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // ... - - @PersistenceContext - EntityManager entityManager; - - @Transactional - @Test // no expected exception! - public void falsePositive() { - updateEntityInJpaPersistenceContext(); - // False positive: an exception will be thrown once the JPA - // EntityManager is finally flushed (i.e., in production code) - } - - @Transactional - @Test(expected = ...) - public void updateWithEntityManagerFlush() { - updateEntityInJpaPersistenceContext(); - // Manual flush is required to avoid false positive in test - entityManager.flush(); - } - - // ... ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // ... - - @PersistenceContext - lateinit var entityManager:EntityManager - - @Transactional - @Test // no expected exception! - fun falsePositive() { - updateEntityInJpaPersistenceContext() - // False positive: an exception will be thrown once the JPA - // EntityManager is finally flushed (i.e., in production code) - } - - @Transactional - @Test(expected = ...) - void updateWithEntityManagerFlush() { - updateEntityInJpaPersistenceContext() - // Manual flush is required to avoid false positive in test - entityManager.flush() - } - - // ... ----- -===== - -[[testcontext-tx-orm-lifecycle-callbacks]] -.Testing ORM entity lifecycle callbacks -[NOTE] -===== -Similar to the note about avoiding <> -when testing ORM code, if your application makes use of entity lifecycle callbacks (also -known as entity listeners), make sure to flush the underlying unit of work within test -methods that run that code. Failing to _flush_ or _clear_ the underlying unit of work can -result in certain lifecycle callbacks not being invoked. - -For example, when using JPA, `@PostPersist`, `@PreUpdate`, and `@PostUpdate` callbacks -will not be called unless `entityManager.flush()` is invoked after an entity has been -saved or updated. Similarly, if an entity is already attached to the current unit of work -(associated with the current persistence context), an attempt to reload the entity will -not result in a `@PostLoad` callback unless `entityManager.clear()` is invoked before the -attempt to reload the entity. - -The following example shows how to flush the `EntityManager` to ensure that -`@PostPersist` callbacks are invoked when an entity is persisted. An entity listener with -a `@PostPersist` callback method has been registered for the `Person` entity used in the -example. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // ... - - @Autowired - JpaPersonRepository repo; - - @PersistenceContext - EntityManager entityManager; - - @Transactional - @Test - void savePerson() { - // EntityManager#persist(...) results in @PrePersist but not @PostPersist - repo.save(new Person("Jane")); - - // Manual flush is required for @PostPersist callback to be invoked - entityManager.flush(); - - // Test code that relies on the @PostPersist callback - // having been invoked... - } - - // ... ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // ... - - @Autowired - lateinit var repo: JpaPersonRepository - - @PersistenceContext - lateinit var entityManager: EntityManager - - @Transactional - @Test - fun savePerson() { - // EntityManager#persist(...) results in @PrePersist but not @PostPersist - repo.save(Person("Jane")) - - // Manual flush is required for @PostPersist callback to be invoked - entityManager.flush() - - // Test code that relies on the @PostPersist callback - // having been invoked... - } - - // ... ----- - -See -https://github.com/spring-projects/spring-framework/blob/main/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/JpaEntityListenerTests.java[JpaEntityListenerTests] -in the Spring Framework test suite for working examples using all JPA lifecycle callbacks. -===== - - -[[testcontext-executing-sql]] -==== Executing SQL Scripts - -When writing integration tests against a relational database, it is often beneficial to -run SQL scripts to modify the database schema or insert test data into tables. The -`spring-jdbc` module provides support for _initializing_ an embedded or existing database -by executing SQL scripts when the Spring `ApplicationContext` is loaded. See -<> and -<> for details. - -Although it is very useful to initialize a database for testing _once_ when the -`ApplicationContext` is loaded, sometimes it is essential to be able to modify the -database _during_ integration tests. The following sections explain how to run SQL -scripts programmatically and declaratively during integration tests. - -[[testcontext-executing-sql-programmatically]] -===== Executing SQL scripts programmatically - -Spring provides the following options for executing SQL scripts programmatically within -integration test methods. - -* `org.springframework.jdbc.datasource.init.ScriptUtils` -* `org.springframework.jdbc.datasource.init.ResourceDatabasePopulator` -* `org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests` -* `org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests` - -`ScriptUtils` provides a collection of static utility methods for working with SQL -scripts and is mainly intended for internal use within the framework. However, if you -require full control over how SQL scripts are parsed and run, `ScriptUtils` may suit -your needs better than some of the other alternatives described later. See the -{api-spring-framework}/jdbc/datasource/init/ScriptUtils.html[javadoc] for individual -methods in `ScriptUtils` for further details. - -`ResourceDatabasePopulator` provides an object-based API for programmatically populating, -initializing, or cleaning up a database by using SQL scripts defined in external -resources. `ResourceDatabasePopulator` provides options for configuring the character -encoding, statement separator, comment delimiters, and error handling flags used when -parsing and running the scripts. Each of the configuration options has a reasonable -default value. See the -{api-spring-framework}/jdbc/datasource/init/ResourceDatabasePopulator.html[javadoc] for -details on default values. To run the scripts configured in a -`ResourceDatabasePopulator`, you can invoke either the `populate(Connection)` method to -run the populator against a `java.sql.Connection` or the `execute(DataSource)` method -to run the populator against a `javax.sql.DataSource`. The following example -specifies SQL scripts for a test schema and test data, sets the statement separator to -`@@`, and run the scripts against a `DataSource`: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Test - void databaseTest() { - ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); - populator.addScripts( - new ClassPathResource("test-schema.sql"), - new ClassPathResource("test-data.sql")); - populator.setSeparator("@@"); - populator.execute(this.dataSource); - // run code that uses the test schema and data - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Test - fun databaseTest() { - val populator = ResourceDatabasePopulator() - populator.addScripts( - ClassPathResource("test-schema.sql"), - ClassPathResource("test-data.sql")) - populator.setSeparator("@@") - populator.execute(dataSource) - // run code that uses the test schema and data - } ----- - -Note that `ResourceDatabasePopulator` internally delegates to `ScriptUtils` for parsing -and running SQL scripts. Similarly, the `executeSqlScript(..)` methods in -<> -and <> -internally use a `ResourceDatabasePopulator` to run SQL scripts. See the Javadoc for the -various `executeSqlScript(..)` methods for further details. - -[[testcontext-executing-sql-declaratively]] -===== Executing SQL scripts declaratively with @Sql - -In addition to the aforementioned mechanisms for running SQL scripts programmatically, -you can declaratively configure SQL scripts in the Spring TestContext Framework. -Specifically, you can declare the `@Sql` annotation on a test class or test method to -configure individual SQL statements or the resource paths to SQL scripts that should be -run against a given database before or after an integration test method. Support for -`@Sql` is provided by the `SqlScriptsTestExecutionListener`, which is enabled by default. - -NOTE: Method-level `@Sql` declarations override class-level declarations by default. As -of Spring Framework 5.2, however, this behavior may be configured per test class or per -test method via `@SqlMergeMode`. See -<> for further details. - -[[testcontext-executing-sql-declaratively-script-resources]] -====== Path Resource Semantics - -Each path is interpreted as a Spring `Resource`. A plain path (for example, -`"schema.sql"`) is treated as a classpath resource that is relative to the package in -which the test class is defined. A path starting with a slash is treated as an absolute -classpath resource (for example, `"/org/example/schema.sql"`). A path that references a -URL (for example, a path prefixed with `classpath:`, `file:`, `http:`) is loaded by using -the specified resource protocol. - -The following example shows how to use `@Sql` at the class level and at the method level -within a JUnit Jupiter based integration test class: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig - @Sql("/test-schema.sql") - class DatabaseTests { - - @Test - void emptySchemaTest() { - // run code that uses the test schema without any test data - } - - @Test - @Sql({"/test-schema.sql", "/test-user-data.sql"}) - void userTest() { - // run code that uses the test schema and test data - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig - @Sql("/test-schema.sql") - class DatabaseTests { - - @Test - fun emptySchemaTest() { - // run code that uses the test schema without any test data - } - - @Test - @Sql("/test-schema.sql", "/test-user-data.sql") - fun userTest() { - // run code that uses the test schema and test data - } - } ----- - -[[testcontext-executing-sql-declaratively-script-detection]] -====== Default Script Detection - -If no SQL scripts or statements are specified, an attempt is made to detect a `default` -script, depending on where `@Sql` is declared. If a default cannot be detected, an -`IllegalStateException` is thrown. - -* Class-level declaration: If the annotated test class is `com.example.MyTest`, the - corresponding default script is `classpath:com/example/MyTest.sql`. -* Method-level declaration: If the annotated test method is named `testMethod()` and is - defined in the class `com.example.MyTest`, the corresponding default script is - `classpath:com/example/MyTest.testMethod.sql`. - -[[testcontext-executing-sql-declaratively-multiple-annotations]] -====== Declaring Multiple `@Sql` Sets - -If you need to configure multiple sets of SQL scripts for a given test class or test -method but with different syntax configuration, different error handling rules, or -different execution phases per set, you can declare multiple instances of `@Sql`. With -Java 8, you can use `@Sql` as a repeatable annotation. Otherwise, you can use the -`@SqlGroup` annotation as an explicit container for declaring multiple instances of -`@Sql`. - -The following example shows how to use `@Sql` as a repeatable annotation with Java 8: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Test - @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")) - @Sql("/test-user-data.sql") - void userTest() { - // run code that uses the test schema and test data - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin ----- - -In the scenario presented in the preceding example, the `test-schema.sql` script uses a -different syntax for single-line comments. - -The following example is identical to the preceding example, except that the `@Sql` -declarations are grouped together within `@SqlGroup`. With Java 8 and above, the use of -`@SqlGroup` is optional, but you may need to use `@SqlGroup` for compatibility with -other JVM languages such as Kotlin. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Test - @SqlGroup({ - @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")), - @Sql("/test-user-data.sql") - )} - void userTest() { - // run code that uses the test schema and test data - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Test - @SqlGroup( - Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")), - Sql("/test-user-data.sql")) - fun userTest() { - // Run code that uses the test schema and test data - } ----- - -[[testcontext-executing-sql-declaratively-script-execution-phases]] -====== Script Execution Phases - -By default, SQL scripts are run before the corresponding test method. However, if -you need to run a particular set of scripts after the test method (for example, to clean -up database state), you can use the `executionPhase` attribute in `@Sql`, as the -following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @Test - @Sql( - scripts = "create-test-data.sql", - config = @SqlConfig(transactionMode = ISOLATED) - ) - @Sql( - scripts = "delete-test-data.sql", - config = @SqlConfig(transactionMode = ISOLATED), - executionPhase = AFTER_TEST_METHOD - ) - void userTest() { - // run code that needs the test data to be committed - // to the database outside of the test's transaction - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Test - @SqlGroup( - Sql("create-test-data.sql", - config = SqlConfig(transactionMode = ISOLATED)), - Sql("delete-test-data.sql", - config = SqlConfig(transactionMode = ISOLATED), - executionPhase = AFTER_TEST_METHOD)) - fun userTest() { - // run code that needs the test data to be committed - // to the database outside of the test's transaction - } ----- - -Note that `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from -`Sql.TransactionMode` and `Sql.ExecutionPhase`, respectively. - -[[testcontext-executing-sql-declaratively-script-configuration]] -====== Script Configuration with `@SqlConfig` - -You can configure script parsing and error handling by using the `@SqlConfig` annotation. -When declared as a class-level annotation on an integration test class, `@SqlConfig` -serves as global configuration for all SQL scripts within the test class hierarchy. When -declared directly by using the `config` attribute of the `@Sql` annotation, `@SqlConfig` -serves as local configuration for the SQL scripts declared within the enclosing `@Sql` -annotation. Every attribute in `@SqlConfig` has an implicit default value, which is -documented in the javadoc of the corresponding attribute. Due to the rules defined for -annotation attributes in the Java Language Specification, it is, unfortunately, not -possible to assign a value of `null` to an annotation attribute. Thus, in order to -support overrides of inherited global configuration, `@SqlConfig` attributes have an -explicit default value of either `""` (for Strings), `{}` (for arrays), or `DEFAULT` (for -enumerations). This approach lets local declarations of `@SqlConfig` selectively override -individual attributes from global declarations of `@SqlConfig` by providing a value other -than `""`, `{}`, or `DEFAULT`. Global `@SqlConfig` attributes are inherited whenever -local `@SqlConfig` attributes do not supply an explicit value other than `""`, `{}`, or -`DEFAULT`. Explicit local configuration, therefore, overrides global configuration. - -The configuration options provided by `@Sql` and `@SqlConfig` are equivalent to those -supported by `ScriptUtils` and `ResourceDatabasePopulator` but are a superset of those -provided by the `` XML namespace element. See the javadoc of -individual attributes in {api-spring-framework}/test/context/jdbc/Sql.html[`@Sql`] and -{api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] for details. - -[[testcontext-executing-sql-declaratively-tx]] -*Transaction management for `@Sql`* - -By default, the `SqlScriptsTestExecutionListener` infers the desired transaction -semantics for scripts configured by using `@Sql`. Specifically, SQL scripts are run -without a transaction, within an existing Spring-managed transaction (for example, a -transaction managed by the `TransactionalTestExecutionListener` for a test annotated with -`@Transactional`), or within an isolated transaction, depending on the configured value -of the `transactionMode` attribute in `@SqlConfig` and the presence of a -`PlatformTransactionManager` in the test's `ApplicationContext`. As a bare minimum, -however, a `javax.sql.DataSource` must be present in the test's `ApplicationContext`. - -If the algorithms used by `SqlScriptsTestExecutionListener` to detect a `DataSource` and -`PlatformTransactionManager` and infer the transaction semantics do not suit your needs, -you can specify explicit names by setting the `dataSource` and `transactionManager` -attributes of `@SqlConfig`. Furthermore, you can control the transaction propagation -behavior by setting the `transactionMode` attribute of `@SqlConfig` (for example, whether -scripts should be run in an isolated transaction). Although a thorough discussion of all -supported options for transaction management with `@Sql` is beyond the scope of this -reference manual, the javadoc for -{api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] and -{api-spring-framework}/test/context/jdbc/SqlScriptsTestExecutionListener.html[`SqlScriptsTestExecutionListener`] -provide detailed information, and the following example shows a typical testing scenario -that uses JUnit Jupiter and transactional tests with `@Sql`: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(TestDatabaseConfig.class) - @Transactional - class TransactionalSqlScriptsTests { - - final JdbcTemplate jdbcTemplate; - - @Autowired - TransactionalSqlScriptsTests(DataSource dataSource) { - this.jdbcTemplate = new JdbcTemplate(dataSource); - } - - @Test - @Sql("/test-data.sql") - void usersTest() { - // verify state in test database: - assertNumUsers(2); - // run code that uses the test data... - } - - int countRowsInTable(String tableName) { - return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); - } - - void assertNumUsers(int expected) { - assertEquals(expected, countRowsInTable("user"), - "Number of rows in the [user] table."); - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(TestDatabaseConfig::class) - @Transactional - class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) { - - val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource) - - @Test - @Sql("/test-data.sql") - fun usersTest() { - // verify state in test database: - assertNumUsers(2) - // run code that uses the test data... - } - - fun countRowsInTable(tableName: String): Int { - return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName) - } - - fun assertNumUsers(expected: Int) { - assertEquals(expected, countRowsInTable("user"), - "Number of rows in the [user] table.") - } - } ----- - -Note that there is no need to clean up the database after the `usersTest()` method is -run, since any changes made to the database (either within the test method or within the -`/test-data.sql` script) are automatically rolled back by the -`TransactionalTestExecutionListener` (see <> for -details). - -[[testcontext-executing-sql-declaratively-script-merging]] -====== Merging and Overriding Configuration with `@SqlMergeMode` - -As of Spring Framework 5.2, it is possible to merge method-level `@Sql` declarations with -class-level declarations. For example, this allows you to provide the configuration for a -database schema or some common test data once per test class and then provide additional, -use case specific test data per test method. To enable `@Sql` merging, annotate either -your test class or test method with `@SqlMergeMode(MERGE)`. To disable merging for a -specific test method (or specific test subclass), you can switch back to the default mode -via `@SqlMergeMode(OVERRIDE)`. Consult the <> for examples and further details. - - -[[testcontext-parallel-test-execution]] -==== Parallel Test Execution - -Spring Framework 5.0 introduced basic support for executing tests in parallel within a -single JVM when using the Spring TestContext Framework. In general, this means that most -test classes or test methods can be run in parallel without any changes to test code -or configuration. - -TIP: For details on how to set up parallel test execution, see the documentation for your -testing framework, build tool, or IDE. - -Keep in mind that the introduction of concurrency into your test suite can result in -unexpected side effects, strange runtime behavior, and tests that fail intermittently or -seemingly randomly. The Spring Team therefore provides the following general guidelines -for when not to run tests in parallel. - -Do not run tests in parallel if the tests: - -* Use Spring Framework's `@DirtiesContext` support. -* Use Spring Boot's `@MockBean` or `@SpyBean` support. -* Use JUnit 4's `@FixMethodOrder` support or any testing framework feature - that is designed to ensure that test methods run in a particular order. Note, - however, that this does not apply if entire test classes are run in parallel. -* Change the state of shared services or systems such as a database, message broker, - filesystem, and others. This applies to both embedded and external systems. - -[TIP] -==== -If parallel test execution fails with an exception stating that the `ApplicationContext` -for the current test is no longer active, this typically means that the -`ApplicationContext` was removed from the `ContextCache` in a different thread. - -This may be due to the use of `@DirtiesContext` or due to automatic eviction from the -`ContextCache`. If `@DirtiesContext` is the culprit, you either need to find a way to -avoid using `@DirtiesContext` or exclude such tests from parallel execution. If the -maximum size of the `ContextCache` has been exceeded, you can increase the maximum size -of the cache. See the discussion on <> -for details. -==== - -WARNING: Parallel test execution in the Spring TestContext Framework is only possible if -the underlying `TestContext` implementation provides a copy constructor, as explained in -the javadoc for {api-spring-framework}/test/context/TestContext.html[`TestContext`]. The -`DefaultTestContext` used in Spring provides such a constructor. However, if you use a -third-party library that provides a custom `TestContext` implementation, you need to -verify that it is suitable for parallel test execution. - - -[[testcontext-support-classes]] -==== TestContext Framework Support Classes - -This section describes the various classes that support the Spring TestContext Framework. - -[[testcontext-junit4-runner]] -===== Spring JUnit 4 Runner - -The Spring TestContext Framework offers full integration with JUnit 4 through a custom -runner (supported on JUnit 4.12 or higher). By annotating test classes with -`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)` -variant, developers can implement standard JUnit 4-based unit and integration tests and -simultaneously reap the benefits of the TestContext framework, such as support for -loading application contexts, dependency injection of test instances, transactional test -method execution, and so on. If you want to use the Spring TestContext Framework with an -alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners -(such as the `MockitoJUnitRunner`), you can, optionally, use -<> instead. - -The following code listing shows the minimal requirements for configuring a test class to -run with the custom Spring `Runner`: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @RunWith(SpringRunner.class) - @TestExecutionListeners({}) - public class SimpleTest { - - @Test - public void testMethod() { - // test logic... - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @RunWith(SpringRunner::class) - @TestExecutionListeners - class SimpleTest { - - @Test - fun testMethod() { - // test logic... - } - } ----- - -In the preceding example, `@TestExecutionListeners` is configured with an empty list, to -disable the default listeners, which otherwise would require an `ApplicationContext` to -be configured through `@ContextConfiguration`. - -[[testcontext-junit4-rules]] -===== Spring JUnit 4 Rules - -The `org.springframework.test.context.junit4.rules` package provides the following JUnit -4 rules (supported on JUnit 4.12 or higher): - -* `SpringClassRule` -* `SpringMethodRule` - -`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring -TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports -instance-level and method-level features of the Spring TestContext Framework. - -In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of -being independent of any `org.junit.runner.Runner` implementation and can, therefore, be -combined with existing alternative runners (such as JUnit 4's `Parameterized`) or -third-party runners (such as the `MockitoJUnitRunner`). - -To support the full functionality of the TestContext framework, you must combine a -`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way -to declare these rules in an integration test: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // Optionally specify a non-Spring Runner via @RunWith(...) - @ContextConfiguration - public class IntegrationTest { - - @ClassRule - public static final SpringClassRule springClassRule = new SpringClassRule(); - - @Rule - public final SpringMethodRule springMethodRule = new SpringMethodRule(); - - @Test - public void testMethod() { - // test logic... - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Optionally specify a non-Spring Runner via @RunWith(...) - @ContextConfiguration - class IntegrationTest { - - @Rule - val springMethodRule = SpringMethodRule() - - @Test - fun testMethod() { - // test logic... - } - - companion object { - @ClassRule - val springClassRule = SpringClassRule() - } - } ----- - -[[testcontext-support-classes-junit4]] -===== JUnit 4 Support Classes - -The `org.springframework.test.context.junit4` package provides the following support -classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher): - -* `AbstractJUnit4SpringContextTests` -* `AbstractTransactionalJUnit4SpringContextTests` - -`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the -Spring TestContext Framework with explicit `ApplicationContext` testing support in a -JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a -`protected` `applicationContext` instance variable that you can use to perform explicit -bean lookups or to test the state of the context as a whole. - -`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of -`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC -access. This class expects a `javax.sql.DataSource` bean and a -`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you -extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected` -`jdbcTemplate` instance variable that you can use to run SQL statements to query the -database. You can use such queries to confirm database state both before and after -running database-related application code, and Spring ensures that such queries run in -the scope of the same transaction as the application code. When used in conjunction with -an ORM tool, be sure to avoid <>. -As mentioned in <>, -`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that -delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`. -Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an -`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`. - -TIP: These classes are a convenience for extension. If you do not want your test classes -to be tied to a Spring-specific class hierarchy, you can configure your own custom test -classes by using `@RunWith(SpringRunner.class)` or <>. - -[[testcontext-junit-jupiter-extension]] -===== SpringExtension for JUnit Jupiter - -The Spring TestContext Framework offers full integration with the JUnit Jupiter testing -framework, introduced in JUnit 5. By annotating test classes with -`@ExtendWith(SpringExtension.class)`, you can implement standard JUnit Jupiter-based unit -and integration tests and simultaneously reap the benefits of the TestContext framework, -such as support for loading application contexts, dependency injection of test instances, -transactional test method execution, and so on. - -Furthermore, thanks to the rich extension API in JUnit Jupiter, Spring provides the -following features above and beyond the feature set that Spring supports for JUnit 4 and -TestNG: - -* Dependency injection for test constructors, test methods, and test lifecycle callback - methods. See <> for further details. -* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional - test execution] based on SpEL expressions, environment variables, system properties, - and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in - <> for further details and examples. -* Custom composed annotations that combine annotations from Spring and JUnit Jupiter. See - the `@TransactionalDevTestConfig` and `@TransactionalIntegrationTest` examples in - <> for further details. - -The following code listing shows how to configure a test class to use the -`SpringExtension` in conjunction with `@ContextConfiguration`: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // Instructs JUnit Jupiter to extend the test with Spring support. - @ExtendWith(SpringExtension.class) - // Instructs Spring to load an ApplicationContext from TestConfig.class - @ContextConfiguration(classes = TestConfig.class) - class SimpleTests { - - @Test - void testMethod() { - // test logic... - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Instructs JUnit Jupiter to extend the test with Spring support. - @ExtendWith(SpringExtension::class) - // Instructs Spring to load an ApplicationContext from TestConfig::class - @ContextConfiguration(classes = [TestConfig::class]) - class SimpleTests { - - @Test - fun testMethod() { - // test logic... - } - } ----- - -Since you can also use annotations in JUnit 5 as meta-annotations, Spring provides the -`@SpringJUnitConfig` and `@SpringJUnitWebConfig` composed annotations to simplify the -configuration of the test `ApplicationContext` and JUnit Jupiter. - -The following example uses `@SpringJUnitConfig` to reduce the amount of configuration -used in the previous example: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // Instructs Spring to register the SpringExtension with JUnit - // Jupiter and load an ApplicationContext from TestConfig.class - @SpringJUnitConfig(TestConfig.class) - class SimpleTests { - - @Test - void testMethod() { - // test logic... - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Instructs Spring to register the SpringExtension with JUnit - // Jupiter and load an ApplicationContext from TestConfig.class - @SpringJUnitConfig(TestConfig::class) - class SimpleTests { - - @Test - fun testMethod() { - // test logic... - } - } ----- - -Similarly, the following example uses `@SpringJUnitWebConfig` to create a -`WebApplicationContext` for use with JUnit Jupiter: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // Instructs Spring to register the SpringExtension with JUnit - // Jupiter and load a WebApplicationContext from TestWebConfig.class - @SpringJUnitWebConfig(TestWebConfig.class) - class SimpleWebTests { - - @Test - void testMethod() { - // test logic... - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Instructs Spring to register the SpringExtension with JUnit - // Jupiter and load a WebApplicationContext from TestWebConfig::class - @SpringJUnitWebConfig(TestWebConfig::class) - class SimpleWebTests { - - @Test - fun testMethod() { - // test logic... - } - } ----- - -See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in -<> for further details. - -[[testcontext-junit-jupiter-di]] -===== Dependency Injection with `SpringExtension` - -`SpringExtension` implements the -link:https://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`] -extension API from JUnit Jupiter, which lets Spring provide dependency injection for test -constructors, test methods, and test lifecycle callback methods. - -Specifically, `SpringExtension` can inject dependencies from the test's -`ApplicationContext` into test constructors and methods that are annotated with -`@BeforeAll`, `@AfterAll`, `@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, -`@ParameterizedTest`, and others. - -[[testcontext-junit-jupiter-di-constructor]] -====== Constructor Injection - -If a specific parameter in a constructor for a JUnit Jupiter test class is of type -`ApplicationContext` (or a sub-type thereof) or is annotated or meta-annotated with -`@Autowired`, `@Qualifier`, or `@Value`, Spring injects the value for that specific -parameter with the corresponding bean or value from the test's `ApplicationContext`. - -Spring can also be configured to autowire all arguments for a test class constructor if -the constructor is considered to be _autowirable_. A constructor is considered to be -autowirable if one of the following conditions is met (in order of precedence). - -* The constructor is annotated with `@Autowired`. -* `@TestConstructor` is present or meta-present on the test class with the `autowireMode` - attribute set to `ALL`. -* The default _test constructor autowire mode_ has been changed to `ALL`. - -See <> for details on the use of -`@TestConstructor` and how to change the global _test constructor autowire mode_. - -WARNING: If the constructor for a test class is considered to be _autowirable_, Spring -assumes the responsibility for resolving arguments for all parameters in the constructor. -Consequently, no other `ParameterResolver` registered with JUnit Jupiter can resolve -parameters for such a constructor. - -[WARNING] -==== -Constructor injection for test classes must not be used in conjunction with JUnit -Jupiter's `@TestInstance(PER_CLASS)` support if `@DirtiesContext` is used to close the -test's `ApplicationContext` before or after test methods. - -The reason is that `@TestInstance(PER_CLASS)` instructs JUnit Jupiter to cache the test -instance between test method invocations. Consequently, the test instance will retain -references to beans that were originally injected from an `ApplicationContext` that has -been subsequently closed. Since the constructor for the test class will only be invoked -once in such scenarios, dependency injection will not occur again, and subsequent tests -will interact with beans from the closed `ApplicationContext` which may result in errors. - -To use `@DirtiesContext` with "before test method" or "after test method" modes in -conjunction with `@TestInstance(PER_CLASS)`, one must configure dependencies from Spring -to be supplied via field or setter injection so that they can be re-injected between test -method invocations. -==== - -In the following example, Spring injects the `OrderService` bean from the -`ApplicationContext` loaded from `TestConfig.class` into the -`OrderServiceIntegrationTests` constructor. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(TestConfig.class) - class OrderServiceIntegrationTests { - - private final OrderService orderService; - - @Autowired - OrderServiceIntegrationTests(OrderService orderService) { - this.orderService = orderService; - } - - // tests that use the injected OrderService - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(TestConfig::class) - class OrderServiceIntegrationTests @Autowired constructor(private val orderService: OrderService){ - // tests that use the injected OrderService - } - ----- - -Note that this feature lets test dependencies be `final` and therefore immutable. - -If the `spring.test.constructor.autowire.mode` property is to `all` (see -<>), we can omit the declaration of -`@Autowired` on the constructor in the previous example, resulting in the following. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(TestConfig.class) - class OrderServiceIntegrationTests { - - private final OrderService orderService; - - OrderServiceIntegrationTests(OrderService orderService) { - this.orderService = orderService; - } - - // tests that use the injected OrderService - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(TestConfig::class) - class OrderServiceIntegrationTests(val orderService:OrderService) { - // tests that use the injected OrderService - } ----- - -[[testcontext-junit-jupiter-di-method]] -====== Method Injection - -If a parameter in a JUnit Jupiter test method or test lifecycle callback method is of -type `ApplicationContext` (or a sub-type thereof) or is annotated or meta-annotated with -`@Autowired`, `@Qualifier`, or `@Value`, Spring injects the value for that specific -parameter with the corresponding bean from the test's `ApplicationContext`. - -In the following example, Spring injects the `OrderService` from the `ApplicationContext` -loaded from `TestConfig.class` into the `deleteOrder()` test method: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(TestConfig.class) - class OrderServiceIntegrationTests { - - @Test - void deleteOrder(@Autowired OrderService orderService) { - // use orderService from the test's ApplicationContext - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(TestConfig::class) - class OrderServiceIntegrationTests { - - @Test - fun deleteOrder(@Autowired orderService: OrderService) { - // use orderService from the test's ApplicationContext - } - } ----- - -Due to the robustness of the `ParameterResolver` support in JUnit Jupiter, you can also -have multiple dependencies injected into a single method, not only from Spring but also -from JUnit Jupiter itself or other third-party extensions. - -The following example shows how to have both Spring and JUnit Jupiter inject dependencies -into the `placeOrderRepeatedly()` test method simultaneously. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(TestConfig.class) - class OrderServiceIntegrationTests { - - @RepeatedTest(10) - void placeOrderRepeatedly(RepetitionInfo repetitionInfo, - @Autowired OrderService orderService) { - - // use orderService from the test's ApplicationContext - // and repetitionInfo from JUnit Jupiter - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(TestConfig::class) - class OrderServiceIntegrationTests { - - @RepeatedTest(10) - fun placeOrderRepeatedly(repetitionInfo:RepetitionInfo, @Autowired orderService:OrderService) { - - // use orderService from the test's ApplicationContext - // and repetitionInfo from JUnit Jupiter - } - } ----- - -Note that the use of `@RepeatedTest` from JUnit Jupiter lets the test method gain access -to the `RepetitionInfo`. - -[[testcontext-junit-jupiter-nested-test-configuration]] -===== `@Nested` test class configuration - -The _Spring TestContext Framework_ has supported the use of test-related annotations on -`@Nested` test classes in JUnit Jupiter since Spring Framework 5.0; however, until Spring -Framework 5.3 class-level test configuration annotations were not _inherited_ from -enclosing classes like they are from superclasses. - -Spring Framework 5.3 introduces first-class support for inheriting test class -configuration from enclosing classes, and such configuration will be inherited by -default. To change from the default `INHERIT` mode to `OVERRIDE` mode, you may annotate -an individual `@Nested` test class with -`@NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)`. An explicit -`@NestedTestConfiguration` declaration will apply to the annotated test class as well as -any of its subclasses and nested classes. Thus, you may annotate a top-level test class -with `@NestedTestConfiguration`, and that will apply to all of its nested test classes -recursively. - -In order to allow development teams to change the default to `OVERRIDE` – for example, -for compatibility with Spring Framework 5.0 through 5.2 – the default mode can be changed -globally via a JVM system property or a `spring.properties` file in the root of the -classpath. See the <> note for details. - -Although the following "Hello World" example is very simplistic, it shows how to declare -common configuration on a top-level class that is inherited by its `@Nested` test -classes. In this particular example, only the `TestConfig` configuration class is -inherited. Each nested test class provides its own set of active profiles, resulting in a -distinct `ApplicationContext` for each nested test class (see -<> for details). Consult the list of -<> to see -which annotations can be inherited in `@Nested` test classes. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitConfig(TestConfig.class) - class GreetingServiceTests { - - @Nested - @ActiveProfiles("lang_en") - class EnglishGreetings { - - @Test - void hello(@Autowired GreetingService service) { - assertThat(service.greetWorld()).isEqualTo("Hello World"); - } - } - - @Nested - @ActiveProfiles("lang_de") - class GermanGreetings { - - @Test - void hello(@Autowired GreetingService service) { - assertThat(service.greetWorld()).isEqualTo("Hallo Welt"); - } - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitConfig(TestConfig::class) - class GreetingServiceTests { - - @Nested - @ActiveProfiles("lang_en") - inner class EnglishGreetings { - - @Test - fun hello(@Autowired service:GreetingService) { - assertThat(service.greetWorld()).isEqualTo("Hello World") - } - } - - @Nested - @ActiveProfiles("lang_de") - inner class GermanGreetings { - - @Test - fun hello(@Autowired service:GreetingService) { - assertThat(service.greetWorld()).isEqualTo("Hallo Welt") - } - } - } ----- - -[[testcontext-support-classes-testng]] -===== TestNG Support Classes - -The `org.springframework.test.context.testng` package provides the following support -classes for TestNG based test cases: - -* `AbstractTestNGSpringContextTests` -* `AbstractTransactionalTestNGSpringContextTests` - -`AbstractTestNGSpringContextTests` is an abstract base test class that integrates the -Spring TestContext Framework with explicit `ApplicationContext` testing support in a -TestNG environment. When you extend `AbstractTestNGSpringContextTests`, you can access a -`protected` `applicationContext` instance variable that you can use to perform explicit -bean lookups or to test the state of the context as a whole. - -`AbstractTransactionalTestNGSpringContextTests` is an abstract transactional extension of -`AbstractTestNGSpringContextTests` that adds some convenience functionality for JDBC -access. This class expects a `javax.sql.DataSource` bean and a -`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you -extend `AbstractTransactionalTestNGSpringContextTests`, you can access a `protected` -`jdbcTemplate` instance variable that you can use to run SQL statements to query the -database. You can use such queries to confirm database state both before and after -running database-related application code, and Spring ensures that such queries run in -the scope of the same transaction as the application code. When used in conjunction with -an ORM tool, be sure to avoid <>. -As mentioned in <>, -`AbstractTransactionalTestNGSpringContextTests` also provides convenience methods that -delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`. -Furthermore, `AbstractTransactionalTestNGSpringContextTests` provides an -`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`. - -TIP: These classes are a convenience for extension. If you do not want your test classes -to be tied to a Spring-specific class hierarchy, you can configure your own custom test -classes by using `@ContextConfiguration`, `@TestExecutionListeners`, and so on and by -manually instrumenting your test class with a `TestContextManager`. See the source code -of `AbstractTestNGSpringContextTests` for an example of how to instrument your test class. - - - -include::testing/testing-webtestclient.adoc[leveloffset=+2] - - - -[[spring-mvc-test-framework]] -=== MockMvc - -The Spring MVC Test framework, also known as MockMvc, provides support for testing Spring -MVC applications. It performs full Spring MVC request handling but via mock request and -response objects instead of a running server. - -MockMvc can be used on its own to perform requests and verify responses. It can also be -used through the <> where MockMvc is plugged in as the server to handle -requests with. The advantage of `WebTestClient` is the option to work with higher level -objects instead of raw data as well as the ability to switch to full, end-to-end HTTP -tests against a live server and use the same test API. - - -[[spring-mvc-test-server]] -==== Overview - -You can write plain unit tests for Spring MVC by instantiating a controller, injecting it -with dependencies, and calling its methods. However such tests do not verify request -mappings, data binding, message conversion, type conversion, validation, and nor -do they involve any of the supporting `@InitBinder`, `@ModelAttribute`, or -`@ExceptionHandler` methods. - -The Spring MVC Test framework, also known as `MockMvc`, aims to provide more complete -testing for Spring MVC controllers without a running server. It does that by invoking -the `DispatcherServlet` and passing -<> from the -`spring-test` module which replicates the full Spring MVC request handling without -a running server. - -MockMvc is a server side test framework that lets you verify most of the functionality -of a Spring MVC application using lightweight and targeted tests. You can use it on -its own to perform requests and to verify responses, or you can also use it through -the <> API with MockMvc plugged in as the server to handle requests -with. - - -[[spring-mvc-test-server-static-imports]] -===== Static Imports - -When using MockMvc directly to perform requests, you'll need static imports for: - -- `MockMvcBuilders.{asterisk}` -- `MockMvcRequestBuilders.{asterisk}` -- `MockMvcResultMatchers.{asterisk}` -- `MockMvcResultHandlers.{asterisk}` - -An easy way to remember that is search for `MockMvc*`. If using Eclipse be sure to also -add the above as "`favorite static members`" in the Eclipse preferences. - -When using MockMvc through the <> you do not need static imports. -The `WebTestClient` provides a fluent API without static imports. - - -[[spring-mvc-test-server-setup-options]] -===== Setup Choices - -MockMvc can be setup in one of two ways. One is to point directly to the controllers you -want to test and programmatically configure Spring MVC infrastructure. The second is to -point to Spring configuration with Spring MVC and controller infrastructure in it. - -To set up MockMvc for testing a specific controller, use the following: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - class MyWebTests { - - MockMvc mockMvc; - - @BeforeEach - void setup() { - this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build(); - } - - // ... - - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - class MyWebTests { - - lateinit var mockMvc : MockMvc - - @BeforeEach - fun setup() { - mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build() - } - - // ... - - } ----- - -Or you can also use this setup when testing through the -<> which delegates to the same builder -as shown above. - -To set up MockMvc through Spring configuration, use the following: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitWebConfig(locations = "my-servlet-context.xml") - class MyWebTests { - - MockMvc mockMvc; - - @BeforeEach - void setup(WebApplicationContext wac) { - this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); - } - - // ... - - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitWebConfig(locations = ["my-servlet-context.xml"]) - class MyWebTests { - - lateinit var mockMvc: MockMvc - - @BeforeEach - fun setup(wac: WebApplicationContext) { - mockMvc = MockMvcBuilders.webAppContextSetup(wac).build() - } - - // ... - - } ----- - -Or you can also use this setup when testing through the -<> which delegates to the same builder -as shown above. - - - -Which setup option should you use? - -The `webAppContextSetup` loads your actual Spring MVC configuration, resulting in a more -complete integration test. Since the TestContext framework caches the loaded Spring -configuration, it helps keep tests running fast, even as you introduce more tests in your -test suite. Furthermore, you can inject mock services into controllers through Spring -configuration to remain focused on testing the web layer. The following example declares -a mock service with Mockito: - -[source,xml,indent=0,subs="verbatim,quotes"] ----- - - - ----- - -You can then inject the mock service into the test to set up and verify your -expectations, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @SpringJUnitWebConfig(locations = "test-servlet-context.xml") - class AccountTests { - - @Autowired - AccountService accountService; - - MockMvc mockMvc; - - @BeforeEach - void setup(WebApplicationContext wac) { - this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); - } - - // ... - - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @SpringJUnitWebConfig(locations = ["test-servlet-context.xml"]) - class AccountTests { - - @Autowired - lateinit var accountService: AccountService - - lateinit mockMvc: MockMvc - - @BeforeEach - fun setup(wac: WebApplicationContext) { - mockMvc = MockMvcBuilders.webAppContextSetup(wac).build() - } - - // ... - - } ----- - -The `standaloneSetup`, on the other hand, is a little closer to a unit test. It tests one -controller at a time. You can manually inject the controller with mock dependencies, and -it does not involve loading Spring configuration. Such tests are more focused on style -and make it easier to see which controller is being tested, whether any specific Spring -MVC configuration is required to work, and so on. The `standaloneSetup` is also a very -convenient way to write ad-hoc tests to verify specific behavior or to debug an issue. - -As with most "`integration versus unit testing`" debates, there is no right or wrong -answer. However, using the `standaloneSetup` does imply the need for additional -`webAppContextSetup` tests in order to verify your Spring MVC configuration. -Alternatively, you can write all your tests with `webAppContextSetup`, in order to always -test against your actual Spring MVC configuration. - -[[spring-mvc-test-server-setup-steps]] -===== Setup Features - -No matter which MockMvc builder you use, all `MockMvcBuilder` implementations provide -some common and very useful features. For example, you can declare an `Accept` header for -all requests and expect a status of 200 as well as a `Content-Type` header in all -responses, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // static import of MockMvcBuilders.standaloneSetup - - MockMvc mockMvc = standaloneSetup(new MusicController()) - .defaultRequest(get("/").accept(MediaType.APPLICATION_JSON)) - .alwaysExpect(status().isOk()) - .alwaysExpect(content().contentType("application/json;charset=UTF-8")) - .build(); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ----- - -In addition, third-party frameworks (and applications) can pre-package setup -instructions, such as those in a `MockMvcConfigurer`. The Spring Framework has one such -built-in implementation that helps to save and re-use the HTTP session across requests. -You can use it as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // static import of SharedHttpSessionConfigurer.sharedHttpSession - - MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController()) - .apply(sharedHttpSession()) - .build(); - - // Use mockMvc to perform requests... ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ----- - -See the javadoc for -{api-spring-framework}/test/web/servlet/setup/ConfigurableMockMvcBuilder.html[`ConfigurableMockMvcBuilder`] -for a list of all MockMvc builder features or use the IDE to explore the available options. - -[[spring-mvc-test-server-performing-requests]] -===== Performing Requests - -This section shows how to use MockMvc on its own to perform requests and verify responses. -If using MockMvc through the `WebTestClient` please see the corresponding section on -<> instead. - -To perform requests that use any HTTP method, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // static import of MockMvcRequestBuilders.* - - mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON)); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - import org.springframework.test.web.servlet.post - - mockMvc.post("/hotels/{id}", 42) { - accept = MediaType.APPLICATION_JSON - } ----- - -You can also perform file upload requests that internally use -`MockMultipartHttpServletRequest` so that there is no actual parsing of a multipart -request. Rather, you have to set it up to be similar to the following example: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8"))); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - import org.springframework.test.web.servlet.multipart - - mockMvc.multipart("/doc") { - file("a1", "ABC".toByteArray(charset("UTF8"))) - } ----- - -You can specify query parameters in URI template style, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - mockMvc.perform(get("/hotels?thing={thing}", "somewhere")); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - mockMvc.get("/hotels?thing={thing}", "somewhere") ----- - -You can also add Servlet request parameters that represent either query or form -parameters, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - mockMvc.perform(get("/hotels").param("thing", "somewhere")); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - import org.springframework.test.web.servlet.get - - mockMvc.get("/hotels") { - param("thing", "somewhere") - } ----- - -If application code relies on Servlet request parameters and does not check the query -string explicitly (as is most often the case), it does not matter which option you use. -Keep in mind, however, that query parameters provided with the URI template are decoded -while request parameters provided through the `param(...)` method are expected to already -be decoded. - -In most cases, it is preferable to leave the context path and the Servlet path out of the -request URI. If you must test with the full request URI, be sure to set the `contextPath` -and `servletPath` accordingly so that request mappings work, as the following example -shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main")) ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - import org.springframework.test.web.servlet.get - - mockMvc.get("/app/main/hotels/{id}") { - contextPath = "/app" - servletPath = "/main" - } ----- - -In the preceding example, it would be cumbersome to set the `contextPath` and -`servletPath` with every performed request. Instead, you can set up default request -properties, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - class MyWebTests { - - MockMvc mockMvc; - - @BeforeEach - void setup() { - mockMvc = standaloneSetup(new AccountController()) - .defaultRequest(get("/") - .contextPath("/app").servletPath("/main") - .accept(MediaType.APPLICATION_JSON)).build(); - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ----- - -The preceding properties affect every request performed through the `MockMvc` instance. -If the same property is also specified on a given request, it overrides the default -value. That is why the HTTP method and URI in the default request do not matter, since -they must be specified on every request. - -[[spring-mvc-test-server-defining-expectations]] -===== Defining Expectations - -You can define expectations by appending one or more `andExpect(..)` calls after -performing a request, as the following example shows. As soon as one expectation fails, -no other expectations will be asserted. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.* - - mockMvc.perform(get("/accounts/1")).andExpect(status().isOk()); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - import org.springframework.test.web.servlet.get - - mockMvc.get("/accounts/1").andExpect { - status { isOk() } - } ----- - -You can define multiple expectations by appending `andExpectAll(..)` after performing a -request, as the following example shows. In contrast to `andExpect(..)`, -`andExpectAll(..)` guarantees that all supplied expectations will be asserted and that -all failures will be tracked and reported. - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.* - - mockMvc.perform(get("/accounts/1")).andExpectAll( - status().isOk(), - content().contentType("application/json;charset=UTF-8")); ----- - -`MockMvcResultMatchers.*` provides a number of expectations, some of which are further -nested with more detailed expectations. - -Expectations fall in two general categories. The first category of assertions verifies -properties of the response (for example, the response status, headers, and content). -These are the most important results to assert. - -The second category of assertions goes beyond the response. These assertions let you -inspect Spring MVC specific aspects, such as which controller method processed the -request, whether an exception was raised and handled, what the content of the model is, -what view was selected, what flash attributes were added, and so on. They also let you -inspect Servlet specific aspects, such as request and session attributes. - -The following test asserts that binding or validation failed: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - mockMvc.perform(post("/persons")) - .andExpect(status().isOk()) - .andExpect(model().attributeHasErrors("person")); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - import org.springframework.test.web.servlet.post - - mockMvc.post("/persons").andExpect { - status { isOk() } - model { - attributeHasErrors("person") - } - } ----- - -Many times, when writing tests, it is useful to dump the results of the performed -request. You can do so as follows, where `print()` is a static import from -`MockMvcResultHandlers`: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - mockMvc.perform(post("/persons")) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(model().attributeHasErrors("person")); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - import org.springframework.test.web.servlet.post - - mockMvc.post("/persons").andDo { - print() - }.andExpect { - status { isOk() } - model { - attributeHasErrors("person") - } - } ----- - -As long as request processing does not cause an unhandled exception, the `print()` method -prints all the available result data to `System.out`. There is also a `log()` method and -two additional variants of the `print()` method, one that accepts an `OutputStream` and -one that accepts a `Writer`. For example, invoking `print(System.err)` prints the result -data to `System.err`, while invoking `print(myWriter)` prints the result data to a custom -writer. If you want to have the result data logged instead of printed, you can invoke the -`log()` method, which logs the result data as a single `DEBUG` message under the -`org.springframework.test.web.servlet.result` logging category. - -In some cases, you may want to get direct access to the result and verify something that -cannot be verified otherwise. This can be achieved by appending `.andReturn()` after all -other expectations, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn(); - // ... ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - var mvcResult = mockMvc.post("/persons").andExpect { status { isOk() } }.andReturn() - // ... ----- - -If all tests repeat the same expectations, you can set up common expectations once when -building the `MockMvc` instance, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - standaloneSetup(new SimpleController()) - .alwaysExpect(status().isOk()) - .alwaysExpect(content().contentType("application/json;charset=UTF-8")) - .build() ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ----- - -Note that common expectations are always applied and cannot be overridden without -creating a separate `MockMvc` instance. - -When a JSON response content contains hypermedia links created with -https://github.com/spring-projects/spring-hateoas[Spring HATEOAS], you can verify the -resulting links by using JsonPath expressions, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people")); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - mockMvc.get("/people") { - accept(MediaType.APPLICATION_JSON) - }.andExpect { - jsonPath("$.links[?(@.rel == 'self')].href") { - value("http://localhost:8080/people") - } - } ----- - -When XML response content contains hypermedia links created with -https://github.com/spring-projects/spring-hateoas[Spring HATEOAS], you can verify the -resulting links by using XPath expressions: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - Map ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom"); - mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML)) - .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people")); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - val ns = mapOf("ns" to "http://www.w3.org/2005/Atom") - mockMvc.get("/handle") { - accept(MediaType.APPLICATION_XML) - }.andExpect { - xpath("/person/ns:link[@rel='self']/@href", ns) { - string("http://localhost:8080/people") - } - } ----- - -[[spring-mvc-test-async-requests]] -===== Async Requests - -This section shows how to use MockMvc on its own to test asynchronous request handling. -If using MockMvc through the <>, there is nothing special to do to make -asynchronous requests work as the `WebTestClient` automatically does what is described -in this section. - -Servlet asynchronous requests, <>, -work by exiting the Servlet container thread and allowing the application to compute -the response asynchronously, after which an async dispatch is made to complete -processing on a Servlet container thread. - -In Spring MVC Test, async requests can be tested by asserting the produced async value -first, then manually performing the async dispatch, and finally verifying the response. -Below is an example test for controller methods that return `DeferredResult`, `Callable`, -or reactive type such as Reactor `Mono`: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - // static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.* - - @Test - void test() throws Exception { - MvcResult mvcResult = this.mockMvc.perform(get("/path")) - .andExpect(status().isOk()) <1> - .andExpect(request().asyncStarted()) <2> - .andExpect(request().asyncResult("body")) <3> - .andReturn(); - - this.mockMvc.perform(asyncDispatch(mvcResult)) <4> - .andExpect(status().isOk()) <5> - .andExpect(content().string("body")); - } ----- -<1> Check response status is still unchanged -<2> Async processing must have started -<3> Wait and assert the async result -<4> Manually perform an ASYNC dispatch (as there is no running container) -<5> Verify the final response - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Test - fun test() { - var mvcResult = mockMvc.get("/path").andExpect { - status { isOk() } // <1> - request { asyncStarted() } // <2> - // TODO Remove unused generic parameter - request { asyncResult("body") } // <3> - }.andReturn() - - - mockMvc.perform(asyncDispatch(mvcResult)) // <4> - .andExpect { - status { isOk() } // <5> - content().string("body") - } - } ----- -<1> Check response status is still unchanged -<2> Async processing must have started -<3> Wait and assert the async result -<4> Manually perform an ASYNC dispatch (as there is no running container) -<5> Verify the final response - - -[[spring-mvc-test-vs-streaming-response]] -===== Streaming Responses - -The best way to test streaming responses such as Server-Sent Events is through the -<> which can be used as a test client to connect to a `MockMvc` instance -to perform tests on Spring MVC controllers without a running server. For example: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build(); - - FluxExchangeResult exchangeResult = client.get() - .uri("/persons") - .exchange() - .expectStatus().isOk() - .expectHeader().contentType("text/event-stream") - .returnResult(Person.class); - - // Use StepVerifier from Project Reactor to test the streaming response - - StepVerifier.create(exchangeResult.getResponseBody()) - .expectNext(new Person("N0"), new Person("N1"), new Person("N2")) - .expectNextCount(4) - .consumeNextWith(person -> assertThat(person.getName()).endsWith("7")) - .thenCancel() - .verify(); ----- - -`WebTestClient` can also connect to a live server and perform full end-to-end integration -tests. This is also supported in Spring Boot where you can -{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server]. - - -[[spring-mvc-test-server-filters]] -===== Filter Registrations - -When setting up a `MockMvc` instance, you can register one or more Servlet `Filter` -instances, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build(); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ----- - -Registered filters are invoked through the `MockFilterChain` from `spring-test`, and the -last filter delegates to the `DispatcherServlet`. - - -[[spring-mvc-test-vs-end-to-end-integration-tests]] -===== MockMvc vs End-to-End Tests - -MockMVc is built on Servlet API mock implementations from the -`spring-test` module and does not rely on a running container. Therefore, there are -some differences when compared to full end-to-end integration tests with an actual -client and a live server running. - -The easiest way to think about this is by starting with a blank `MockHttpServletRequest`. -Whatever you add to it is what the request becomes. Things that may catch you by surprise -are that there is no context path by default; no `jsessionid` cookie; no forwarding, -error, or async dispatches; and, therefore, no actual JSP rendering. Instead, -"`forwarded`" and "`redirected`" URLs are saved in the `MockHttpServletResponse` and can -be asserted with expectations. - -This means that, if you use JSPs, you can verify the JSP page to which the request was -forwarded, but no HTML is rendered. In other words, the JSP is not invoked. Note, -however, that all other rendering technologies that do not rely on forwarding, such as -Thymeleaf and Freemarker, render HTML to the response body as expected. The same is true -for rendering JSON, XML, and other formats through `@ResponseBody` methods. - -Alternatively, you may consider the full end-to-end integration testing support from -Spring Boot with `@SpringBootTest`. See the -{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing[Spring Boot Reference Guide]. - -There are pros and cons for each approach. The options provided in Spring MVC Test are -different stops on the scale from classic unit testing to full integration testing. To be -certain, none of the options in Spring MVC Test fall under the category of classic unit -testing, but they are a little closer to it. For example, you can isolate the web layer -by injecting mocked services into controllers, in which case you are testing the web -layer only through the `DispatcherServlet` but with actual Spring configuration, as you -might test the data access layer in isolation from the layers above it. Also, you can use -the stand-alone setup, focusing on one controller at a time and manually providing the -configuration required to make it work. - -Another important distinction when using Spring MVC Test is that, conceptually, such -tests are the server-side, so you can check what handler was used, if an exception was -handled with a HandlerExceptionResolver, what the content of the model is, what binding -errors there were, and other details. That means that it is easier to write expectations, -since the server is not an opaque box, as it is when testing it through an actual HTTP -client. This is generally an advantage of classic unit testing: It is easier to write, -reason about, and debug but does not replace the need for full integration tests. At the -same time, it is important not to lose sight of the fact that the response is the most -important thing to check. In short, there is room here for multiple styles and strategies -of testing even within the same project. - -[[spring-mvc-test-server-resources]] -===== Further Examples - -The framework's own tests include -{spring-framework-main-code}/spring-test/src/test/java/org/springframework/test/web/servlet/samples[ -many sample tests] intended to show how to use MockMvc on its own or through the -{spring-framework-main-code}/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client[ -WebTestClient]. Browse these examples for further ideas. - - -[[spring-mvc-test-server-htmlunit]] -==== HtmlUnit Integration - -Spring provides integration between <> and -https://htmlunit.sourceforge.io/[HtmlUnit]. This simplifies performing end-to-end testing -when using HTML-based views. This integration lets you: - -* Easily test HTML pages by using tools such as - https://htmlunit.sourceforge.io/[HtmlUnit], - https://www.seleniumhq.org[WebDriver], and - https://www.gebish.org/manual/current/#spock-junit-testng[Geb] without the need to - deploy to a Servlet container. -* Test JavaScript within pages. -* Optionally, test using mock services to speed up testing. -* Share logic between in-container end-to-end tests and out-of-container integration tests. - -NOTE: MockMvc works with templating technologies that do not rely on a Servlet Container -(for example, Thymeleaf, FreeMarker, and others), but it does not work with JSPs, since -they rely on the Servlet container. - -[[spring-mvc-test-server-htmlunit-why]] -===== Why HtmlUnit Integration? - -The most obvious question that comes to mind is "`Why do I need this?`" The answer is -best found by exploring a very basic sample application. Assume you have a Spring MVC web -application that supports CRUD operations on a `Message` object. The application also -supports paging through all messages. How would you go about testing it? - -With Spring MVC Test, we can easily test if we are able to create a `Message`, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - MockHttpServletRequestBuilder createMessage = post("/messages/") - .param("summary", "Spring Rocks") - .param("text", "In case you didn't know, Spring Rocks!"); - - mockMvc.perform(createMessage) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/messages/123")); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @Test - fun test() { - mockMvc.post("/messages/") { - param("summary", "Spring Rocks") - param("text", "In case you didn't know, Spring Rocks!") - }.andExpect { - status().is3xxRedirection() - redirectedUrl("/messages/123") - } - } ----- - -What if we want to test the form view that lets us create the message? For example, -assume our form looks like the following snippet: - -[source,xml,indent=0] ----- -
- - - - - - - - -
- -
-
----- - -How do we ensure that our form produce the correct request to create a new message? A -naive attempt might resemble the following: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - mockMvc.perform(get("/messages/form")) - .andExpect(xpath("//input[@name='summary']").exists()) - .andExpect(xpath("//textarea[@name='text']").exists()); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - mockMvc.get("/messages/form").andExpect { - xpath("//input[@name='summary']") { exists() } - xpath("//textarea[@name='text']") { exists() } - } ----- - -This test has some obvious drawbacks. If we update our controller to use the parameter -`message` instead of `text`, our form test continues to pass, even though the HTML form -is out of synch with the controller. To resolve this we can combine our two tests, as -follows: - -[[spring-mvc-test-server-htmlunit-mock-mvc-test]] -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - String summaryParamName = "summary"; - String textParamName = "text"; - mockMvc.perform(get("/messages/form")) - .andExpect(xpath("//input[@name='" + summaryParamName + "']").exists()) - .andExpect(xpath("//textarea[@name='" + textParamName + "']").exists()); - - MockHttpServletRequestBuilder createMessage = post("/messages/") - .param(summaryParamName, "Spring Rocks") - .param(textParamName, "In case you didn't know, Spring Rocks!"); - - mockMvc.perform(createMessage) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/messages/123")); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - val summaryParamName = "summary"; - val textParamName = "text"; - mockMvc.get("/messages/form").andExpect { - xpath("//input[@name='$summaryParamName']") { exists() } - xpath("//textarea[@name='$textParamName']") { exists() } - } - mockMvc.post("/messages/") { - param(summaryParamName, "Spring Rocks") - param(textParamName, "In case you didn't know, Spring Rocks!") - }.andExpect { - status().is3xxRedirection() - redirectedUrl("/messages/123") - } ----- - -This would reduce the risk of our test incorrectly passing, but there are still some -problems: - -* What if we have multiple forms on our page? Admittedly, we could update our XPath - expressions, but they get more complicated as we take more factors into account: Are - the fields the correct type? Are the fields enabled? And so on. -* Another issue is that we are doing double the work we would expect. We must first - verify the view, and then we submit the view with the same parameters we just verified. - Ideally, this could be done all at once. -* Finally, we still cannot account for some things. For example, what if the form has - JavaScript validation that we wish to test as well? - -The overall problem is that testing a web page does not involve a single interaction. -Instead, it is a combination of how the user interacts with a web page and how that web -page interacts with other resources. For example, the result of a form view is used as -the input to a user for creating a message. In addition, our form view can potentially -use additional resources that impact the behavior of the page, such as JavaScript -validation. - -[[spring-mvc-test-server-htmlunit-why-integration]] -====== Integration Testing to the Rescue? - -To resolve the issues mentioned earlier, we could perform end-to-end integration testing, -but this has some drawbacks. Consider testing the view that lets us page through the -messages. We might need the following tests: - -* Does our page display a notification to the user to indicate that no results are - available when the messages are empty? -* Does our page properly display a single message? -* Does our page properly support paging? - -To set up these tests, we need to ensure our database contains the proper messages. This -leads to a number of additional challenges: - -* Ensuring the proper messages are in the database can be tedious. (Consider foreign key - constraints.) -* Testing can become slow, since each test would need to ensure that the database is in - the correct state. -* Since our database needs to be in a specific state, we cannot run tests in parallel. -* Performing assertions on such items as auto-generated ids, timestamps, and others can - be difficult. - -These challenges do not mean that we should abandon end-to-end integration testing -altogether. Instead, we can reduce the number of end-to-end integration tests by -refactoring our detailed tests to use mock services that run much faster, more reliably, -and without side effects. We can then implement a small number of true end-to-end -integration tests that validate simple workflows to ensure that everything works together -properly. - -[[spring-mvc-test-server-htmlunit-why-mockmvc]] -====== Enter HtmlUnit Integration - -So how can we achieve a balance between testing the interactions of our pages and still -retain good performance within our test suite? The answer is: "`By integrating MockMvc -with HtmlUnit.`" - -[[spring-mvc-test-server-htmlunit-options]] -====== HtmlUnit Integration Options - -You have a number of options when you want to integrate MockMvc with HtmlUnit: - -* <>: Use this option if you - want to use the raw HtmlUnit libraries. -* <>: Use this option to - ease development and reuse code between integration and end-to-end testing. -* <>: Use this option if you want to - use Groovy for testing, ease development, and reuse code between integration and - end-to-end testing. - -[[spring-mvc-test-server-htmlunit-mah]] -===== MockMvc and HtmlUnit - -This section describes how to integrate MockMvc and HtmlUnit. Use this option if you want -to use the raw HtmlUnit libraries. - -[[spring-mvc-test-server-htmlunit-mah-setup]] -====== MockMvc and HtmlUnit Setup - -First, make sure that you have included a test dependency on -`net.sourceforge.htmlunit:htmlunit`. In order to use HtmlUnit with Apache HttpComponents -4.5+, you need to use HtmlUnit 2.18 or higher. - -We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the -`MockMvcWebClientBuilder`, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - WebClient webClient; - - @BeforeEach - void setup(WebApplicationContext context) { - webClient = MockMvcWebClientBuilder - .webAppContextSetup(context) - .build(); - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - lateinit var webClient: WebClient - - @BeforeEach - fun setup(context: WebApplicationContext) { - webClient = MockMvcWebClientBuilder - .webAppContextSetup(context) - .build() - } ----- - -NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage, -see <>. - -This ensures that any URL that references `localhost` as the server is directed to our -`MockMvc` instance without the need for a real HTTP connection. Any other URL is -requested by using a network connection, as normal. This lets us easily test the use of -CDNs. - -[[spring-mvc-test-server-htmlunit-mah-usage]] -====== MockMvc and HtmlUnit Usage - -Now we can use HtmlUnit as we normally would but without the need to deploy our -application to a Servlet container. For example, we can request the view to create a -message with the following: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form"); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - val createMsgFormPage = webClient.getPage("http://localhost/messages/form") ----- - -NOTE: The default context path is `""`. Alternatively, we can specify the context path, -as described in <>. - -Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it -to create a message, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm"); - HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary"); - summaryInput.setValueAttribute("Spring Rocks"); - HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text"); - textInput.setText("In case you didn't know, Spring Rocks!"); - HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit"); - HtmlPage newMessagePage = submit.click(); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - val form = createMsgFormPage.getHtmlElementById("messageForm") - val summaryInput = createMsgFormPage.getHtmlElementById("summary") - summaryInput.setValueAttribute("Spring Rocks") - val textInput = createMsgFormPage.getHtmlElementById("text") - textInput.setText("In case you didn't know, Spring Rocks!") - val submit = form.getOneHtmlElementByAttribute("input", "type", "submit") - val newMessagePage = submit.click() ----- - -Finally, we can verify that a new message was created successfully. The following -assertions use the https://assertj.github.io/doc/[AssertJ] library: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123"); - String id = newMessagePage.getHtmlElementById("id").getTextContent(); - assertThat(id).isEqualTo("123"); - String summary = newMessagePage.getHtmlElementById("summary").getTextContent(); - assertThat(summary).isEqualTo("Spring Rocks"); - String text = newMessagePage.getHtmlElementById("text").getTextContent(); - assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!"); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123") - val id = newMessagePage.getHtmlElementById("id").getTextContent() - assertThat(id).isEqualTo("123") - val summary = newMessagePage.getHtmlElementById("summary").getTextContent() - assertThat(summary).isEqualTo("Spring Rocks") - val text = newMessagePage.getHtmlElementById("text").getTextContent() - assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!") ----- - -The preceding code improves on our -<> in a number of ways. -First, we no longer have to explicitly verify our form and then create a request that -looks like the form. Instead, we request the form, fill it out, and submit it, thereby -significantly reducing the overhead. - -Another important factor is that https://htmlunit.sourceforge.io/javascript.html[HtmlUnit -uses the Mozilla Rhino engine] to evaluate JavaScript. This means that we can also test -the behavior of JavaScript within our pages. - -See the https://htmlunit.sourceforge.io/gettingStarted.html[HtmlUnit documentation] for -additional information about using HtmlUnit. - -[[spring-mvc-test-server-htmlunit-mah-advanced-builder]] -====== Advanced `MockMvcWebClientBuilder` - -In the examples so far, we have used `MockMvcWebClientBuilder` in the simplest way -possible, by building a `WebClient` based on the `WebApplicationContext` loaded for us by -the Spring TestContext Framework. This approach is repeated in the following example: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - WebClient webClient; - - @BeforeEach - void setup(WebApplicationContext context) { - webClient = MockMvcWebClientBuilder - .webAppContextSetup(context) - .build(); - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - lateinit var webClient: WebClient - - @BeforeEach - fun setup(context: WebApplicationContext) { - webClient = MockMvcWebClientBuilder - .webAppContextSetup(context) - .build() - } ----- - -We can also specify additional configuration options, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - WebClient webClient; - - @BeforeEach - void setup() { - webClient = MockMvcWebClientBuilder - // demonstrates applying a MockMvcConfigurer (Spring Security) - .webAppContextSetup(context, springSecurity()) - // for illustration only - defaults to "" - .contextPath("") - // By default MockMvc is used for localhost only; - // the following will use MockMvc for example.com and example.org as well - .useMockMvcForHosts("example.com","example.org") - .build(); - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - lateinit var webClient: WebClient - - @BeforeEach - fun setup() { - webClient = MockMvcWebClientBuilder - // demonstrates applying a MockMvcConfigurer (Spring Security) - .webAppContextSetup(context, springSecurity()) - // for illustration only - defaults to "" - .contextPath("") - // By default MockMvc is used for localhost only; - // the following will use MockMvc for example.com and example.org as well - .useMockMvcForHosts("example.com","example.org") - .build() - } ----- - -As an alternative, we can perform the exact same setup by configuring the `MockMvc` -instance separately and supplying it to the `MockMvcWebClientBuilder`, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - MockMvc mockMvc = MockMvcBuilders - .webAppContextSetup(context) - .apply(springSecurity()) - .build(); - - webClient = MockMvcWebClientBuilder - .mockMvcSetup(mockMvc) - // for illustration only - defaults to "" - .contextPath("") - // By default MockMvc is used for localhost only; - // the following will use MockMvc for example.com and example.org as well - .useMockMvcForHosts("example.com","example.org") - .build(); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ----- - -This is more verbose, but, by building the `WebClient` with a `MockMvc` instance, we have -the full power of MockMvc at our fingertips. - -TIP: For additional information on creating a `MockMvc` instance, see -<>. - -[[spring-mvc-test-server-htmlunit-webdriver]] -===== MockMvc and WebDriver - -In the previous sections, we have seen how to use MockMvc in conjunction with the raw -HtmlUnit APIs. In this section, we use additional abstractions within the Selenium -https://docs.seleniumhq.org/projects/webdriver/[WebDriver] to make things even easier. - -[[spring-mvc-test-server-htmlunit-webdriver-why]] -====== Why WebDriver and MockMvc? - -We can already use HtmlUnit and MockMvc, so why would we want to use WebDriver? The -Selenium WebDriver provides a very elegant API that lets us easily organize our code. To -better show how it works, we explore an example in this section. - -NOTE: Despite being a part of https://docs.seleniumhq.org/[Selenium], WebDriver does not -require a Selenium Server to run your tests. - -Suppose we need to ensure that a message is created properly. The tests involve finding -the HTML form input elements, filling them out, and making various assertions. - -This approach results in numerous separate tests because we want to test error conditions -as well. For example, we want to ensure that we get an error if we fill out only part of -the form. If we fill out the entire form, the newly created message should be displayed -afterwards. - -If one of the fields were named "`summary`", we might have something that resembles the -following repeated in multiple places within our tests: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary"); - summaryInput.setValueAttribute(summary); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - val summaryInput = currentPage.getHtmlElementById("summary") - summaryInput.setValueAttribute(summary) ----- - -So what happens if we change the `id` to `smmry`? Doing so would force us to update all -of our tests to incorporate this change. This violates the DRY principle, so we should -ideally extract this code into its own method, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) { - setSummary(currentPage, summary); - // ... - } - - public void setSummary(HtmlPage currentPage, String summary) { - HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary"); - summaryInput.setValueAttribute(summary); - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{ - setSummary(currentPage, summary); - // ... - } - - fun setSummary(currentPage:HtmlPage , summary: String) { - val summaryInput = currentPage.getHtmlElementById("summary") - summaryInput.setValueAttribute(summary) - } ----- - -Doing so ensures that we do not have to update all of our tests if we change the UI. - -We might even take this a step further and place this logic within an `Object` that -represents the `HtmlPage` we are currently on, as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - public class CreateMessagePage { - - final HtmlPage currentPage; - - final HtmlTextInput summaryInput; - - final HtmlSubmitInput submit; - - public CreateMessagePage(HtmlPage currentPage) { - this.currentPage = currentPage; - this.summaryInput = currentPage.getHtmlElementById("summary"); - this.submit = currentPage.getHtmlElementById("submit"); - } - - public T createMessage(String summary, String text) throws Exception { - setSummary(summary); - - HtmlPage result = submit.click(); - boolean error = CreateMessagePage.at(result); - - return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result)); - } - - public void setSummary(String summary) throws Exception { - summaryInput.setValueAttribute(summary); - } - - public static boolean at(HtmlPage page) { - return "Create Message".equals(page.getTitleText()); - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - class CreateMessagePage(private val currentPage: HtmlPage) { - - val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary") - - val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit") - - fun createMessage(summary: String, text: String): T { - setSummary(summary) - - val result = submit.click() - val error = at(result) - - return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T - } - - fun setSummary(summary: String) { - summaryInput.setValueAttribute(summary) - } - - fun at(page: HtmlPage): Boolean { - return "Create Message" == page.getTitleText() - } - } -} ----- - -Formerly, this pattern was known as the -https://github.com/SeleniumHQ/selenium/wiki/PageObjects[Page Object Pattern]. While we -can certainly do this with HtmlUnit, WebDriver provides some tools that we explore in the -following sections to make this pattern much easier to implement. - -[[spring-mvc-test-server-htmlunit-webdriver-setup]] -====== MockMvc and WebDriver Setup - -To use Selenium WebDriver with the Spring MVC Test framework, make sure that your project -includes a test dependency on `org.seleniumhq.selenium:selenium-htmlunit-driver`. - -We can easily create a Selenium WebDriver that integrates with MockMvc by using the -`MockMvcHtmlUnitDriverBuilder` as the following example shows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - WebDriver driver; - - @BeforeEach - void setup(WebApplicationContext context) { - driver = MockMvcHtmlUnitDriverBuilder - .webAppContextSetup(context) - .build(); - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - lateinit var driver: WebDriver - - @BeforeEach - fun setup(context: WebApplicationContext) { - driver = MockMvcHtmlUnitDriverBuilder - .webAppContextSetup(context) - .build() - } ----- - -NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced -usage, see <>. - -The preceding example ensures that any URL that references `localhost` as the server is -directed to our `MockMvc` instance without the need for a real HTTP connection. Any other -URL is requested by using a network connection, as normal. This lets us easily test the -use of CDNs. - -[[spring-mvc-test-server-htmlunit-webdriver-usage]] -====== MockMvc and WebDriver Usage - -Now we can use WebDriver as we normally would but without the need to deploy our -application to a Servlet container. For example, we can request the view to create a -message with the following: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - CreateMessagePage page = CreateMessagePage.to(driver); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - val page = CreateMessagePage.to(driver) ----- - -We can then fill out the form and submit it to create a message, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - ViewMessagePage viewMessagePage = - page.createMessage(ViewMessagePage.class, expectedSummary, expectedText); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - val viewMessagePage = - page.createMessage(ViewMessagePage::class, expectedSummary, expectedText) ----- - -This improves on the design of our <> -by leveraging the Page Object Pattern. As we mentioned in -<>, we can use the Page Object Pattern -with HtmlUnit, but it is much easier with WebDriver. Consider the following -`CreateMessagePage` implementation: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - public class CreateMessagePage - extends AbstractPage { // <1> - - // <2> - private WebElement summary; - private WebElement text; - - // <3> - @FindBy(css = "input[type=submit]") - private WebElement submit; - - public CreateMessagePage(WebDriver driver) { - super(driver); - } - - public T createMessage(Class resultPage, String summary, String details) { - this.summary.sendKeys(summary); - this.text.sendKeys(details); - this.submit.click(); - return PageFactory.initElements(driver, resultPage); - } - - public static CreateMessagePage to(WebDriver driver) { - driver.get("http://localhost:9990/mail/messages/form"); - return PageFactory.initElements(driver, CreateMessagePage.class); - } - } ----- -<1> `CreateMessagePage` extends the `AbstractPage`. We do not go over the details of -`AbstractPage`, but, in summary, it contains common functionality for all of our pages. -For example, if our application has a navigational bar, global error messages, and other -features, we can place this logic in a shared location. -<2> We have a member variable for each of the parts of the HTML page in which we are -interested. These are of type `WebElement`. WebDriver's -https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a -lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving -each `WebElement`. The -https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class)`] -method automatically resolves each `WebElement` by using the field name and looking it up -by the `id` or `name` of the element within the HTML page. -<3> We can use the -https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation] -to override the default lookup behavior. Our example shows how to use the `@FindBy` -annotation to look up our submit button with a `css` selector (*input[type=submit]*). - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { // <1> - - // <2> - private lateinit var summary: WebElement - private lateinit var text: WebElement - - // <3> - @FindBy(css = "input[type=submit]") - private lateinit var submit: WebElement - - fun createMessage(resultPage: Class, summary: String, details: String): T { - this.summary.sendKeys(summary) - text.sendKeys(details) - submit.click() - return PageFactory.initElements(driver, resultPage) - } - companion object { - fun to(driver: WebDriver): CreateMessagePage { - driver.get("http://localhost:9990/mail/messages/form") - return PageFactory.initElements(driver, CreateMessagePage::class.java) - } - } - } ----- -<1> `CreateMessagePage` extends the `AbstractPage`. We do not go over the details of -`AbstractPage`, but, in summary, it contains common functionality for all of our pages. -For example, if our application has a navigational bar, global error messages, and other -features, we can place this logic in a shared location. -<2> We have a member variable for each of the parts of the HTML page in which we are -interested. These are of type `WebElement`. WebDriver's -https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a -lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving -each `WebElement`. The -https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class)`] -method automatically resolves each `WebElement` by using the field name and looking it up -by the `id` or `name` of the element within the HTML page. -<3> We can use the -https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation] -to override the default lookup behavior. Our example shows how to use the `@FindBy` -annotation to look up our submit button with a `css` selector (*input[type=submit]*). - - -Finally, we can verify that a new message was created successfully. The following -assertions use the https://assertj.github.io/doc/[AssertJ] assertion library: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage); - assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message"); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - assertThat(viewMessagePage.message).isEqualTo(expectedMessage) - assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message") ----- - -We can see that our `ViewMessagePage` lets us interact with our custom domain model. For -example, it exposes a method that returns a `Message` object: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - public Message getMessage() throws ParseException { - Message message = new Message(); - message.setId(getId()); - message.setCreated(getCreated()); - message.setSummary(getSummary()); - message.setText(getText()); - return message; - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - fun getMessage() = Message(getId(), getCreated(), getSummary(), getText()) ----- - -We can then use the rich domain objects in our assertions. - -Lastly, we must not forget to close the `WebDriver` instance when the test is complete, -as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - @AfterEach - void destroy() { - if (driver != null) { - driver.close(); - } - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - @AfterEach - fun destroy() { - if (driver != null) { - driver.close() - } - } ----- - -For additional information on using WebDriver, see the Selenium -https://github.com/SeleniumHQ/selenium/wiki/Getting-Started[WebDriver documentation]. - -[[spring-mvc-test-server-htmlunit-webdriver-advanced-builder]] -====== Advanced `MockMvcHtmlUnitDriverBuilder` - -In the examples so far, we have used `MockMvcHtmlUnitDriverBuilder` in the simplest way -possible, by building a `WebDriver` based on the `WebApplicationContext` loaded for us by -the Spring TestContext Framework. This approach is repeated here, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - WebDriver driver; - - @BeforeEach - void setup(WebApplicationContext context) { - driver = MockMvcHtmlUnitDriverBuilder - .webAppContextSetup(context) - .build(); - } ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - lateinit var driver: WebDriver - - @BeforeEach - fun setup(context: WebApplicationContext) { - driver = MockMvcHtmlUnitDriverBuilder - .webAppContextSetup(context) - .build() - } ----- - -We can also specify additional configuration options, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - WebDriver driver; - - @BeforeEach - void setup() { - driver = MockMvcHtmlUnitDriverBuilder - // demonstrates applying a MockMvcConfigurer (Spring Security) - .webAppContextSetup(context, springSecurity()) - // for illustration only - defaults to "" - .contextPath("") - // By default MockMvc is used for localhost only; - // the following will use MockMvc for example.com and example.org as well - .useMockMvcForHosts("example.com","example.org") - .build(); - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - lateinit var driver: WebDriver - - @BeforeEach - fun setup() { - driver = MockMvcHtmlUnitDriverBuilder - // demonstrates applying a MockMvcConfigurer (Spring Security) - .webAppContextSetup(context, springSecurity()) - // for illustration only - defaults to "" - .contextPath("") - // By default MockMvc is used for localhost only; - // the following will use MockMvc for example.com and example.org as well - .useMockMvcForHosts("example.com","example.org") - .build() - } ----- - -As an alternative, we can perform the exact same setup by configuring the `MockMvc` -instance separately and supplying it to the `MockMvcHtmlUnitDriverBuilder`, as follows: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - MockMvc mockMvc = MockMvcBuilders - .webAppContextSetup(context) - .apply(springSecurity()) - .build(); - - driver = MockMvcHtmlUnitDriverBuilder - .mockMvcSetup(mockMvc) - // for illustration only - defaults to "" - .contextPath("") - // By default MockMvc is used for localhost only; - // the following will use MockMvc for example.com and example.org as well - .useMockMvcForHosts("example.com","example.org") - .build(); ----- - -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed ----- - -This is more verbose, but, by building the `WebDriver` with a `MockMvc` instance, we have -the full power of MockMvc at our fingertips. - -TIP: For additional information on creating a `MockMvc` instance, see -<>. - -[[spring-mvc-test-server-htmlunit-geb]] -===== MockMvc and Geb - -In the previous section, we saw how to use MockMvc with WebDriver. In this section, we -use https://www.gebish.org/[Geb] to make our tests even Groovy-er. - -[[spring-mvc-test-server-htmlunit-geb-why]] -====== Why Geb and MockMvc? - -Geb is backed by WebDriver, so it offers many of the -<> that we get from -WebDriver. However, Geb makes things even easier by taking care of some of the -boilerplate code for us. - -[[spring-mvc-test-server-htmlunit-geb-setup]] -====== MockMvc and Geb Setup - -We can easily initialize a Geb `Browser` with a Selenium WebDriver that uses MockMvc, as -follows: - -[source,groovy] ----- -def setup() { - browser.driver = MockMvcHtmlUnitDriverBuilder - .webAppContextSetup(context) - .build() -} ----- - -NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced -usage, see <>. - -This ensures that any URL referencing `localhost` as the server is directed to our -`MockMvc` instance without the need for a real HTTP connection. Any other URL is -requested by using a network connection as normal. This lets us easily test the use of -CDNs. - -[[spring-mvc-test-server-htmlunit-geb-usage]] -====== MockMvc and Geb Usage - -Now we can use Geb as we normally would but without the need to deploy our application to -a Servlet container. For example, we can request the view to create a message with the -following: - -[source,groovy] ----- -to CreateMessagePage ----- - -We can then fill out the form and submit it to create a message, as follows: - -[source,groovy] ----- -when: -form.summary = expectedSummary -form.text = expectedMessage -submit.click(ViewMessagePage) ----- - -Any unrecognized method calls or property accesses or references that are not found are -forwarded to the current page object. This removes a lot of the boilerplate code we -needed when using WebDriver directly. - -As with direct WebDriver usage, this improves on the design of our -<> by using the Page Object -Pattern. As mentioned previously, we can use the Page Object Pattern with HtmlUnit and -WebDriver, but it is even easier with Geb. Consider our new Groovy-based -`CreateMessagePage` implementation: - -[source,groovy] ----- -class CreateMessagePage extends Page { - static url = 'messages/form' - static at = { assert title == 'Messages : Create'; true } - static content = { - submit { $('input[type=submit]') } - form { $('form') } - errors(required:false) { $('label.error, .alert-error')?.text() } - } -} ----- - -Our `CreateMessagePage` extends `Page`. We do not go over the details of `Page`, but, in -summary, it contains common functionality for all of our pages. We define a URL in which -this page can be found. This lets us navigate to the page, as follows: - -[source,groovy] ----- -to CreateMessagePage ----- - -We also have an `at` closure that determines if we are at the specified page. It should -return `true` if we are on the correct page. This is why we can assert that we are on the -correct page, as follows: - -[source,groovy] ----- -then: -at CreateMessagePage -errors.contains('This field is required.') ----- - -NOTE: We use an assertion in the closure so that we can determine where things went wrong -if we were at the wrong page. - -Next, we create a `content` closure that specifies all the areas of interest within the -page. We can use a -https://www.gebish.org/manual/current/#the-jquery-ish-navigator-api[jQuery-ish Navigator -API] to select the content in which we are interested. - -Finally, we can verify that a new message was created successfully, as follows: - -[source,groovy] ----- -then: -at ViewMessagePage -success == 'Successfully created a new message' -id -date -summary == expectedSummary -message == expectedMessage ----- - -For further details on how to get the most out of Geb, see -https://www.gebish.org/manual/current/[The Book of Geb] user's manual. - - -[[spring-mvc-test-client]] -=== Testing Client Applications - -You can use client-side tests to test code that internally uses the `RestTemplate`. The -idea is to declare expected requests and to provide "`stub`" responses so that you can -focus on testing the code in isolation (that is, without running a server). The following -example shows how to do so: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - RestTemplate restTemplate = new RestTemplate(); - - MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build(); - mockServer.expect(requestTo("/greeting")).andRespond(withSuccess()); - - // Test code that uses the above RestTemplate ... - - mockServer.verify(); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - val restTemplate = RestTemplate() - - val mockServer = MockRestServiceServer.bindTo(restTemplate).build() - mockServer.expect(requestTo("/greeting")).andRespond(withSuccess()) - - // Test code that uses the above RestTemplate ... - - mockServer.verify() ----- - -In the preceding example, `MockRestServiceServer` (the central class for client-side REST -tests) configures the `RestTemplate` with a custom `ClientHttpRequestFactory` that -asserts actual requests against expectations and returns "`stub`" responses. In this -case, we expect a request to `/greeting` and want to return a 200 response with -`text/plain` content. We can define additional expected requests and stub responses as -needed. When we define expected requests and stub responses, the `RestTemplate` can be -used in client-side code as usual. At the end of testing, `mockServer.verify()` can be -used to verify that all expectations have been satisfied. - -By default, requests are expected in the order in which expectations were declared. You -can set the `ignoreExpectOrder` option when building the server, in which case all -expectations are checked (in order) to find a match for a given request. That means -requests are allowed to come in any order. The following example uses `ignoreExpectOrder`: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build(); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build() ----- - -Even with unordered requests by default, each request is allowed to run once only. -The `expect` method provides an overloaded variant that accepts an `ExpectedCount` -argument that specifies a count range (for example, `once`, `manyTimes`, `max`, `min`, -`between`, and so on). The following example uses `times`: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - RestTemplate restTemplate = new RestTemplate(); - - MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build(); - mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess()); - mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess()); - - // ... - - mockServer.verify(); ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - val restTemplate = RestTemplate() - - val mockServer = MockRestServiceServer.bindTo(restTemplate).build() - mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess()) - mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess()) - - // ... - - mockServer.verify() ----- - -Note that, when `ignoreExpectOrder` is not set (the default), and, therefore, requests -are expected in order of declaration, then that order applies only to the first of any -expected request. For example if "/something" is expected two times followed by -"/somewhere" three times, then there should be a request to "/something" before there is -a request to "/somewhere", but, aside from that subsequent "/something" and "/somewhere", -requests can come at any time. - -As an alternative to all of the above, the client-side test support also provides a -`ClientHttpRequestFactory` implementation that you can configure into a `RestTemplate` to -bind it to a `MockMvc` instance. That allows processing requests using actual server-side -logic but without running a server. The following example shows how to do so: - -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); - this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc)); - - // Test code that uses the above RestTemplate ... ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build() - restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc)) - - // Test code that uses the above RestTemplate ... ----- - -[[spring-mvc-test-client-static-imports]] -==== Static Imports - -As with server-side tests, the fluent API for client-side tests requires a few static -imports. Those are easy to find by searching for `MockRest*`. Eclipse users should add -`MockRestRequestMatchers.{asterisk}` and `MockRestResponseCreators.{asterisk}` as -"`favorite static members`" in the Eclipse preferences under Java -> Editor -> Content -Assist -> Favorites. That allows using content assist after typing the first character of -the static method name. Other IDEs (such IntelliJ) may not require any additional -configuration. Check for the support for code completion on static members. - -[[spring-mvc-test-client-resources]] -==== Further Examples of Client-side REST Tests - -Spring MVC Test's own tests include -{spring-framework-main-code}/spring-test/src/test/java/org/springframework/test/web/client/samples[example -tests] of client-side REST tests. - - -[[testing-resources]] -== Further Resources -See the following resources for more information about testing: - -* https://www.junit.org/[JUnit]: "`A programmer-friendly testing framework for Java`". - Used by the Spring Framework in its test suite and supported in the - <>. -* https://testng.org/[TestNG]: A testing framework inspired by JUnit with added support - for test groups, data-driven testing, distributed testing, and other features. Supported - in the <> -* https://assertj.github.io/doc/[AssertJ]: "`Fluent assertions for Java`", - including support for Java 8 lambdas, streams, and other features. -* https://en.wikipedia.org/wiki/Mock_Object[Mock Objects]: Article in Wikipedia. -* http://www.mockobjects.com/[MockObjects.com]: Web site dedicated to mock objects, a - technique for improving the design of code within test-driven development. -* https://mockito.github.io[Mockito]: Java mock library based on the - http://xunitpatterns.com/Test%20Spy.html[Test Spy] pattern. Used by the Spring Framework - in its test suite. -* https://easymock.org/[EasyMock]: Java library "`that provides Mock Objects for - interfaces (and objects through the class extension) by generating them on the fly using - Java's proxy mechanism.`" -* https://jmock.org/[JMock]: Library that supports test-driven development of Java code - with mock objects. -* https://www.dbunit.org/[DbUnit]: JUnit extension (also usable with Ant and Maven) that - is targeted at database-driven projects and, among other things, puts your database into - a known state between test runs. -* https://www.testcontainers.org/[Testcontainers]: Java library that supports JUnit - tests, providing lightweight, throwaway instances of common databases, Selenium web - browsers, or anything else that can run in a Docker container. -* https://sourceforge.net/projects/grinder/[The Grinder]: Java load testing framework. -* https://github.com/Ninja-Squad/springmockk[SpringMockK]: Support for Spring Boot - integration tests written in Kotlin using https://mockk.io/[MockK] instead of Mockito. +include::testing/testing-appendix.adoc[leveloffset=+1] diff --git a/framework-docs/src/docs/asciidoc/testing/integration-testing.adoc b/framework-docs/src/docs/asciidoc/testing/integration-testing.adoc new file mode 100644 index 000000000000..5c5bc4e90b30 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/testing/integration-testing.adoc @@ -0,0 +1,155 @@ +[[integration-testing]] += Integration Testing + +It is important to be able to perform some integration testing without requiring +deployment to your application server or connecting to other enterprise infrastructure. +Doing so lets you test things such as: + +* The correct wiring of your Spring IoC container contexts. +* Data access using JDBC or an ORM tool. This can include such things as the correctness + of SQL statements, Hibernate queries, JPA entity mappings, and so forth. + +The Spring Framework provides first-class support for integration testing in the +`spring-test` module. The name of the actual JAR file might include the release version +and might also be in the long `org.springframework.test` form, depending on where you get +it from (see the <> +for an explanation). This library includes the `org.springframework.test` package, which +contains valuable classes for integration testing with a Spring container. This testing +does not rely on an application server or other deployment environment. Such tests are +slower to run than unit tests but much faster than the equivalent Selenium tests or +remote tests that rely on deployment to an application server. + +Unit and integration testing support is provided in the form of the annotation-driven +<>. The TestContext framework is +agnostic of the actual testing framework in use, which allows instrumentation of tests +in various environments, including JUnit, TestNG, and others. + +The following section provides an overview of the high-level goals of Spring's +integration support, and the rest of this chapter then focuses on dedicated topics: + +* <> +* <> +* <> +* <> +* <> +* <> + + + +[[integration-testing-goals]] +== Goals of Integration Testing + +Spring's integration testing support has the following primary goals: + +* To manage <> between tests. +* To provide <>. +* To provide <> appropriate to integration testing. +* To supply <> that assist + developers in writing integration tests. + +The next few sections describe each goal and provide links to implementation and +configuration details. + + +[[testing-ctx-management]] +=== Context Management and Caching + +The Spring TestContext Framework provides consistent loading of Spring +`ApplicationContext` instances and `WebApplicationContext` instances as well as caching +of those contexts. Support for the caching of loaded contexts is important, because +startup time can become an issue -- not because of the overhead of Spring itself, but +because the objects instantiated by the Spring container take time to instantiate. For +example, a project with 50 to 100 Hibernate mapping files might take 10 to 20 seconds to +load the mapping files, and incurring that cost before running every test in every test +fixture leads to slower overall test runs that reduce developer productivity. + +Test classes typically declare either an array of resource locations for XML or Groovy +configuration metadata -- often in the classpath -- or an array of component classes that +is used to configure the application. These locations or classes are the same as or +similar to those specified in `web.xml` or other configuration files for production +deployments. + +By default, once loaded, the configured `ApplicationContext` is reused for each test. +Thus, the setup cost is incurred only once per test suite, and subsequent test execution +is much faster. In this context, the term "`test suite`" means all tests run in the same +JVM -- for example, all tests run from an Ant, Maven, or Gradle build for a given project +or module. In the unlikely case that a test corrupts the application context and requires +reloading (for example, by modifying a bean definition or the state of an application +object) the TestContext framework can be configured to reload the configuration and +rebuild the application context before executing the next test. + +See <> and <> with the +TestContext framework. + + +[[testing-fixture-di]] +=== Dependency Injection of Test Fixtures + +When the TestContext framework loads your application context, it can optionally +configure instances of your test classes by using Dependency Injection. This provides a +convenient mechanism for setting up test fixtures by using preconfigured beans from your +application context. A strong benefit here is that you can reuse application contexts +across various testing scenarios (for example, for configuring Spring-managed object +graphs, transactional proxies, `DataSource` instances, and others), thus avoiding the +need to duplicate complex test fixture setup for individual test cases. + +As an example, consider a scenario where we have a class (`HibernateTitleRepository`) +that implements data access logic for a `Title` domain entity. We want to write +integration tests that test the following areas: + +* The Spring configuration: Basically, is everything related to the configuration of the + `HibernateTitleRepository` bean correct and present? +* The Hibernate mapping file configuration: Is everything mapped correctly and are the + correct lazy-loading settings in place? +* The logic of the `HibernateTitleRepository`: Does the configured instance of this class + perform as anticipated? + +See dependency injection of test fixtures with the +<>. + + +[[testing-tx]] +=== Transaction Management + +One common issue in tests that access a real database is their effect on the state of the +persistence store. Even when you use a development database, changes to the state may +affect future tests. Also, many operations -- such as inserting or modifying persistent +data -- cannot be performed (or verified) outside of a transaction. + +The TestContext framework addresses this issue. By default, the framework creates and +rolls back a transaction for each test. You can write code that can assume the existence +of a transaction. If you call transactionally proxied objects in your tests, they behave +correctly, according to their configured transactional semantics. In addition, if a test +method deletes the contents of selected tables while running within the transaction +managed for the test, the transaction rolls back by default, and the database returns to +its state prior to execution of the test. Transactional support is provided to a test by +using a `PlatformTransactionManager` bean defined in the test's application context. + +If you want a transaction to commit (unusual, but occasionally useful when you want a +particular test to populate or modify the database), you can tell the TestContext +framework to cause the transaction to commit instead of roll back by using the +<> annotation. + +See transaction management with the <>. + + +[[testing-support-classes]] +=== Support Classes for Integration Testing + +The Spring TestContext Framework provides several `abstract` support classes that +simplify the writing of integration tests. These base test classes provide well-defined +hooks into the testing framework as well as convenient instance variables and methods, +which let you access: + +* The `ApplicationContext`, for performing explicit bean lookups or testing the state of + the context as a whole. +* A `JdbcTemplate`, for executing SQL statements to query the database. You can use such + queries to confirm database state both before and after execution of database-related + application code, and Spring ensures that such queries run in the scope of the same + transaction as the application code. When used in conjunction with an ORM tool, be sure + to avoid <>. + +In addition, you may want to create your own custom, application-wide superclass with +instance variables and methods specific to your project. + +See support classes for the <>. diff --git a/framework-docs/src/docs/asciidoc/testing/spring-mvc-test-client.adoc b/framework-docs/src/docs/asciidoc/testing/spring-mvc-test-client.adoc new file mode 100644 index 000000000000..83eaecc40a6b --- /dev/null +++ b/framework-docs/src/docs/asciidoc/testing/spring-mvc-test-client.adoc @@ -0,0 +1,136 @@ +[[spring-mvc-test-client]] += Testing Client Applications + +You can use client-side tests to test code that internally uses the `RestTemplate`. The +idea is to declare expected requests and to provide "`stub`" responses so that you can +focus on testing the code in isolation (that is, without running a server). The following +example shows how to do so: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + RestTemplate restTemplate = new RestTemplate(); + + MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build(); + mockServer.expect(requestTo("/greeting")).andRespond(withSuccess()); + + // Test code that uses the above RestTemplate ... + + mockServer.verify(); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val restTemplate = RestTemplate() + + val mockServer = MockRestServiceServer.bindTo(restTemplate).build() + mockServer.expect(requestTo("/greeting")).andRespond(withSuccess()) + + // Test code that uses the above RestTemplate ... + + mockServer.verify() +---- + +In the preceding example, `MockRestServiceServer` (the central class for client-side REST +tests) configures the `RestTemplate` with a custom `ClientHttpRequestFactory` that +asserts actual requests against expectations and returns "`stub`" responses. In this +case, we expect a request to `/greeting` and want to return a 200 response with +`text/plain` content. We can define additional expected requests and stub responses as +needed. When we define expected requests and stub responses, the `RestTemplate` can be +used in client-side code as usual. At the end of testing, `mockServer.verify()` can be +used to verify that all expectations have been satisfied. + +By default, requests are expected in the order in which expectations were declared. You +can set the `ignoreExpectOrder` option when building the server, in which case all +expectations are checked (in order) to find a match for a given request. That means +requests are allowed to come in any order. The following example uses `ignoreExpectOrder`: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build(); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build() +---- + +Even with unordered requests by default, each request is allowed to run once only. +The `expect` method provides an overloaded variant that accepts an `ExpectedCount` +argument that specifies a count range (for example, `once`, `manyTimes`, `max`, `min`, +`between`, and so on). The following example uses `times`: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + RestTemplate restTemplate = new RestTemplate(); + + MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build(); + mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess()); + mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess()); + + // ... + + mockServer.verify(); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val restTemplate = RestTemplate() + + val mockServer = MockRestServiceServer.bindTo(restTemplate).build() + mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess()) + mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess()) + + // ... + + mockServer.verify() +---- + +Note that, when `ignoreExpectOrder` is not set (the default), and, therefore, requests +are expected in order of declaration, then that order applies only to the first of any +expected request. For example if "/something" is expected two times followed by +"/somewhere" three times, then there should be a request to "/something" before there is +a request to "/somewhere", but, aside from that subsequent "/something" and "/somewhere", +requests can come at any time. + +As an alternative to all of the above, the client-side test support also provides a +`ClientHttpRequestFactory` implementation that you can configure into a `RestTemplate` to +bind it to a `MockMvc` instance. That allows processing requests using actual server-side +logic but without running a server. The following example shows how to do so: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc)); + + // Test code that uses the above RestTemplate ... +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build() + restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc)) + + // Test code that uses the above RestTemplate ... +---- + +[[spring-mvc-test-client-static-imports]] +== Static Imports + +As with server-side tests, the fluent API for client-side tests requires a few static +imports. Those are easy to find by searching for `MockRest*`. Eclipse users should add +`MockRestRequestMatchers.{asterisk}` and `MockRestResponseCreators.{asterisk}` as +"`favorite static members`" in the Eclipse preferences under Java -> Editor -> Content +Assist -> Favorites. That allows using content assist after typing the first character of +the static method name. Other IDEs (such IntelliJ) may not require any additional +configuration. Check for the support for code completion on static members. + +[[spring-mvc-test-client-resources]] +== Further Examples of Client-side REST Tests + +Spring MVC Test's own tests include +{spring-framework-main-code}/spring-test/src/test/java/org/springframework/test/web/client/samples[example +tests] of client-side REST tests. diff --git a/framework-docs/src/docs/asciidoc/testing/spring-mvc-test-framework.adoc b/framework-docs/src/docs/asciidoc/testing/spring-mvc-test-framework.adoc new file mode 100644 index 000000000000..196212377fa0 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/testing/spring-mvc-test-framework.adoc @@ -0,0 +1,1863 @@ +[[spring-mvc-test-framework]] += MockMvc + +The Spring MVC Test framework, also known as MockMvc, provides support for testing Spring +MVC applications. It performs full Spring MVC request handling but via mock request and +response objects instead of a running server. + +MockMvc can be used on its own to perform requests and verify responses. It can also be +used through the <> where MockMvc is plugged in as the server to handle +requests with. The advantage of `WebTestClient` is the option to work with higher level +objects instead of raw data as well as the ability to switch to full, end-to-end HTTP +tests against a live server and use the same test API. + + +[[spring-mvc-test-server]] +== Overview + +You can write plain unit tests for Spring MVC by instantiating a controller, injecting it +with dependencies, and calling its methods. However such tests do not verify request +mappings, data binding, message conversion, type conversion, validation, and nor +do they involve any of the supporting `@InitBinder`, `@ModelAttribute`, or +`@ExceptionHandler` methods. + +The Spring MVC Test framework, also known as `MockMvc`, aims to provide more complete +testing for Spring MVC controllers without a running server. It does that by invoking +the `DispatcherServlet` and passing +<> from the +`spring-test` module which replicates the full Spring MVC request handling without +a running server. + +MockMvc is a server side test framework that lets you verify most of the functionality +of a Spring MVC application using lightweight and targeted tests. You can use it on +its own to perform requests and to verify responses, or you can also use it through +the <> API with MockMvc plugged in as the server to handle requests +with. + + +[[spring-mvc-test-server-static-imports]] +== Static Imports + +When using MockMvc directly to perform requests, you'll need static imports for: + +- `MockMvcBuilders.{asterisk}` +- `MockMvcRequestBuilders.{asterisk}` +- `MockMvcResultMatchers.{asterisk}` +- `MockMvcResultHandlers.{asterisk}` + +An easy way to remember that is search for `MockMvc*`. If using Eclipse be sure to also +add the above as "`favorite static members`" in the Eclipse preferences. + +When using MockMvc through the <> you do not need static imports. +The `WebTestClient` provides a fluent API without static imports. + + +[[spring-mvc-test-server-setup-options]] +== Setup Choices + +MockMvc can be setup in one of two ways. One is to point directly to the controllers you +want to test and programmatically configure Spring MVC infrastructure. The second is to +point to Spring configuration with Spring MVC and controller infrastructure in it. + +To set up MockMvc for testing a specific controller, use the following: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + class MyWebTests { + + MockMvc mockMvc; + + @BeforeEach + void setup() { + this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build(); + } + + // ... + + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class MyWebTests { + + lateinit var mockMvc : MockMvc + + @BeforeEach + fun setup() { + mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build() + } + + // ... + + } +---- + +Or you can also use this setup when testing through the +<> which delegates to the same builder +as shown above. + +To set up MockMvc through Spring configuration, use the following: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitWebConfig(locations = "my-servlet-context.xml") + class MyWebTests { + + MockMvc mockMvc; + + @BeforeEach + void setup(WebApplicationContext wac) { + this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); + } + + // ... + + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitWebConfig(locations = ["my-servlet-context.xml"]) + class MyWebTests { + + lateinit var mockMvc: MockMvc + + @BeforeEach + fun setup(wac: WebApplicationContext) { + mockMvc = MockMvcBuilders.webAppContextSetup(wac).build() + } + + // ... + + } +---- + +Or you can also use this setup when testing through the +<> which delegates to the same builder +as shown above. + + + +Which setup option should you use? + +The `webAppContextSetup` loads your actual Spring MVC configuration, resulting in a more +complete integration test. Since the TestContext framework caches the loaded Spring +configuration, it helps keep tests running fast, even as you introduce more tests in your +test suite. Furthermore, you can inject mock services into controllers through Spring +configuration to remain focused on testing the web layer. The following example declares +a mock service with Mockito: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + +---- + +You can then inject the mock service into the test to set up and verify your +expectations, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitWebConfig(locations = "test-servlet-context.xml") + class AccountTests { + + @Autowired + AccountService accountService; + + MockMvc mockMvc; + + @BeforeEach + void setup(WebApplicationContext wac) { + this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); + } + + // ... + + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitWebConfig(locations = ["test-servlet-context.xml"]) + class AccountTests { + + @Autowired + lateinit var accountService: AccountService + + lateinit mockMvc: MockMvc + + @BeforeEach + fun setup(wac: WebApplicationContext) { + mockMvc = MockMvcBuilders.webAppContextSetup(wac).build() + } + + // ... + + } +---- + +The `standaloneSetup`, on the other hand, is a little closer to a unit test. It tests one +controller at a time. You can manually inject the controller with mock dependencies, and +it does not involve loading Spring configuration. Such tests are more focused on style +and make it easier to see which controller is being tested, whether any specific Spring +MVC configuration is required to work, and so on. The `standaloneSetup` is also a very +convenient way to write ad-hoc tests to verify specific behavior or to debug an issue. + +As with most "`integration versus unit testing`" debates, there is no right or wrong +answer. However, using the `standaloneSetup` does imply the need for additional +`webAppContextSetup` tests in order to verify your Spring MVC configuration. +Alternatively, you can write all your tests with `webAppContextSetup`, in order to always +test against your actual Spring MVC configuration. + +[[spring-mvc-test-server-setup-steps]] +== Setup Features + +No matter which MockMvc builder you use, all `MockMvcBuilder` implementations provide +some common and very useful features. For example, you can declare an `Accept` header for +all requests and expect a status of 200 as well as a `Content-Type` header in all +responses, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // static import of MockMvcBuilders.standaloneSetup + + MockMvc mockMvc = standaloneSetup(new MusicController()) + .defaultRequest(get("/").accept(MediaType.APPLICATION_JSON)) + .alwaysExpect(status().isOk()) + .alwaysExpect(content().contentType("application/json;charset=UTF-8")) + .build(); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- + +In addition, third-party frameworks (and applications) can pre-package setup +instructions, such as those in a `MockMvcConfigurer`. The Spring Framework has one such +built-in implementation that helps to save and re-use the HTTP session across requests. +You can use it as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // static import of SharedHttpSessionConfigurer.sharedHttpSession + + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController()) + .apply(sharedHttpSession()) + .build(); + + // Use mockMvc to perform requests... +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- + +See the javadoc for +{api-spring-framework}/test/web/servlet/setup/ConfigurableMockMvcBuilder.html[`ConfigurableMockMvcBuilder`] +for a list of all MockMvc builder features or use the IDE to explore the available options. + +[[spring-mvc-test-server-performing-requests]] +== Performing Requests + +This section shows how to use MockMvc on its own to perform requests and verify responses. +If using MockMvc through the `WebTestClient` please see the corresponding section on +<> instead. + +To perform requests that use any HTTP method, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // static import of MockMvcRequestBuilders.* + + mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON)); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.servlet.post + + mockMvc.post("/hotels/{id}", 42) { + accept = MediaType.APPLICATION_JSON + } +---- + +You can also perform file upload requests that internally use +`MockMultipartHttpServletRequest` so that there is no actual parsing of a multipart +request. Rather, you have to set it up to be similar to the following example: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8"))); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.servlet.multipart + + mockMvc.multipart("/doc") { + file("a1", "ABC".toByteArray(charset("UTF8"))) + } +---- + +You can specify query parameters in URI template style, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + mockMvc.perform(get("/hotels?thing={thing}", "somewhere")); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + mockMvc.get("/hotels?thing={thing}", "somewhere") +---- + +You can also add Servlet request parameters that represent either query or form +parameters, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + mockMvc.perform(get("/hotels").param("thing", "somewhere")); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.servlet.get + + mockMvc.get("/hotels") { + param("thing", "somewhere") + } +---- + +If application code relies on Servlet request parameters and does not check the query +string explicitly (as is most often the case), it does not matter which option you use. +Keep in mind, however, that query parameters provided with the URI template are decoded +while request parameters provided through the `param(...)` method are expected to already +be decoded. + +In most cases, it is preferable to leave the context path and the Servlet path out of the +request URI. If you must test with the full request URI, be sure to set the `contextPath` +and `servletPath` accordingly so that request mappings work, as the following example +shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main")) +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.servlet.get + + mockMvc.get("/app/main/hotels/{id}") { + contextPath = "/app" + servletPath = "/main" + } +---- + +In the preceding example, it would be cumbersome to set the `contextPath` and +`servletPath` with every performed request. Instead, you can set up default request +properties, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + class MyWebTests { + + MockMvc mockMvc; + + @BeforeEach + void setup() { + mockMvc = standaloneSetup(new AccountController()) + .defaultRequest(get("/") + .contextPath("/app").servletPath("/main") + .accept(MediaType.APPLICATION_JSON)).build(); + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- + +The preceding properties affect every request performed through the `MockMvc` instance. +If the same property is also specified on a given request, it overrides the default +value. That is why the HTTP method and URI in the default request do not matter, since +they must be specified on every request. + +[[spring-mvc-test-server-defining-expectations]] +== Defining Expectations + +You can define expectations by appending one or more `andExpect(..)` calls after +performing a request, as the following example shows. As soon as one expectation fails, +no other expectations will be asserted. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.* + + mockMvc.perform(get("/accounts/1")).andExpect(status().isOk()); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.servlet.get + + mockMvc.get("/accounts/1").andExpect { + status { isOk() } + } +---- + +You can define multiple expectations by appending `andExpectAll(..)` after performing a +request, as the following example shows. In contrast to `andExpect(..)`, +`andExpectAll(..)` guarantees that all supplied expectations will be asserted and that +all failures will be tracked and reported. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.* + + mockMvc.perform(get("/accounts/1")).andExpectAll( + status().isOk(), + content().contentType("application/json;charset=UTF-8")); +---- + +`MockMvcResultMatchers.*` provides a number of expectations, some of which are further +nested with more detailed expectations. + +Expectations fall in two general categories. The first category of assertions verifies +properties of the response (for example, the response status, headers, and content). +These are the most important results to assert. + +The second category of assertions goes beyond the response. These assertions let you +inspect Spring MVC specific aspects, such as which controller method processed the +request, whether an exception was raised and handled, what the content of the model is, +what view was selected, what flash attributes were added, and so on. They also let you +inspect Servlet specific aspects, such as request and session attributes. + +The following test asserts that binding or validation failed: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + mockMvc.perform(post("/persons")) + .andExpect(status().isOk()) + .andExpect(model().attributeHasErrors("person")); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.servlet.post + + mockMvc.post("/persons").andExpect { + status { isOk() } + model { + attributeHasErrors("person") + } + } +---- + +Many times, when writing tests, it is useful to dump the results of the performed +request. You can do so as follows, where `print()` is a static import from +`MockMvcResultHandlers`: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + mockMvc.perform(post("/persons")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(model().attributeHasErrors("person")); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + import org.springframework.test.web.servlet.post + + mockMvc.post("/persons").andDo { + print() + }.andExpect { + status { isOk() } + model { + attributeHasErrors("person") + } + } +---- + +As long as request processing does not cause an unhandled exception, the `print()` method +prints all the available result data to `System.out`. There is also a `log()` method and +two additional variants of the `print()` method, one that accepts an `OutputStream` and +one that accepts a `Writer`. For example, invoking `print(System.err)` prints the result +data to `System.err`, while invoking `print(myWriter)` prints the result data to a custom +writer. If you want to have the result data logged instead of printed, you can invoke the +`log()` method, which logs the result data as a single `DEBUG` message under the +`org.springframework.test.web.servlet.result` logging category. + +In some cases, you may want to get direct access to the result and verify something that +cannot be verified otherwise. This can be achieved by appending `.andReturn()` after all +other expectations, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn(); + // ... +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + var mvcResult = mockMvc.post("/persons").andExpect { status { isOk() } }.andReturn() + // ... +---- + +If all tests repeat the same expectations, you can set up common expectations once when +building the `MockMvc` instance, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + standaloneSetup(new SimpleController()) + .alwaysExpect(status().isOk()) + .alwaysExpect(content().contentType("application/json;charset=UTF-8")) + .build() +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- + +Note that common expectations are always applied and cannot be overridden without +creating a separate `MockMvc` instance. + +When a JSON response content contains hypermedia links created with +https://github.com/spring-projects/spring-hateoas[Spring HATEOAS], you can verify the +resulting links by using JsonPath expressions, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people")); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + mockMvc.get("/people") { + accept(MediaType.APPLICATION_JSON) + }.andExpect { + jsonPath("$.links[?(@.rel == 'self')].href") { + value("http://localhost:8080/people") + } + } +---- + +When XML response content contains hypermedia links created with +https://github.com/spring-projects/spring-hateoas[Spring HATEOAS], you can verify the +resulting links by using XPath expressions: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + Map ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom"); + mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML)) + .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people")); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val ns = mapOf("ns" to "http://www.w3.org/2005/Atom") + mockMvc.get("/handle") { + accept(MediaType.APPLICATION_XML) + }.andExpect { + xpath("/person/ns:link[@rel='self']/@href", ns) { + string("http://localhost:8080/people") + } + } +---- + +[[spring-mvc-test-async-requests]] +== Async Requests + +This section shows how to use MockMvc on its own to test asynchronous request handling. +If using MockMvc through the <>, there is nothing special to do to make +asynchronous requests work as the `WebTestClient` automatically does what is described +in this section. + +Servlet asynchronous requests, <>, +work by exiting the Servlet container thread and allowing the application to compute +the response asynchronously, after which an async dispatch is made to complete +processing on a Servlet container thread. + +In Spring MVC Test, async requests can be tested by asserting the produced async value +first, then manually performing the async dispatch, and finally verifying the response. +Below is an example test for controller methods that return `DeferredResult`, `Callable`, +or reactive type such as Reactor `Mono`: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.* + + @Test + void test() throws Exception { + MvcResult mvcResult = this.mockMvc.perform(get("/path")) + .andExpect(status().isOk()) <1> + .andExpect(request().asyncStarted()) <2> + .andExpect(request().asyncResult("body")) <3> + .andReturn(); + + this.mockMvc.perform(asyncDispatch(mvcResult)) <4> + .andExpect(status().isOk()) <5> + .andExpect(content().string("body")); + } +---- +<1> Check response status is still unchanged +<2> Async processing must have started +<3> Wait and assert the async result +<4> Manually perform an ASYNC dispatch (as there is no running container) +<5> Verify the final response + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Test + fun test() { + var mvcResult = mockMvc.get("/path").andExpect { + status { isOk() } // <1> + request { asyncStarted() } // <2> + // TODO Remove unused generic parameter + request { asyncResult("body") } // <3> + }.andReturn() + + + mockMvc.perform(asyncDispatch(mvcResult)) // <4> + .andExpect { + status { isOk() } // <5> + content().string("body") + } + } +---- +<1> Check response status is still unchanged +<2> Async processing must have started +<3> Wait and assert the async result +<4> Manually perform an ASYNC dispatch (as there is no running container) +<5> Verify the final response + + +[[spring-mvc-test-vs-streaming-response]] +== Streaming Responses + +The best way to test streaming responses such as Server-Sent Events is through the +<> which can be used as a test client to connect to a `MockMvc` instance +to perform tests on Spring MVC controllers without a running server. For example: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build(); + + FluxExchangeResult exchangeResult = client.get() + .uri("/persons") + .exchange() + .expectStatus().isOk() + .expectHeader().contentType("text/event-stream") + .returnResult(Person.class); + + // Use StepVerifier from Project Reactor to test the streaming response + + StepVerifier.create(exchangeResult.getResponseBody()) + .expectNext(new Person("N0"), new Person("N1"), new Person("N2")) + .expectNextCount(4) + .consumeNextWith(person -> assertThat(person.getName()).endsWith("7")) + .thenCancel() + .verify(); +---- + +`WebTestClient` can also connect to a live server and perform full end-to-end integration +tests. This is also supported in Spring Boot where you can +{docs-spring-boot}/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server]. + + +[[spring-mvc-test-server-filters]] +== Filter Registrations + +When setting up a `MockMvc` instance, you can register one or more Servlet `Filter` +instances, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build(); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- + +Registered filters are invoked through the `MockFilterChain` from `spring-test`, and the +last filter delegates to the `DispatcherServlet`. + + +[[spring-mvc-test-vs-end-to-end-integration-tests]] +== MockMvc vs End-to-End Tests + +MockMVc is built on Servlet API mock implementations from the +`spring-test` module and does not rely on a running container. Therefore, there are +some differences when compared to full end-to-end integration tests with an actual +client and a live server running. + +The easiest way to think about this is by starting with a blank `MockHttpServletRequest`. +Whatever you add to it is what the request becomes. Things that may catch you by surprise +are that there is no context path by default; no `jsessionid` cookie; no forwarding, +error, or async dispatches; and, therefore, no actual JSP rendering. Instead, +"`forwarded`" and "`redirected`" URLs are saved in the `MockHttpServletResponse` and can +be asserted with expectations. + +This means that, if you use JSPs, you can verify the JSP page to which the request was +forwarded, but no HTML is rendered. In other words, the JSP is not invoked. Note, +however, that all other rendering technologies that do not rely on forwarding, such as +Thymeleaf and Freemarker, render HTML to the response body as expected. The same is true +for rendering JSON, XML, and other formats through `@ResponseBody` methods. + +Alternatively, you may consider the full end-to-end integration testing support from +Spring Boot with `@SpringBootTest`. See the +{docs-spring-boot}/html/spring-boot-features.html#boot-features-testing[Spring Boot Reference Guide]. + +There are pros and cons for each approach. The options provided in Spring MVC Test are +different stops on the scale from classic unit testing to full integration testing. To be +certain, none of the options in Spring MVC Test fall under the category of classic unit +testing, but they are a little closer to it. For example, you can isolate the web layer +by injecting mocked services into controllers, in which case you are testing the web +layer only through the `DispatcherServlet` but with actual Spring configuration, as you +might test the data access layer in isolation from the layers above it. Also, you can use +the stand-alone setup, focusing on one controller at a time and manually providing the +configuration required to make it work. + +Another important distinction when using Spring MVC Test is that, conceptually, such +tests are the server-side, so you can check what handler was used, if an exception was +handled with a HandlerExceptionResolver, what the content of the model is, what binding +errors there were, and other details. That means that it is easier to write expectations, +since the server is not an opaque box, as it is when testing it through an actual HTTP +client. This is generally an advantage of classic unit testing: It is easier to write, +reason about, and debug but does not replace the need for full integration tests. At the +same time, it is important not to lose sight of the fact that the response is the most +important thing to check. In short, there is room here for multiple styles and strategies +of testing even within the same project. + +[[spring-mvc-test-server-resources]] +== Further Examples + +The framework's own tests include +{spring-framework-main-code}/spring-test/src/test/java/org/springframework/test/web/servlet/samples[ +many sample tests] intended to show how to use MockMvc on its own or through the +{spring-framework-main-code}/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client[ +WebTestClient]. Browse these examples for further ideas. + + +[[spring-mvc-test-server-htmlunit]] +== HtmlUnit Integration + +Spring provides integration between <> and +https://htmlunit.sourceforge.io/[HtmlUnit]. This simplifies performing end-to-end testing +when using HTML-based views. This integration lets you: + +* Easily test HTML pages by using tools such as + https://htmlunit.sourceforge.io/[HtmlUnit], + https://www.seleniumhq.org[WebDriver], and + https://www.gebish.org/manual/current/#spock-junit-testng[Geb] without the need to + deploy to a Servlet container. +* Test JavaScript within pages. +* Optionally, test using mock services to speed up testing. +* Share logic between in-container end-to-end tests and out-of-container integration tests. + +NOTE: MockMvc works with templating technologies that do not rely on a Servlet Container +(for example, Thymeleaf, FreeMarker, and others), but it does not work with JSPs, since +they rely on the Servlet container. + +[[spring-mvc-test-server-htmlunit-why]] +=== Why HtmlUnit Integration? + +The most obvious question that comes to mind is "`Why do I need this?`" The answer is +best found by exploring a very basic sample application. Assume you have a Spring MVC web +application that supports CRUD operations on a `Message` object. The application also +supports paging through all messages. How would you go about testing it? + +With Spring MVC Test, we can easily test if we are able to create a `Message`, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + MockHttpServletRequestBuilder createMessage = post("/messages/") + .param("summary", "Spring Rocks") + .param("text", "In case you didn't know, Spring Rocks!"); + + mockMvc.perform(createMessage) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/messages/123")); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Test + fun test() { + mockMvc.post("/messages/") { + param("summary", "Spring Rocks") + param("text", "In case you didn't know, Spring Rocks!") + }.andExpect { + status().is3xxRedirection() + redirectedUrl("/messages/123") + } + } +---- + +What if we want to test the form view that lets us create the message? For example, +assume our form looks like the following snippet: + +[source,xml,indent=0] +---- +
+ + + + + + + + +
+ +
+
+---- + +How do we ensure that our form produce the correct request to create a new message? A +naive attempt might resemble the following: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + mockMvc.perform(get("/messages/form")) + .andExpect(xpath("//input[@name='summary']").exists()) + .andExpect(xpath("//textarea[@name='text']").exists()); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + mockMvc.get("/messages/form").andExpect { + xpath("//input[@name='summary']") { exists() } + xpath("//textarea[@name='text']") { exists() } + } +---- + +This test has some obvious drawbacks. If we update our controller to use the parameter +`message` instead of `text`, our form test continues to pass, even though the HTML form +is out of synch with the controller. To resolve this we can combine our two tests, as +follows: + +[[spring-mvc-test-server-htmlunit-mock-mvc-test]] +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + String summaryParamName = "summary"; + String textParamName = "text"; + mockMvc.perform(get("/messages/form")) + .andExpect(xpath("//input[@name='" + summaryParamName + "']").exists()) + .andExpect(xpath("//textarea[@name='" + textParamName + "']").exists()); + + MockHttpServletRequestBuilder createMessage = post("/messages/") + .param(summaryParamName, "Spring Rocks") + .param(textParamName, "In case you didn't know, Spring Rocks!"); + + mockMvc.perform(createMessage) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/messages/123")); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val summaryParamName = "summary"; + val textParamName = "text"; + mockMvc.get("/messages/form").andExpect { + xpath("//input[@name='$summaryParamName']") { exists() } + xpath("//textarea[@name='$textParamName']") { exists() } + } + mockMvc.post("/messages/") { + param(summaryParamName, "Spring Rocks") + param(textParamName, "In case you didn't know, Spring Rocks!") + }.andExpect { + status().is3xxRedirection() + redirectedUrl("/messages/123") + } +---- + +This would reduce the risk of our test incorrectly passing, but there are still some +problems: + +* What if we have multiple forms on our page? Admittedly, we could update our XPath + expressions, but they get more complicated as we take more factors into account: Are + the fields the correct type? Are the fields enabled? And so on. +* Another issue is that we are doing double the work we would expect. We must first + verify the view, and then we submit the view with the same parameters we just verified. + Ideally, this could be done all at once. +* Finally, we still cannot account for some things. For example, what if the form has + JavaScript validation that we wish to test as well? + +The overall problem is that testing a web page does not involve a single interaction. +Instead, it is a combination of how the user interacts with a web page and how that web +page interacts with other resources. For example, the result of a form view is used as +the input to a user for creating a message. In addition, our form view can potentially +use additional resources that impact the behavior of the page, such as JavaScript +validation. + +[[spring-mvc-test-server-htmlunit-why-integration]] +==== Integration Testing to the Rescue? + +To resolve the issues mentioned earlier, we could perform end-to-end integration testing, +but this has some drawbacks. Consider testing the view that lets us page through the +messages. We might need the following tests: + +* Does our page display a notification to the user to indicate that no results are + available when the messages are empty? +* Does our page properly display a single message? +* Does our page properly support paging? + +To set up these tests, we need to ensure our database contains the proper messages. This +leads to a number of additional challenges: + +* Ensuring the proper messages are in the database can be tedious. (Consider foreign key + constraints.) +* Testing can become slow, since each test would need to ensure that the database is in + the correct state. +* Since our database needs to be in a specific state, we cannot run tests in parallel. +* Performing assertions on such items as auto-generated IDs, timestamps, and others can + be difficult. + +These challenges do not mean that we should abandon end-to-end integration testing +altogether. Instead, we can reduce the number of end-to-end integration tests by +refactoring our detailed tests to use mock services that run much faster, more reliably, +and without side effects. We can then implement a small number of true end-to-end +integration tests that validate simple workflows to ensure that everything works together +properly. + +[[spring-mvc-test-server-htmlunit-why-mockmvc]] +==== Enter HtmlUnit Integration + +So how can we achieve a balance between testing the interactions of our pages and still +retain good performance within our test suite? The answer is: "`By integrating MockMvc +with HtmlUnit.`" + +[[spring-mvc-test-server-htmlunit-options]] +==== HtmlUnit Integration Options + +You have a number of options when you want to integrate MockMvc with HtmlUnit: + +* <>: Use this option if you + want to use the raw HtmlUnit libraries. +* <>: Use this option to + ease development and reuse code between integration and end-to-end testing. +* <>: Use this option if you want to + use Groovy for testing, ease development, and reuse code between integration and + end-to-end testing. + +[[spring-mvc-test-server-htmlunit-mah]] +=== MockMvc and HtmlUnit + +This section describes how to integrate MockMvc and HtmlUnit. Use this option if you want +to use the raw HtmlUnit libraries. + +[[spring-mvc-test-server-htmlunit-mah-setup]] +==== MockMvc and HtmlUnit Setup + +First, make sure that you have included a test dependency on +`net.sourceforge.htmlunit:htmlunit`. In order to use HtmlUnit with Apache HttpComponents +4.5+, you need to use HtmlUnit 2.18 or higher. + +We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the +`MockMvcWebClientBuilder`, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + WebClient webClient; + + @BeforeEach + void setup(WebApplicationContext context) { + webClient = MockMvcWebClientBuilder + .webAppContextSetup(context) + .build(); + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + lateinit var webClient: WebClient + + @BeforeEach + fun setup(context: WebApplicationContext) { + webClient = MockMvcWebClientBuilder + .webAppContextSetup(context) + .build() + } +---- + +NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage, +see <>. + +This ensures that any URL that references `localhost` as the server is directed to our +`MockMvc` instance without the need for a real HTTP connection. Any other URL is +requested by using a network connection, as normal. This lets us easily test the use of +CDNs. + +[[spring-mvc-test-server-htmlunit-mah-usage]] +==== MockMvc and HtmlUnit Usage + +Now we can use HtmlUnit as we normally would but without the need to deploy our +application to a Servlet container. For example, we can request the view to create a +message with the following: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form"); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val createMsgFormPage = webClient.getPage("http://localhost/messages/form") +---- + +NOTE: The default context path is `""`. Alternatively, we can specify the context path, +as described in <>. + +Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it +to create a message, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm"); + HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary"); + summaryInput.setValueAttribute("Spring Rocks"); + HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text"); + textInput.setText("In case you didn't know, Spring Rocks!"); + HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit"); + HtmlPage newMessagePage = submit.click(); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val form = createMsgFormPage.getHtmlElementById("messageForm") + val summaryInput = createMsgFormPage.getHtmlElementById("summary") + summaryInput.setValueAttribute("Spring Rocks") + val textInput = createMsgFormPage.getHtmlElementById("text") + textInput.setText("In case you didn't know, Spring Rocks!") + val submit = form.getOneHtmlElementByAttribute("input", "type", "submit") + val newMessagePage = submit.click() +---- + +Finally, we can verify that a new message was created successfully. The following +assertions use the https://assertj.github.io/doc/[AssertJ] library: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123"); + String id = newMessagePage.getHtmlElementById("id").getTextContent(); + assertThat(id).isEqualTo("123"); + String summary = newMessagePage.getHtmlElementById("summary").getTextContent(); + assertThat(summary).isEqualTo("Spring Rocks"); + String text = newMessagePage.getHtmlElementById("text").getTextContent(); + assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!"); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123") + val id = newMessagePage.getHtmlElementById("id").getTextContent() + assertThat(id).isEqualTo("123") + val summary = newMessagePage.getHtmlElementById("summary").getTextContent() + assertThat(summary).isEqualTo("Spring Rocks") + val text = newMessagePage.getHtmlElementById("text").getTextContent() + assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!") +---- + +The preceding code improves on our +<> in a number of ways. +First, we no longer have to explicitly verify our form and then create a request that +looks like the form. Instead, we request the form, fill it out, and submit it, thereby +significantly reducing the overhead. + +Another important factor is that https://htmlunit.sourceforge.io/javascript.html[HtmlUnit +uses the Mozilla Rhino engine] to evaluate JavaScript. This means that we can also test +the behavior of JavaScript within our pages. + +See the https://htmlunit.sourceforge.io/gettingStarted.html[HtmlUnit documentation] for +additional information about using HtmlUnit. + +[[spring-mvc-test-server-htmlunit-mah-advanced-builder]] +==== Advanced `MockMvcWebClientBuilder` + +In the examples so far, we have used `MockMvcWebClientBuilder` in the simplest way +possible, by building a `WebClient` based on the `WebApplicationContext` loaded for us by +the Spring TestContext Framework. This approach is repeated in the following example: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + WebClient webClient; + + @BeforeEach + void setup(WebApplicationContext context) { + webClient = MockMvcWebClientBuilder + .webAppContextSetup(context) + .build(); + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + lateinit var webClient: WebClient + + @BeforeEach + fun setup(context: WebApplicationContext) { + webClient = MockMvcWebClientBuilder + .webAppContextSetup(context) + .build() + } +---- + +We can also specify additional configuration options, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + WebClient webClient; + + @BeforeEach + void setup() { + webClient = MockMvcWebClientBuilder + // demonstrates applying a MockMvcConfigurer (Spring Security) + .webAppContextSetup(context, springSecurity()) + // for illustration only - defaults to "" + .contextPath("") + // By default MockMvc is used for localhost only; + // the following will use MockMvc for example.com and example.org as well + .useMockMvcForHosts("example.com","example.org") + .build(); + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + lateinit var webClient: WebClient + + @BeforeEach + fun setup() { + webClient = MockMvcWebClientBuilder + // demonstrates applying a MockMvcConfigurer (Spring Security) + .webAppContextSetup(context, springSecurity()) + // for illustration only - defaults to "" + .contextPath("") + // By default MockMvc is used for localhost only; + // the following will use MockMvc for example.com and example.org as well + .useMockMvcForHosts("example.com","example.org") + .build() + } +---- + +As an alternative, we can perform the exact same setup by configuring the `MockMvc` +instance separately and supplying it to the `MockMvcWebClientBuilder`, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + MockMvc mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + + webClient = MockMvcWebClientBuilder + .mockMvcSetup(mockMvc) + // for illustration only - defaults to "" + .contextPath("") + // By default MockMvc is used for localhost only; + // the following will use MockMvc for example.com and example.org as well + .useMockMvcForHosts("example.com","example.org") + .build(); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- + +This is more verbose, but, by building the `WebClient` with a `MockMvc` instance, we have +the full power of MockMvc at our fingertips. + +TIP: For additional information on creating a `MockMvc` instance, see +<>. + +[[spring-mvc-test-server-htmlunit-webdriver]] +=== MockMvc and WebDriver + +In the previous sections, we have seen how to use MockMvc in conjunction with the raw +HtmlUnit APIs. In this section, we use additional abstractions within the Selenium +https://docs.seleniumhq.org/projects/webdriver/[WebDriver] to make things even easier. + +[[spring-mvc-test-server-htmlunit-webdriver-why]] +==== Why WebDriver and MockMvc? + +We can already use HtmlUnit and MockMvc, so why would we want to use WebDriver? The +Selenium WebDriver provides a very elegant API that lets us easily organize our code. To +better show how it works, we explore an example in this section. + +NOTE: Despite being a part of https://docs.seleniumhq.org/[Selenium], WebDriver does not +require a Selenium Server to run your tests. + +Suppose we need to ensure that a message is created properly. The tests involve finding +the HTML form input elements, filling them out, and making various assertions. + +This approach results in numerous separate tests because we want to test error conditions +as well. For example, we want to ensure that we get an error if we fill out only part of +the form. If we fill out the entire form, the newly created message should be displayed +afterwards. + +If one of the fields were named "`summary`", we might have something that resembles the +following repeated in multiple places within our tests: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary"); + summaryInput.setValueAttribute(summary); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val summaryInput = currentPage.getHtmlElementById("summary") + summaryInput.setValueAttribute(summary) +---- + +So what happens if we change the `id` to `smmry`? Doing so would force us to update all +of our tests to incorporate this change. This violates the DRY principle, so we should +ideally extract this code into its own method, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) { + setSummary(currentPage, summary); + // ... + } + + public void setSummary(HtmlPage currentPage, String summary) { + HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary"); + summaryInput.setValueAttribute(summary); + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{ + setSummary(currentPage, summary); + // ... + } + + fun setSummary(currentPage:HtmlPage , summary: String) { + val summaryInput = currentPage.getHtmlElementById("summary") + summaryInput.setValueAttribute(summary) + } +---- + +Doing so ensures that we do not have to update all of our tests if we change the UI. + +We might even take this a step further and place this logic within an `Object` that +represents the `HtmlPage` we are currently on, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + public class CreateMessagePage { + + final HtmlPage currentPage; + + final HtmlTextInput summaryInput; + + final HtmlSubmitInput submit; + + public CreateMessagePage(HtmlPage currentPage) { + this.currentPage = currentPage; + this.summaryInput = currentPage.getHtmlElementById("summary"); + this.submit = currentPage.getHtmlElementById("submit"); + } + + public T createMessage(String summary, String text) throws Exception { + setSummary(summary); + + HtmlPage result = submit.click(); + boolean error = CreateMessagePage.at(result); + + return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result)); + } + + public void setSummary(String summary) throws Exception { + summaryInput.setValueAttribute(summary); + } + + public static boolean at(HtmlPage page) { + return "Create Message".equals(page.getTitleText()); + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class CreateMessagePage(private val currentPage: HtmlPage) { + + val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary") + + val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit") + + fun createMessage(summary: String, text: String): T { + setSummary(summary) + + val result = submit.click() + val error = at(result) + + return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T + } + + fun setSummary(summary: String) { + summaryInput.setValueAttribute(summary) + } + + fun at(page: HtmlPage): Boolean { + return "Create Message" == page.getTitleText() + } + } +} +---- + +Formerly, this pattern was known as the +https://github.com/SeleniumHQ/selenium/wiki/PageObjects[Page Object Pattern]. While we +can certainly do this with HtmlUnit, WebDriver provides some tools that we explore in the +following sections to make this pattern much easier to implement. + +[[spring-mvc-test-server-htmlunit-webdriver-setup]] +==== MockMvc and WebDriver Setup + +To use Selenium WebDriver with the Spring MVC Test framework, make sure that your project +includes a test dependency on `org.seleniumhq.selenium:selenium-htmlunit-driver`. + +We can easily create a Selenium WebDriver that integrates with MockMvc by using the +`MockMvcHtmlUnitDriverBuilder` as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + WebDriver driver; + + @BeforeEach + void setup(WebApplicationContext context) { + driver = MockMvcHtmlUnitDriverBuilder + .webAppContextSetup(context) + .build(); + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + lateinit var driver: WebDriver + + @BeforeEach + fun setup(context: WebApplicationContext) { + driver = MockMvcHtmlUnitDriverBuilder + .webAppContextSetup(context) + .build() + } +---- + +NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced +usage, see <>. + +The preceding example ensures that any URL that references `localhost` as the server is +directed to our `MockMvc` instance without the need for a real HTTP connection. Any other +URL is requested by using a network connection, as normal. This lets us easily test the +use of CDNs. + +[[spring-mvc-test-server-htmlunit-webdriver-usage]] +==== MockMvc and WebDriver Usage + +Now we can use WebDriver as we normally would but without the need to deploy our +application to a Servlet container. For example, we can request the view to create a +message with the following: + +-- +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + CreateMessagePage page = CreateMessagePage.to(driver); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val page = CreateMessagePage.to(driver) +---- +-- + +We can then fill out the form and submit it to create a message, as follows: + +-- +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + ViewMessagePage viewMessagePage = + page.createMessage(ViewMessagePage.class, expectedSummary, expectedText); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val viewMessagePage = + page.createMessage(ViewMessagePage::class, expectedSummary, expectedText) +---- +-- + +This improves on the design of our <> +by leveraging the Page Object Pattern. As we mentioned in +<>, we can use the Page Object Pattern +with HtmlUnit, but it is much easier with WebDriver. Consider the following +`CreateMessagePage` implementation: + +-- +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + public class CreateMessagePage extends AbstractPage { // <1> + + // <2> + private WebElement summary; + private WebElement text; + + @FindBy(css = "input[type=submit]") // <3> + private WebElement submit; + + public CreateMessagePage(WebDriver driver) { + super(driver); + } + + public T createMessage(Class resultPage, String summary, String details) { + this.summary.sendKeys(summary); + this.text.sendKeys(details); + this.submit.click(); + return PageFactory.initElements(driver, resultPage); + } + + public static CreateMessagePage to(WebDriver driver) { + driver.get("http://localhost:9990/mail/messages/form"); + return PageFactory.initElements(driver, CreateMessagePage.class); + } + } +---- +<1> `CreateMessagePage` extends the `AbstractPage`. We do not go over the details of +`AbstractPage`, but, in summary, it contains common functionality for all of our pages. +For example, if our application has a navigational bar, global error messages, and other +features, we can place this logic in a shared location. +<2> We have a member variable for each of the parts of the HTML page in which we are +interested. These are of type `WebElement`. WebDriver's +https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a +lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving +each `WebElement`. The +https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class)`] +method automatically resolves each `WebElement` by using the field name and looking it up +by the `id` or `name` of the element within the HTML page. +<3> We can use the +https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation] +to override the default lookup behavior. Our example shows how to use the `@FindBy` +annotation to look up our submit button with a `css` selector (`input[type=submit]`). + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { // <1> + + // <2> + private lateinit var summary: WebElement + private lateinit var text: WebElement + + @FindBy(css = "input[type=submit]") // <3> + private lateinit var submit: WebElement + + fun createMessage(resultPage: Class, summary: String, details: String): T { + this.summary.sendKeys(summary) + text.sendKeys(details) + submit.click() + return PageFactory.initElements(driver, resultPage) + } + companion object { + fun to(driver: WebDriver): CreateMessagePage { + driver.get("http://localhost:9990/mail/messages/form") + return PageFactory.initElements(driver, CreateMessagePage::class.java) + } + } + } +---- +<1> `CreateMessagePage` extends the `AbstractPage`. We do not go over the details of +`AbstractPage`, but, in summary, it contains common functionality for all of our pages. +For example, if our application has a navigational bar, global error messages, and other +features, we can place this logic in a shared location. +<2> We have a member variable for each of the parts of the HTML page in which we are +interested. These are of type `WebElement`. WebDriver's +https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a +lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving +each `WebElement`. The +https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class)`] +method automatically resolves each `WebElement` by using the field name and looking it up +by the `id` or `name` of the element within the HTML page. +<3> We can use the +https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation] +to override the default lookup behavior. Our example shows how to use the `@FindBy` +annotation to look up our submit button with a `css` selector (*input[type=submit]*). +-- + +Finally, we can verify that a new message was created successfully. The following +assertions use the https://assertj.github.io/doc/[AssertJ] assertion library: + +-- +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage); + assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message"); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + assertThat(viewMessagePage.message).isEqualTo(expectedMessage) + assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message") +---- +-- + +We can see that our `ViewMessagePage` lets us interact with our custom domain model. For +example, it exposes a method that returns a `Message` object: + +-- +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + public Message getMessage() throws ParseException { + Message message = new Message(); + message.setId(getId()); + message.setCreated(getCreated()); + message.setSummary(getSummary()); + message.setText(getText()); + return message; + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun getMessage() = Message(getId(), getCreated(), getSummary(), getText()) +---- +-- + +We can then use the rich domain objects in our assertions. + +Lastly, we must not forget to close the `WebDriver` instance when the test is complete, +as follows: + +-- +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @AfterEach + void destroy() { + if (driver != null) { + driver.close(); + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @AfterEach + fun destroy() { + if (driver != null) { + driver.close() + } + } +---- +-- + +For additional information on using WebDriver, see the Selenium +https://github.com/SeleniumHQ/selenium/wiki/Getting-Started[WebDriver documentation]. + +[[spring-mvc-test-server-htmlunit-webdriver-advanced-builder]] +==== Advanced `MockMvcHtmlUnitDriverBuilder` + +In the examples so far, we have used `MockMvcHtmlUnitDriverBuilder` in the simplest way +possible, by building a `WebDriver` based on the `WebApplicationContext` loaded for us by +the Spring TestContext Framework. This approach is repeated here, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + WebDriver driver; + + @BeforeEach + void setup(WebApplicationContext context) { + driver = MockMvcHtmlUnitDriverBuilder + .webAppContextSetup(context) + .build(); + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + lateinit var driver: WebDriver + + @BeforeEach + fun setup(context: WebApplicationContext) { + driver = MockMvcHtmlUnitDriverBuilder + .webAppContextSetup(context) + .build() + } +---- + +We can also specify additional configuration options, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + WebDriver driver; + + @BeforeEach + void setup() { + driver = MockMvcHtmlUnitDriverBuilder + // demonstrates applying a MockMvcConfigurer (Spring Security) + .webAppContextSetup(context, springSecurity()) + // for illustration only - defaults to "" + .contextPath("") + // By default MockMvc is used for localhost only; + // the following will use MockMvc for example.com and example.org as well + .useMockMvcForHosts("example.com","example.org") + .build(); + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + lateinit var driver: WebDriver + + @BeforeEach + fun setup() { + driver = MockMvcHtmlUnitDriverBuilder + // demonstrates applying a MockMvcConfigurer (Spring Security) + .webAppContextSetup(context, springSecurity()) + // for illustration only - defaults to "" + .contextPath("") + // By default MockMvc is used for localhost only; + // the following will use MockMvc for example.com and example.org as well + .useMockMvcForHosts("example.com","example.org") + .build() + } +---- + +As an alternative, we can perform the exact same setup by configuring the `MockMvc` +instance separately and supplying it to the `MockMvcHtmlUnitDriverBuilder`, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + MockMvc mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + + driver = MockMvcHtmlUnitDriverBuilder + .mockMvcSetup(mockMvc) + // for illustration only - defaults to "" + .contextPath("") + // By default MockMvc is used for localhost only; + // the following will use MockMvc for example.com and example.org as well + .useMockMvcForHosts("example.com","example.org") + .build(); +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed +---- + +This is more verbose, but, by building the `WebDriver` with a `MockMvc` instance, we have +the full power of MockMvc at our fingertips. + +TIP: For additional information on creating a `MockMvc` instance, see +<>. + +[[spring-mvc-test-server-htmlunit-geb]] +=== MockMvc and Geb + +In the previous section, we saw how to use MockMvc with WebDriver. In this section, we +use https://www.gebish.org/[Geb] to make our tests even Groovy-er. + +[[spring-mvc-test-server-htmlunit-geb-why]] +==== Why Geb and MockMvc? + +Geb is backed by WebDriver, so it offers many of the +<> that we get from +WebDriver. However, Geb makes things even easier by taking care of some of the +boilerplate code for us. + +[[spring-mvc-test-server-htmlunit-geb-setup]] +==== MockMvc and Geb Setup + +We can easily initialize a Geb `Browser` with a Selenium WebDriver that uses MockMvc, as +follows: + +[source,groovy] +---- +def setup() { + browser.driver = MockMvcHtmlUnitDriverBuilder + .webAppContextSetup(context) + .build() +} +---- + +NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced +usage, see <>. + +This ensures that any URL referencing `localhost` as the server is directed to our +`MockMvc` instance without the need for a real HTTP connection. Any other URL is +requested by using a network connection as normal. This lets us easily test the use of +CDNs. + +[[spring-mvc-test-server-htmlunit-geb-usage]] +==== MockMvc and Geb Usage + +Now we can use Geb as we normally would but without the need to deploy our application to +a Servlet container. For example, we can request the view to create a message with the +following: + +[source,groovy] +---- +to CreateMessagePage +---- + +We can then fill out the form and submit it to create a message, as follows: + +[source,groovy] +---- +when: +form.summary = expectedSummary +form.text = expectedMessage +submit.click(ViewMessagePage) +---- + +Any unrecognized method calls or property accesses or references that are not found are +forwarded to the current page object. This removes a lot of the boilerplate code we +needed when using WebDriver directly. + +As with direct WebDriver usage, this improves on the design of our +<> by using the Page Object +Pattern. As mentioned previously, we can use the Page Object Pattern with HtmlUnit and +WebDriver, but it is even easier with Geb. Consider our new Groovy-based +`CreateMessagePage` implementation: + +[source,groovy] +---- +class CreateMessagePage extends Page { + static url = 'messages/form' + static at = { assert title == 'Messages : Create'; true } + static content = { + submit { $('input[type=submit]') } + form { $('form') } + errors(required:false) { $('label.error, .alert-error')?.text() } + } +} +---- + +Our `CreateMessagePage` extends `Page`. We do not go over the details of `Page`, but, in +summary, it contains common functionality for all of our pages. We define a URL in which +this page can be found. This lets us navigate to the page, as follows: + +[source,groovy] +---- +to CreateMessagePage +---- + +We also have an `at` closure that determines if we are at the specified page. It should +return `true` if we are on the correct page. This is why we can assert that we are on the +correct page, as follows: + +[source,groovy] +---- +then: +at CreateMessagePage +errors.contains('This field is required.') +---- + +NOTE: We use an assertion in the closure so that we can determine where things went wrong +if we were at the wrong page. + +Next, we create a `content` closure that specifies all the areas of interest within the +page. We can use a +https://www.gebish.org/manual/current/#the-jquery-ish-navigator-api[jQuery-ish Navigator +API] to select the content in which we are interested. + +Finally, we can verify that a new message was created successfully, as follows: + +[source,groovy] +---- +then: +at ViewMessagePage +success == 'Successfully created a new message' +id +date +summary == expectedSummary +message == expectedMessage +---- + +For further details on how to get the most out of Geb, see +https://www.gebish.org/manual/current/[The Book of Geb] user's manual. diff --git a/framework-docs/src/docs/asciidoc/testing/testcontext-framework.adoc b/framework-docs/src/docs/asciidoc/testing/testcontext-framework.adoc new file mode 100644 index 000000000000..ed8e029e3d0f --- /dev/null +++ b/framework-docs/src/docs/asciidoc/testing/testcontext-framework.adoc @@ -0,0 +1,4723 @@ +[[testcontext-framework]] += Spring TestContext Framework + +The Spring TestContext Framework (located in the `org.springframework.test.context` +package) provides generic, annotation-driven unit and integration testing support that is +agnostic of the testing framework in use. The TestContext framework also places a great +deal of importance on convention over configuration, with reasonable defaults that you +can override through annotation-based configuration. + +In addition to generic testing infrastructure, the TestContext framework provides +explicit support for JUnit 4, JUnit Jupiter (AKA JUnit 5), and TestNG. For JUnit 4 and +TestNG, Spring provides `abstract` support classes. Furthermore, Spring provides a custom +JUnit `Runner` and custom JUnit `Rules` for JUnit 4 and a custom `Extension` for JUnit +Jupiter that let you write so-called POJO test classes. POJO test classes are not +required to extend a particular class hierarchy, such as the `abstract` support classes. + +The following section provides an overview of the internals of the TestContext framework. +If you are interested only in using the framework and are not interested in extending it +with your own custom listeners or custom loaders, feel free to go directly to the +configuration (<>, +<>, <>), <>, and +<> sections. + + +[[testcontext-key-abstractions]] +== Key Abstractions + +The core of the framework consists of the `TestContextManager` class and the +`TestContext`, `TestExecutionListener`, and `SmartContextLoader` interfaces. A +`TestContextManager` is created for each test class (for example, for the execution of +all test methods within a single test class in JUnit Jupiter). The `TestContextManager`, +in turn, manages a `TestContext` that holds the context of the current test. The +`TestContextManager` also updates the state of the `TestContext` as the test progresses +and delegates to `TestExecutionListener` implementations, which instrument the actual +test execution by providing dependency injection, managing transactions, and so on. A +`SmartContextLoader` is responsible for loading an `ApplicationContext` for a given test +class. See the {api-spring-framework}/test/context/package-summary.html[javadoc] and the +Spring test suite for further information and examples of various implementations. + +=== `TestContext` + +`TestContext` encapsulates the context in which a test is run (agnostic of the +actual testing framework in use) and provides context management and caching support for +the test instance for which it is responsible. The `TestContext` also delegates to a +`SmartContextLoader` to load an `ApplicationContext` if requested. + +=== `TestContextManager` + +`TestContextManager` is the main entry point into the Spring TestContext Framework and is +responsible for managing a single `TestContext` and signaling events to each registered +`TestExecutionListener` at well-defined test execution points: + +* Prior to any "`before class`" or "`before all`" methods of a particular testing framework. +* Test instance post-processing. +* Prior to any "`before`" or "`before each`" methods of a particular testing framework. +* Immediately before execution of the test method but after test setup. +* Immediately after execution of the test method but before test tear down. +* After any "`after`" or "`after each`" methods of a particular testing framework. +* After any "`after class`" or "`after all`" methods of a particular testing framework. + +=== `TestExecutionListener` + +`TestExecutionListener` defines the API for reacting to test-execution events published by +the `TestContextManager` with which the listener is registered. See <>. + +=== Context Loaders + +`ContextLoader` is a strategy interface for loading an `ApplicationContext` for an +integration test managed by the Spring TestContext Framework. You should implement +`SmartContextLoader` instead of this interface to provide support for component classes, +active bean definition profiles, test property sources, context hierarchies, and +`WebApplicationContext` support. + +`SmartContextLoader` is an extension of the `ContextLoader` interface that supersedes the +original minimal `ContextLoader` SPI. Specifically, a `SmartContextLoader` can choose to +process resource locations, component classes, or context initializers. Furthermore, a +`SmartContextLoader` can set active bean definition profiles and test property sources in +the context that it loads. + +Spring provides the following implementations: + +* `DelegatingSmartContextLoader`: One of two default loaders, it delegates internally to + an `AnnotationConfigContextLoader`, a `GenericXmlContextLoader`, or a + `GenericGroovyXmlContextLoader`, depending either on the configuration declared for the + test class or on the presence of default locations or default configuration classes. + Groovy support is enabled only if Groovy is on the classpath. +* `WebDelegatingSmartContextLoader`: One of two default loaders, it delegates internally + to an `AnnotationConfigWebContextLoader`, a `GenericXmlWebContextLoader`, or a + `GenericGroovyXmlWebContextLoader`, depending either on the configuration declared for + the test class or on the presence of default locations or default configuration + classes. A web `ContextLoader` is used only if `@WebAppConfiguration` is present on the + test class. Groovy support is enabled only if Groovy is on the classpath. +* `AnnotationConfigContextLoader`: Loads a standard `ApplicationContext` from component + classes. +* `AnnotationConfigWebContextLoader`: Loads a `WebApplicationContext` from component + classes. +* `GenericGroovyXmlContextLoader`: Loads a standard `ApplicationContext` from resource + locations that are either Groovy scripts or XML configuration files. +* `GenericGroovyXmlWebContextLoader`: Loads a `WebApplicationContext` from resource + locations that are either Groovy scripts or XML configuration files. +* `GenericXmlContextLoader`: Loads a standard `ApplicationContext` from XML resource + locations. +* `GenericXmlWebContextLoader`: Loads a `WebApplicationContext` from XML resource + locations. + + +[[testcontext-bootstrapping]] +== Bootstrapping the TestContext Framework + +The default configuration for the internals of the Spring TestContext Framework is +sufficient for all common use cases. However, there are times when a development team or +third party framework would like to change the default `ContextLoader`, implement a +custom `TestContext` or `ContextCache`, augment the default sets of +`ContextCustomizerFactory` and `TestExecutionListener` implementations, and so on. For +such low-level control over how the TestContext framework operates, Spring provides a +bootstrapping strategy. + +`TestContextBootstrapper` defines the SPI for bootstrapping the TestContext framework. A +`TestContextBootstrapper` is used by the `TestContextManager` to load the +`TestExecutionListener` implementations for the current test and to build the +`TestContext` that it manages. You can configure a custom bootstrapping strategy for a +test class (or test class hierarchy) by using `@BootstrapWith`, either directly or as a +meta-annotation. If a bootstrapper is not explicitly configured by using +`@BootstrapWith`, either the `DefaultTestContextBootstrapper` or the +`WebTestContextBootstrapper` is used, depending on the presence of `@WebAppConfiguration`. + +Since the `TestContextBootstrapper` SPI is likely to change in the future (to accommodate +new requirements), we strongly encourage implementers not to implement this interface +directly but rather to extend `AbstractTestContextBootstrapper` or one of its concrete +subclasses instead. + + +[[testcontext-tel-config]] +== `TestExecutionListener` Configuration + +Spring provides the following `TestExecutionListener` implementations that are registered +by default, exactly in the following order: + +* `ServletTestExecutionListener`: Configures Servlet API mocks for a + `WebApplicationContext`. +* `DirtiesContextBeforeModesTestExecutionListener`: Handles the `@DirtiesContext` + annotation for "`before`" modes. +* `ApplicationEventsTestExecutionListener`: Provides support for + <>. +* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test + instance. +* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for + "`after`" modes. +* `TransactionalTestExecutionListener`: Provides transactional test execution with + default rollback semantics. +* `SqlScriptsTestExecutionListener`: Runs SQL scripts configured by using the `@Sql` + annotation. +* `EventPublishingTestExecutionListener`: Publishes test execution events to the test's + `ApplicationContext` (see <>). + +[[testcontext-tel-config-registering-tels]] +=== Registering `TestExecutionListener` Implementations + +You can register `TestExecutionListener` implementations explicitly for a test class, its +subclasses, and its nested classes by using the `@TestExecutionListeners` annotation. See +<> and the javadoc for +{api-spring-framework}/test/context/TestExecutionListeners.html[`@TestExecutionListeners`] +for details and examples. + +.Switching to default `TestExecutionListener` implementations +[NOTE] +==== +If you extend a class that is annotated with `@TestExecutionListeners` and you need to +switch to using the default set of listeners, you can annotate your class with the +following. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // Switch to default listeners + @TestExecutionListeners( + listeners = {}, + inheritListeners = false, + mergeMode = MERGE_WITH_DEFAULTS) + class MyTest extends BaseTest { + // class body... + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Switch to default listeners + @TestExecutionListeners( + listeners = [], + inheritListeners = false, + mergeMode = MERGE_WITH_DEFAULTS) + class MyTest : BaseTest { + // class body... + } +---- +==== + +[[testcontext-tel-config-automatic-discovery]] +=== Automatic Discovery of Default `TestExecutionListener` Implementations + +Registering `TestExecutionListener` implementations by using `@TestExecutionListeners` is +suitable for custom listeners that are used in limited testing scenarios. However, it can +become cumbersome if a custom listener needs to be used across an entire test suite. This +issue is addressed through support for automatic discovery of default +`TestExecutionListener` implementations through the `SpringFactoriesLoader` mechanism. + +Specifically, the `spring-test` module declares all core default `TestExecutionListener` +implementations under the `org.springframework.test.context.TestExecutionListener` key in +its `META-INF/spring.factories` properties file. Third-party frameworks and developers +can contribute their own `TestExecutionListener` implementations to the list of default +listeners in the same manner through their own `META-INF/spring.factories` properties +file. + +[[testcontext-tel-config-ordering]] +=== Ordering `TestExecutionListener` Implementations + +When the TestContext framework discovers default `TestExecutionListener` implementations +through the <> +`SpringFactoriesLoader` mechanism, the instantiated listeners are sorted by using +Spring's `AnnotationAwareOrderComparator`, which honors Spring's `Ordered` interface and +`@Order` annotation for ordering. `AbstractTestExecutionListener` and all default +`TestExecutionListener` implementations provided by Spring implement `Ordered` with +appropriate values. Third-party frameworks and developers should therefore make sure that +their default `TestExecutionListener` implementations are registered in the proper order +by implementing `Ordered` or declaring `@Order`. See the javadoc for the `getOrder()` +methods of the core default `TestExecutionListener` implementations for details on what +values are assigned to each core listener. + +[[testcontext-tel-config-merging]] +=== Merging `TestExecutionListener` Implementations + +If a custom `TestExecutionListener` is registered via `@TestExecutionListeners`, the +default listeners are not registered. In most common testing scenarios, this effectively +forces the developer to manually declare all default listeners in addition to any custom +listeners. The following listing demonstrates this style of configuration: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @TestExecutionListeners({ + MyCustomTestExecutionListener.class, + ServletTestExecutionListener.class, + DirtiesContextBeforeModesTestExecutionListener.class, + DependencyInjectionTestExecutionListener.class, + DirtiesContextTestExecutionListener.class, + TransactionalTestExecutionListener.class, + SqlScriptsTestExecutionListener.class + }) + class MyTest { + // class body... + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @TestExecutionListeners( + MyCustomTestExecutionListener::class, + ServletTestExecutionListener::class, + DirtiesContextBeforeModesTestExecutionListener::class, + DependencyInjectionTestExecutionListener::class, + DirtiesContextTestExecutionListener::class, + TransactionalTestExecutionListener::class, + SqlScriptsTestExecutionListener::class + ) + class MyTest { + // class body... + } +---- + +The challenge with this approach is that it requires that the developer know exactly +which listeners are registered by default. Moreover, the set of default listeners can +change from release to release -- for example, `SqlScriptsTestExecutionListener` was +introduced in Spring Framework 4.1, and `DirtiesContextBeforeModesTestExecutionListener` +was introduced in Spring Framework 4.2. Furthermore, third-party frameworks like Spring +Boot and Spring Security register their own default `TestExecutionListener` +implementations by using the aforementioned <>. + +To avoid having to be aware of and re-declare all default listeners, you can set the +`mergeMode` attribute of `@TestExecutionListeners` to `MergeMode.MERGE_WITH_DEFAULTS`. +`MERGE_WITH_DEFAULTS` indicates that locally declared listeners should be merged with the +default listeners. The merging algorithm ensures that duplicates are removed from the +list and that the resulting set of merged listeners is sorted according to the semantics +of `AnnotationAwareOrderComparator`, as described in <>. +If a listener implements `Ordered` or is annotated with `@Order`, it can influence the +position in which it is merged with the defaults. Otherwise, locally declared listeners +are appended to the list of default listeners when merged. + +For example, if the `MyCustomTestExecutionListener` class in the previous example +configures its `order` value (for example, `500`) to be less than the order of the +`ServletTestExecutionListener` (which happens to be `1000`), the +`MyCustomTestExecutionListener` can then be automatically merged with the list of +defaults in front of the `ServletTestExecutionListener`, and the previous example could +be replaced with the following: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @TestExecutionListeners( + listeners = MyCustomTestExecutionListener.class, + mergeMode = MERGE_WITH_DEFAULTS + ) + class MyTest { + // class body... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @TestExecutionListeners( + listeners = [MyCustomTestExecutionListener::class], + mergeMode = MERGE_WITH_DEFAULTS + ) + class MyTest { + // class body... + } +---- + +[[testcontext-application-events]] +== Application Events + +Since Spring Framework 5.3.3, the TestContext framework provides support for recording +<> published in the +`ApplicationContext` so that assertions can be performed against those events within +tests. All events published during the execution of a single test are made available via +the `ApplicationEvents` API which allows you to process the events as a +`java.util.Stream`. + +To use `ApplicationEvents` in your tests, do the following. + +* Ensure that your test class is annotated or meta-annotated with + <>. +* Ensure that the `ApplicationEventsTestExecutionListener` is registered. Note, however, + that `ApplicationEventsTestExecutionListener` is registered by default and only needs + to be manually registered if you have custom configuration via + `@TestExecutionListeners` that does not include the default listeners. +* Annotate a field of type `ApplicationEvents` with `@Autowired` and use that instance of + `ApplicationEvents` in your test and lifecycle methods (such as `@BeforeEach` and + `@AfterEach` methods in JUnit Jupiter). +** When using the <>, you may declare a method + parameter of type `ApplicationEvents` in a test or lifecycle method as an alternative + to an `@Autowired` field in the test class. + +The following test class uses the `SpringExtension` for JUnit Jupiter and +https://assertj.github.io/doc/[AssertJ] to assert the types of application events +published while invoking a method in a Spring-managed component: + +// Don't use "quotes" in the "subs" section because of the asterisks in /* ... */ +[source,java,indent=0,subs="verbatim",role="primary"] +.Java +---- + @SpringJUnitConfig(/* ... */) + @RecordApplicationEvents // <1> + class OrderServiceTests { + + @Autowired + OrderService orderService; + + @Autowired + ApplicationEvents events; // <2> + + @Test + void submitOrder() { + // Invoke method in OrderService that publishes an event + orderService.submitOrder(new Order(/* ... */)); + // Verify that an OrderSubmitted event was published + long numEvents = events.stream(OrderSubmitted.class).count(); // <3> + assertThat(numEvents).isEqualTo(1); + } + } +---- +<1> Annotate the test class with `@RecordApplicationEvents`. +<2> Inject the `ApplicationEvents` instance for the current test. +<3> Use the `ApplicationEvents` API to count how many `OrderSubmitted` events were published. + +// Don't use "quotes" in the "subs" section because of the asterisks in /* ... */ +[source,kotlin,indent=0,subs="verbatim",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(/* ... */) + @RecordApplicationEvents // <1> + class OrderServiceTests { + + @Autowired + lateinit var orderService: OrderService + + @Autowired + lateinit var events: ApplicationEvents // <2> + + @Test + fun submitOrder() { + // Invoke method in OrderService that publishes an event + orderService.submitOrder(Order(/* ... */)) + // Verify that an OrderSubmitted event was published + val numEvents = events.stream(OrderSubmitted::class).count() // <3> + assertThat(numEvents).isEqualTo(1) + } + } +---- +<1> Annotate the test class with `@RecordApplicationEvents`. +<2> Inject the `ApplicationEvents` instance for the current test. +<3> Use the `ApplicationEvents` API to count how many `OrderSubmitted` events were published. + +See the +{api-spring-framework}/test/context/event/ApplicationEvents.html[`ApplicationEvents` +javadoc] for further details regarding the `ApplicationEvents` API. + +[[testcontext-test-execution-events]] +== Test Execution Events + +The `EventPublishingTestExecutionListener` introduced in Spring Framework 5.2 offers an +alternative approach to implementing a custom `TestExecutionListener`. Components in the +test's `ApplicationContext` can listen to the following events published by the +`EventPublishingTestExecutionListener`, each of which corresponds to a method in the +`TestExecutionListener` API. + +* `BeforeTestClassEvent` +* `PrepareTestInstanceEvent` +* `BeforeTestMethodEvent` +* `BeforeTestExecutionEvent` +* `AfterTestExecutionEvent` +* `AfterTestMethodEvent` +* `AfterTestClassEvent` + +These events may be consumed for various reasons, such as resetting mock beans or tracing +test execution. One advantage of consuming test execution events rather than implementing +a custom `TestExecutionListener` is that test execution events may be consumed by any +Spring bean registered in the test `ApplicationContext`, and such beans may benefit +directly from dependency injection and other features of the `ApplicationContext`. In +contrast, a `TestExecutionListener` is not a bean in the `ApplicationContext`. + +[NOTE] +==== +The `EventPublishingTestExecutionListener` is registered by default; however, it only +publishes events if the `ApplicationContext` has _already been loaded_. This prevents the +`ApplicationContext` from being loaded unnecessarily or too early. + +Consequently, a `BeforeTestClassEvent` will not be published until after the +`ApplicationContext` has been loaded by another `TestExecutionListener`. For example, with +the default set of `TestExecutionListener` implementations registered, a +`BeforeTestClassEvent` will not be published for the first test class that uses a +particular test `ApplicationContext`, but a `BeforeTestClassEvent` _will_ be published for +any subsequent test class in the same test suite that uses the same test +`ApplicationContext` since the context will already have been loaded when subsequent test +classes run (as long as the context has not been removed from the `ContextCache` via +`@DirtiesContext` or the max-size eviction policy). + +If you wish to ensure that a `BeforeTestClassEvent` is always published for every test +class, you need to register a `TestExecutionListener` that loads the `ApplicationContext` +in the `beforeTestClass` callback, and that `TestExecutionListener` must be registered +_before_ the `EventPublishingTestExecutionListener`. + +Similarly, if `@DirtiesContext` is used to remove the `ApplicationContext` from the +context cache after the last test method in a given test class, the `AfterTestClassEvent` +will not be published for that test class. +==== + +In order to listen to test execution events, a Spring bean may choose to implement the +`org.springframework.context.ApplicationListener` interface. Alternatively, listener +methods can be annotated with `@EventListener` and configured to listen to one of the +particular event types listed above (see +<>). +Due to the popularity of this approach, Spring provides the following dedicated +`@EventListener` annotations to simplify registration of test execution event listeners. +These annotations reside in the `org.springframework.test.context.event.annotation` +package. + +* `@BeforeTestClass` +* `@PrepareTestInstance` +* `@BeforeTestMethod` +* `@BeforeTestExecution` +* `@AfterTestExecution` +* `@AfterTestMethod` +* `@AfterTestClass` + +[[testcontext-test-execution-events-exception-handling]] +=== Exception Handling + +By default, if a test execution event listener throws an exception while consuming an +event, that exception will propagate to the underlying testing framework in use (such as +JUnit or TestNG). For example, if the consumption of a `BeforeTestMethodEvent` results in +an exception, the corresponding test method will fail as a result of the exception. In +contrast, if an asynchronous test execution event listener throws an exception, the +exception will not propagate to the underlying testing framework. For further details on +asynchronous exception handling, consult the class-level javadoc for `@EventListener`. + +[[testcontext-test-execution-events-async]] +=== Asynchronous Listeners + +If you want a particular test execution event listener to process events asynchronously, +you can use Spring's <>. For further details, consult the class-level javadoc for +`@EventListener`. + + +[[testcontext-ctx-management]] +== Context Management + +Each `TestContext` provides context management and caching support for the test instance +for which it is responsible. Test instances do not automatically receive access to the +configured `ApplicationContext`. However, if a test class implements the +`ApplicationContextAware` interface, a reference to the `ApplicationContext` is supplied +to the test instance. Note that `AbstractJUnit4SpringContextTests` and +`AbstractTestNGSpringContextTests` implement `ApplicationContextAware` and, therefore, +provide access to the `ApplicationContext` automatically. + +.@Autowired ApplicationContext +[TIP] +===== +As an alternative to implementing the `ApplicationContextAware` interface, you can inject +the application context for your test class through the `@Autowired` annotation on either +a field or setter method, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig + class MyTest { + + @Autowired // <1> + ApplicationContext applicationContext; + + // class body... + } +---- +<1> Injecting the `ApplicationContext`. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig + class MyTest { + + @Autowired // <1> + lateinit var applicationContext: ApplicationContext + + // class body... + } +---- +<1> Injecting the `ApplicationContext`. + + +Similarly, if your test is configured to load a `WebApplicationContext`, you can inject +the web application context into your test, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitWebConfig // <1> + class MyWebAppTest { + + @Autowired // <2> + WebApplicationContext wac; + + // class body... + } +---- +<1> Configuring the `WebApplicationContext`. +<2> Injecting the `WebApplicationContext`. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitWebConfig // <1> + class MyWebAppTest { + + @Autowired // <2> + lateinit var wac: WebApplicationContext + // class body... + } +---- +<1> Configuring the `WebApplicationContext`. +<2> Injecting the `WebApplicationContext`. + + +Dependency injection by using `@Autowired` is provided by the +`DependencyInjectionTestExecutionListener`, which is configured by default +(see <>). +===== + +Test classes that use the TestContext framework do not need to extend any particular +class or implement a specific interface to configure their application context. Instead, +configuration is achieved by declaring the `@ContextConfiguration` annotation at the +class level. If your test class does not explicitly declare application context resource +locations or component classes, the configured `ContextLoader` determines how to load a +context from a default location or default configuration classes. In addition to context +resource locations and component classes, an application context can also be configured +through application context initializers. + +The following sections explain how to use Spring's `@ContextConfiguration` annotation to +configure a test `ApplicationContext` by using XML configuration files, Groovy scripts, +component classes (typically `@Configuration` classes), or context initializers. +Alternatively, you can implement and configure your own custom `SmartContextLoader` for +advanced use cases. + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> + +[[testcontext-ctx-management-xml]] +=== Context Configuration with XML resources + +To load an `ApplicationContext` for your tests by using XML configuration files, annotate +your test class with `@ContextConfiguration` and configure the `locations` attribute with +an array that contains the resource locations of XML configuration metadata. A plain or +relative path (for example, `context.xml`) is treated as a classpath resource that is +relative to the package in which the test class is defined. A path starting with a slash +is treated as an absolute classpath location (for example, `/org/example/config.xml`). A +path that represents a resource URL (i.e., a path prefixed with `classpath:`, `file:`, +`http:`, etc.) is used _as is_. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // ApplicationContext will be loaded from "/app-config.xml" and + // "/test-config.xml" in the root of the classpath + @ContextConfiguration(locations = {"/app-config.xml", "/test-config.xml"}) // <1> + class MyTest { + // class body... + } +---- +<1> Setting the locations attribute to a list of XML files. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // ApplicationContext will be loaded from "/app-config.xml" and + // "/test-config.xml" in the root of the classpath + @ContextConfiguration(locations = ["/app-config.xml", "/test-config.xml"]) // <1> + class MyTest { + // class body... + } +---- +<1> Setting the locations attribute to a list of XML files. + + +`@ContextConfiguration` supports an alias for the `locations` attribute through the +standard Java `value` attribute. Thus, if you do not need to declare additional +attributes in `@ContextConfiguration`, you can omit the declaration of the `locations` +attribute name and declare the resource locations by using the shorthand format +demonstrated in the following example: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + @ContextConfiguration({"/app-config.xml", "/test-config.xml"}) <1> + class MyTest { + // class body... + } +---- +<1> Specifying XML files without using the `locations` attribute. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + @ContextConfiguration("/app-config.xml", "/test-config.xml") // <1> + class MyTest { + // class body... + } +---- +<1> Specifying XML files without using the `locations` attribute. + + +If you omit both the `locations` and the `value` attributes from the +`@ContextConfiguration` annotation, the TestContext framework tries to detect a default +XML resource location. Specifically, `GenericXmlContextLoader` and +`GenericXmlWebContextLoader` detect a default location based on the name of the test +class. If your class is named `com.example.MyTest`, `GenericXmlContextLoader` loads your +application context from `"classpath:com/example/MyTest-context.xml"`. The following +example shows how to do so: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // ApplicationContext will be loaded from + // "classpath:com/example/MyTest-context.xml" + @ContextConfiguration // <1> + class MyTest { + // class body... + } +---- +<1> Loading configuration from the default location. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // ApplicationContext will be loaded from + // "classpath:com/example/MyTest-context.xml" + @ContextConfiguration // <1> + class MyTest { + // class body... + } +---- +<1> Loading configuration from the default location. + + +[[testcontext-ctx-management-groovy]] +=== Context Configuration with Groovy Scripts + +To load an `ApplicationContext` for your tests by using Groovy scripts that use the +<>, you can annotate +your test class with `@ContextConfiguration` and configure the `locations` or `value` +attribute with an array that contains the resource locations of Groovy scripts. Resource +lookup semantics for Groovy scripts are the same as those described for +<>. + +.Enabling Groovy script support +TIP: Support for using Groovy scripts to load an `ApplicationContext` in the Spring +TestContext Framework is enabled automatically if Groovy is on the classpath. + +The following example shows how to specify Groovy configuration files: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // ApplicationContext will be loaded from "/AppConfig.groovy" and + // "/TestConfig.groovy" in the root of the classpath + @ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) <1> + class MyTest { + // class body... + } +---- +<1> Specifying the location of Groovy configuration files. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // ApplicationContext will be loaded from "/AppConfig.groovy" and + // "/TestConfig.groovy" in the root of the classpath + @ContextConfiguration("/AppConfig.groovy", "/TestConfig.Groovy") // <1> + class MyTest { + // class body... + } +---- +<1> Specifying the location of Groovy configuration files. + + +If you omit both the `locations` and `value` attributes from the `@ContextConfiguration` +annotation, the TestContext framework tries to detect a default Groovy script. +Specifically, `GenericGroovyXmlContextLoader` and `GenericGroovyXmlWebContextLoader` +detect a default location based on the name of the test class. If your class is named +`com.example.MyTest`, the Groovy context loader loads your application context from +`"classpath:com/example/MyTestContext.groovy"`. The following example shows how to use +the default: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // ApplicationContext will be loaded from + // "classpath:com/example/MyTestContext.groovy" + @ContextConfiguration // <1> + class MyTest { + // class body... + } +---- +<1> Loading configuration from the default location. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // ApplicationContext will be loaded from + // "classpath:com/example/MyTestContext.groovy" + @ContextConfiguration // <1> + class MyTest { + // class body... + } +---- +<1> Loading configuration from the default location. + + +.Declaring XML configuration and Groovy scripts simultaneously +[TIP] +===== +You can declare both XML configuration files and Groovy scripts simultaneously by using +the `locations` or `value` attribute of `@ContextConfiguration`. If the path to a +configured resource location ends with `.xml`, it is loaded by using an +`XmlBeanDefinitionReader`. Otherwise, it is loaded by using a +`GroovyBeanDefinitionReader`. + +The following listing shows how to combine both in an integration test: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // ApplicationContext will be loaded from + // "/app-config.xml" and "/TestConfig.groovy" + @ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" }) + class MyTest { + // class body... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // ApplicationContext will be loaded from + // "/app-config.xml" and "/TestConfig.groovy" + @ContextConfiguration("/app-config.xml", "/TestConfig.groovy") + class MyTest { + // class body... + } +---- +===== + +[[testcontext-ctx-management-javaconfig]] +=== Context Configuration with Component Classes + +To load an `ApplicationContext` for your tests by using component classes (see +<>), you can annotate your test +class with `@ContextConfiguration` and configure the `classes` attribute with an array +that contains references to component classes. The following example shows how to do so: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // ApplicationContext will be loaded from AppConfig and TestConfig + @ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) // <1> + class MyTest { + // class body... + } +---- +<1> Specifying component classes. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // ApplicationContext will be loaded from AppConfig and TestConfig + @ContextConfiguration(classes = [AppConfig::class, TestConfig::class]) // <1> + class MyTest { + // class body... + } +---- +<1> Specifying component classes. + + +[[testcontext-ctx-management-javaconfig-component-classes]] +.Component Classes +[TIP] +==== +The term "`component class`" can refer to any of the following: + +* A class annotated with `@Configuration`. +* A component (that is, a class annotated with `@Component`, `@Service`, `@Repository`, or other stereotype annotations). +* A JSR-330 compliant class that is annotated with `jakarta.inject` annotations. +* Any class that contains `@Bean`-methods. +* Any other class that is intended to be registered as a Spring component (i.e., a Spring + bean in the `ApplicationContext`), potentially taking advantage of automatic autowiring + of a single constructor without the use of Spring annotations. + +See the javadoc of +{api-spring-framework}/context/annotation/Configuration.html[`@Configuration`] and +{api-spring-framework}/context/annotation/Bean.html[`@Bean`] for further information +regarding the configuration and semantics of component classes, paying special attention +to the discussion of `@Bean` Lite Mode. +==== + +If you omit the `classes` attribute from the `@ContextConfiguration` annotation, the +TestContext framework tries to detect the presence of default configuration classes. +Specifically, `AnnotationConfigContextLoader` and `AnnotationConfigWebContextLoader` +detect all `static` nested classes of the test class that meet the requirements for +configuration class implementations, as specified in the +{api-spring-framework}/context/annotation/Configuration.html[`@Configuration`] javadoc. +Note that the name of the configuration class is arbitrary. In addition, a test class can +contain more than one `static` nested configuration class if desired. In the following +example, the `OrderServiceTest` class declares a `static` nested configuration class +named `Config` that is automatically used to load the `ApplicationContext` for the test +class: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig <1> + // ApplicationContext will be loaded from the static nested Config class + class OrderServiceTest { + + @Configuration + static class Config { + + // this bean will be injected into the OrderServiceTest class + @Bean + OrderService orderService() { + OrderService orderService = new OrderServiceImpl(); + // set properties, etc. + return orderService; + } + } + + @Autowired + OrderService orderService; + + @Test + void testOrderService() { + // test the orderService + } + + } +---- +<1> Loading configuration information from the nested `Config` class. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig <1> + // ApplicationContext will be loaded from the nested Config class + class OrderServiceTest { + + @Autowired + lateinit var orderService: OrderService + + @Configuration + class Config { + + // this bean will be injected into the OrderServiceTest class + @Bean + fun orderService(): OrderService { + // set properties, etc. + return OrderServiceImpl() + } + } + + @Test + fun testOrderService() { + // test the orderService + } + } +---- +<1> Loading configuration information from the nested `Config` class. + + +[[testcontext-ctx-management-mixed-config]] +=== Mixing XML, Groovy Scripts, and Component Classes + +It may sometimes be desirable to mix XML configuration files, Groovy scripts, and +component classes (typically `@Configuration` classes) to configure an +`ApplicationContext` for your tests. For example, if you use XML configuration in +production, you may decide that you want to use `@Configuration` classes to configure +specific Spring-managed components for your tests, or vice versa. + +Furthermore, some third-party frameworks (such as Spring Boot) provide first-class +support for loading an `ApplicationContext` from different types of resources +simultaneously (for example, XML configuration files, Groovy scripts, and +`@Configuration` classes). The Spring Framework, historically, has not supported this for +standard deployments. Consequently, most of the `SmartContextLoader` implementations that +the Spring Framework delivers in the `spring-test` module support only one resource type +for each test context. However, this does not mean that you cannot use both. One +exception to the general rule is that the `GenericGroovyXmlContextLoader` and +`GenericGroovyXmlWebContextLoader` support both XML configuration files and Groovy +scripts simultaneously. Furthermore, third-party frameworks may choose to support the +declaration of both `locations` and `classes` through `@ContextConfiguration`, and, with +the standard testing support in the TestContext framework, you have the following options. + +If you want to use resource locations (for example, XML or Groovy) and `@Configuration` +classes to configure your tests, you must pick one as the entry point, and that one must +include or import the other. For example, in XML or Groovy scripts, you can include +`@Configuration` classes by using component scanning or defining them as normal Spring +beans, whereas, in a `@Configuration` class, you can use `@ImportResource` to import XML +configuration files or Groovy scripts. Note that this behavior is semantically equivalent +to how you configure your application in production: In production configuration, you +define either a set of XML or Groovy resource locations or a set of `@Configuration` +classes from which your production `ApplicationContext` is loaded, but you still have the +freedom to include or import the other type of configuration. + +[[testcontext-ctx-management-initializers]] +=== Context Configuration with Context Initializers + +To configure an `ApplicationContext` for your tests by using context initializers, +annotate your test class with `@ContextConfiguration` and configure the `initializers` +attribute with an array that contains references to classes that implement +`ApplicationContextInitializer`. The declared context initializers are then used to +initialize the `ConfigurableApplicationContext` that is loaded for your tests. Note that +the concrete `ConfigurableApplicationContext` type supported by each declared initializer +must be compatible with the type of `ApplicationContext` created by the +`SmartContextLoader` in use (typically a `GenericApplicationContext`). Furthermore, the +order in which the initializers are invoked depends on whether they implement Spring's +`Ordered` interface or are annotated with Spring's `@Order` annotation or the standard +`@Priority` annotation. The following example shows how to use initializers: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // ApplicationContext will be loaded from TestConfig + // and initialized by TestAppCtxInitializer + @ContextConfiguration( + classes = TestConfig.class, + initializers = TestAppCtxInitializer.class) // <1> + class MyTest { + // class body... + } +---- +<1> Specifying configuration by using a configuration class and an initializer. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // ApplicationContext will be loaded from TestConfig + // and initialized by TestAppCtxInitializer + @ContextConfiguration( + classes = [TestConfig::class], + initializers = [TestAppCtxInitializer::class]) // <1> + class MyTest { + // class body... + } +---- +<1> Specifying configuration by using a configuration class and an initializer. + + +You can also omit the declaration of XML configuration files, Groovy scripts, or +component classes in `@ContextConfiguration` entirely and instead declare only +`ApplicationContextInitializer` classes, which are then responsible for registering beans +in the context -- for example, by programmatically loading bean definitions from XML +files or configuration classes. The following example shows how to do so: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // ApplicationContext will be initialized by EntireAppInitializer + // which presumably registers beans in the context + @ContextConfiguration(initializers = EntireAppInitializer.class) <1> + class MyTest { + // class body... + } +---- +<1> Specifying configuration by using only an initializer. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // ApplicationContext will be initialized by EntireAppInitializer + // which presumably registers beans in the context + @ContextConfiguration(initializers = [EntireAppInitializer::class]) // <1> + class MyTest { + // class body... + } +---- +<1> Specifying configuration by using only an initializer. + + +[[testcontext-ctx-management-inheritance]] +=== Context Configuration Inheritance + +`@ContextConfiguration` supports boolean `inheritLocations` and `inheritInitializers` +attributes that denote whether resource locations or component classes and context +initializers declared by superclasses should be inherited. The default value for both +flags is `true`. This means that a test class inherits the resource locations or +component classes as well as the context initializers declared by any superclasses. +Specifically, the resource locations or component classes for a test class are appended +to the list of resource locations or annotated classes declared by superclasses. +Similarly, the initializers for a given test class are added to the set of initializers +defined by test superclasses. Thus, subclasses have the option of extending the resource +locations, component classes, or context initializers. + +If the `inheritLocations` or `inheritInitializers` attribute in `@ContextConfiguration` +is set to `false`, the resource locations or component classes and the context +initializers, respectively, for the test class shadow and effectively replace the +configuration defined by superclasses. + +NOTE: As of Spring Framework 5.3, test configuration may also be inherited from enclosing +classes. See <> for details. + +In the next example, which uses XML resource locations, the `ApplicationContext` for +`ExtendedTest` is loaded from `base-config.xml` and `extended-config.xml`, in that order. +Beans defined in `extended-config.xml` can, therefore, override (that is, replace) those +defined in `base-config.xml`. The following example shows how one class can extend +another and use both its own configuration file and the superclass's configuration file: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // ApplicationContext will be loaded from "/base-config.xml" + // in the root of the classpath + @ContextConfiguration("/base-config.xml") <1> + class BaseTest { + // class body... + } + + // ApplicationContext will be loaded from "/base-config.xml" and + // "/extended-config.xml" in the root of the classpath + @ContextConfiguration("/extended-config.xml") <2> + class ExtendedTest extends BaseTest { + // class body... + } +---- +<1> Configuration file defined in the superclass. +<2> Configuration file defined in the subclass. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // ApplicationContext will be loaded from "/base-config.xml" + // in the root of the classpath + @ContextConfiguration("/base-config.xml") // <1> + open class BaseTest { + // class body... + } + + // ApplicationContext will be loaded from "/base-config.xml" and + // "/extended-config.xml" in the root of the classpath + @ContextConfiguration("/extended-config.xml") // <2> + class ExtendedTest : BaseTest() { + // class body... + } +---- +<1> Configuration file defined in the superclass. +<2> Configuration file defined in the subclass. + + +Similarly, in the next example, which uses component classes, the `ApplicationContext` +for `ExtendedTest` is loaded from the `BaseConfig` and `ExtendedConfig` classes, in that +order. Beans defined in `ExtendedConfig` can, therefore, override (that is, replace) +those defined in `BaseConfig`. The following example shows how one class can extend +another and use both its own configuration class and the superclass's configuration class: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // ApplicationContext will be loaded from BaseConfig + @SpringJUnitConfig(BaseConfig.class) // <1> + class BaseTest { + // class body... + } + + // ApplicationContext will be loaded from BaseConfig and ExtendedConfig + @SpringJUnitConfig(ExtendedConfig.class) // <2> + class ExtendedTest extends BaseTest { + // class body... + } +---- +<1> Configuration class defined in the superclass. +<2> Configuration class defined in the subclass. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // ApplicationContext will be loaded from BaseConfig + @SpringJUnitConfig(BaseConfig::class) // <1> + open class BaseTest { + // class body... + } + + // ApplicationContext will be loaded from BaseConfig and ExtendedConfig + @SpringJUnitConfig(ExtendedConfig::class) // <2> + class ExtendedTest : BaseTest() { + // class body... + } +---- +<1> Configuration class defined in the superclass. +<2> Configuration class defined in the subclass. + + +In the next example, which uses context initializers, the `ApplicationContext` for +`ExtendedTest` is initialized by using `BaseInitializer` and `ExtendedInitializer`. Note, +however, that the order in which the initializers are invoked depends on whether they +implement Spring's `Ordered` interface or are annotated with Spring's `@Order` annotation +or the standard `@Priority` annotation. The following example shows how one class can +extend another and use both its own initializer and the superclass's initializer: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // ApplicationContext will be initialized by BaseInitializer + @SpringJUnitConfig(initializers = BaseInitializer.class) // <1> + class BaseTest { + // class body... + } + + // ApplicationContext will be initialized by BaseInitializer + // and ExtendedInitializer + @SpringJUnitConfig(initializers = ExtendedInitializer.class) // <2> + class ExtendedTest extends BaseTest { + // class body... + } +---- +<1> Initializer defined in the superclass. +<2> Initializer defined in the subclass. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // ApplicationContext will be initialized by BaseInitializer + @SpringJUnitConfig(initializers = [BaseInitializer::class]) // <1> + open class BaseTest { + // class body... + } + + // ApplicationContext will be initialized by BaseInitializer + // and ExtendedInitializer + @SpringJUnitConfig(initializers = [ExtendedInitializer::class]) // <2> + class ExtendedTest : BaseTest() { + // class body... + } +---- +<1> Initializer defined in the superclass. +<2> Initializer defined in the subclass. + + +[[testcontext-ctx-management-env-profiles]] +=== Context Configuration with Environment Profiles + +The Spring Framework has first-class support for the notion of environments and profiles +(AKA "bean definition profiles"), and integration tests can be configured to activate +particular bean definition profiles for various testing scenarios. This is achieved by +annotating a test class with the `@ActiveProfiles` annotation and supplying a list of +profiles that should be activated when loading the `ApplicationContext` for the test. + +NOTE: You can use `@ActiveProfiles` with any implementation of the `SmartContextLoader` +SPI, but `@ActiveProfiles` is not supported with implementations of the older +`ContextLoader` SPI. + +Consider two examples with XML configuration and `@Configuration` classes: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // ApplicationContext will be loaded from "classpath:/app-config.xml" + @ContextConfiguration("/app-config.xml") + @ActiveProfiles("dev") + class TransferServiceTest { + + @Autowired + TransferService transferService; + + @Test + void testTransferService() { + // test the transferService + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // ApplicationContext will be loaded from "classpath:/app-config.xml" + @ContextConfiguration("/app-config.xml") + @ActiveProfiles("dev") + class TransferServiceTest { + + @Autowired + lateinit var transferService: TransferService + + @Test + fun testTransferService() { + // test the transferService + } + } +---- + +When `TransferServiceTest` is run, its `ApplicationContext` is loaded from the +`app-config.xml` configuration file in the root of the classpath. If you inspect +`app-config.xml`, you can see that the `accountRepository` bean has a dependency on a +`dataSource` bean. However, `dataSource` is not defined as a top-level bean. Instead, +`dataSource` is defined three times: in the `production` profile, in the `dev` profile, +and in the `default` profile. + +By annotating `TransferServiceTest` with `@ActiveProfiles("dev")`, we instruct the Spring +TestContext Framework to load the `ApplicationContext` with the active profiles set to +`{"dev"}`. As a result, an embedded database is created and populated with test data, and +the `accountRepository` bean is wired with a reference to the development `DataSource`. +That is likely what we want in an integration test. + +It is sometimes useful to assign beans to a `default` profile. Beans within the default +profile are included only when no other profile is specifically activated. You can use +this to define "`fallback`" beans to be used in the application's default state. For +example, you may explicitly provide a data source for `dev` and `production` profiles, +but define an in-memory data source as a default when neither of these is active. + +The following code listings demonstrate how to implement the same configuration and +integration test with `@Configuration` classes instead of XML: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Configuration + @Profile("dev") + public class StandaloneDataConfig { + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.HSQL) + .addScript("classpath:com/bank/config/sql/schema.sql") + .addScript("classpath:com/bank/config/sql/test-data.sql") + .build(); + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @Profile("dev") + class StandaloneDataConfig { + + @Bean + fun dataSource(): DataSource { + return EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.HSQL) + .addScript("classpath:com/bank/config/sql/schema.sql") + .addScript("classpath:com/bank/config/sql/test-data.sql") + .build() + } + } +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Configuration + @Profile("production") + public class JndiDataConfig { + + @Bean(destroyMethod="") + public DataSource dataSource() throws Exception { + Context ctx = new InitialContext(); + return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @Profile("production") + class JndiDataConfig { + + @Bean(destroyMethod = "") + fun dataSource(): DataSource { + val ctx = InitialContext() + return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource + } + } +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Configuration + @Profile("default") + public class DefaultDataConfig { + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.HSQL) + .addScript("classpath:com/bank/config/sql/schema.sql") + .build(); + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @Profile("default") + class DefaultDataConfig { + + @Bean + fun dataSource(): DataSource { + return EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.HSQL) + .addScript("classpath:com/bank/config/sql/schema.sql") + .build() + } + } +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Configuration + public class TransferServiceConfig { + + @Autowired DataSource dataSource; + + @Bean + public TransferService transferService() { + return new DefaultTransferService(accountRepository(), feePolicy()); + } + + @Bean + public AccountRepository accountRepository() { + return new JdbcAccountRepository(dataSource); + } + + @Bean + public FeePolicy feePolicy() { + return new ZeroFeePolicy(); + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + class TransferServiceConfig { + + @Autowired + lateinit var dataSource: DataSource + + @Bean + fun transferService(): TransferService { + return DefaultTransferService(accountRepository(), feePolicy()) + } + + @Bean + fun accountRepository(): AccountRepository { + return JdbcAccountRepository(dataSource) + } + + @Bean + fun feePolicy(): FeePolicy { + return ZeroFeePolicy() + } + } +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig({ + TransferServiceConfig.class, + StandaloneDataConfig.class, + JndiDataConfig.class, + DefaultDataConfig.class}) + @ActiveProfiles("dev") + class TransferServiceTest { + + @Autowired + TransferService transferService; + + @Test + void testTransferService() { + // test the transferService + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig( + TransferServiceConfig::class, + StandaloneDataConfig::class, + JndiDataConfig::class, + DefaultDataConfig::class) + @ActiveProfiles("dev") + class TransferServiceTest { + + @Autowired + lateinit var transferService: TransferService + + @Test + fun testTransferService() { + // test the transferService + } + } +---- + +In this variation, we have split the XML configuration into four independent +`@Configuration` classes: + +* `TransferServiceConfig`: Acquires a `dataSource` through dependency injection by using + `@Autowired`. +* `StandaloneDataConfig`: Defines a `dataSource` for an embedded database suitable for + developer tests. +* `JndiDataConfig`: Defines a `dataSource` that is retrieved from JNDI in a production + environment. +* `DefaultDataConfig`: Defines a `dataSource` for a default embedded database, in case no + profile is active. + +As with the XML-based configuration example, we still annotate `TransferServiceTest` with +`@ActiveProfiles("dev")`, but this time we specify all four configuration classes by +using the `@ContextConfiguration` annotation. The body of the test class itself remains +completely unchanged. + +It is often the case that a single set of profiles is used across multiple test classes +within a given project. Thus, to avoid duplicate declarations of the `@ActiveProfiles` +annotation, you can declare `@ActiveProfiles` once on a base class, and subclasses +automatically inherit the `@ActiveProfiles` configuration from the base class. In the +following example, the declaration of `@ActiveProfiles` (as well as other annotations) +has been moved to an abstract superclass, `AbstractIntegrationTest`: + +NOTE: As of Spring Framework 5.3, test configuration may also be inherited from enclosing +classes. See <> for details. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig({ + TransferServiceConfig.class, + StandaloneDataConfig.class, + JndiDataConfig.class, + DefaultDataConfig.class}) + @ActiveProfiles("dev") + abstract class AbstractIntegrationTest { + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig( + TransferServiceConfig::class, + StandaloneDataConfig::class, + JndiDataConfig::class, + DefaultDataConfig::class) + @ActiveProfiles("dev") + abstract class AbstractIntegrationTest { + } +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // "dev" profile inherited from superclass + class TransferServiceTest extends AbstractIntegrationTest { + + @Autowired + TransferService transferService; + + @Test + void testTransferService() { + // test the transferService + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // "dev" profile inherited from superclass + class TransferServiceTest : AbstractIntegrationTest() { + + @Autowired + lateinit var transferService: TransferService + + @Test + fun testTransferService() { + // test the transferService + } + } +---- + +`@ActiveProfiles` also supports an `inheritProfiles` attribute that can be used to +disable the inheritance of active profiles, as the following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // "dev" profile overridden with "production" + @ActiveProfiles(profiles = "production", inheritProfiles = false) + class ProductionTransferServiceTest extends AbstractIntegrationTest { + // test body + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // "dev" profile overridden with "production" + @ActiveProfiles("production", inheritProfiles = false) + class ProductionTransferServiceTest : AbstractIntegrationTest() { + // test body + } +---- + +[[testcontext-ctx-management-env-profiles-ActiveProfilesResolver]] +Furthermore, it is sometimes necessary to resolve active profiles for tests +programmatically instead of declaratively -- for example, based on: + +* The current operating system. +* Whether tests are being run on a continuous integration build server. +* The presence of certain environment variables. +* The presence of custom class-level annotations. +* Other concerns. + +To resolve active bean definition profiles programmatically, you can implement +a custom `ActiveProfilesResolver` and register it by using the `resolver` +attribute of `@ActiveProfiles`. For further information, see the corresponding +{api-spring-framework}/test/context/ActiveProfilesResolver.html[javadoc]. +The following example demonstrates how to implement and register a custom +`OperatingSystemActiveProfilesResolver`: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // "dev" profile overridden programmatically via a custom resolver + @ActiveProfiles( + resolver = OperatingSystemActiveProfilesResolver.class, + inheritProfiles = false) + class TransferServiceTest extends AbstractIntegrationTest { + // test body + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // "dev" profile overridden programmatically via a custom resolver + @ActiveProfiles( + resolver = OperatingSystemActiveProfilesResolver::class, + inheritProfiles = false) + class TransferServiceTest : AbstractIntegrationTest() { + // test body + } +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + String profile = ...; + // determine the value of profile based on the operating system + return new String[] {profile}; + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver { + + override fun resolve(testClass: Class<*>): Array { + val profile: String = ... + // determine the value of profile based on the operating system + return arrayOf(profile) + } + } +---- + +[[testcontext-ctx-management-property-sources]] +=== Context Configuration with Test Property Sources + +The Spring Framework has first-class support for the notion of an environment with a +hierarchy of property sources, and you can configure integration tests with test-specific +property sources. In contrast to the `@PropertySource` annotation used on +`@Configuration` classes, you can declare the `@TestPropertySource` annotation on a test +class to declare resource locations for test properties files or inlined properties. +These test property sources are added to the set of `PropertySources` in the +`Environment` for the `ApplicationContext` loaded for the annotated integration test. + +[NOTE] +==== +You can use `@TestPropertySource` with any implementation of the `SmartContextLoader` +SPI, but `@TestPropertySource` is not supported with implementations of the older +`ContextLoader` SPI. + +Implementations of `SmartContextLoader` gain access to merged test property source values +through the `getPropertySourceLocations()` and `getPropertySourceProperties()` methods in +`MergedContextConfiguration`. +==== + +==== Declaring Test Property Sources + +You can configure test properties files by using the `locations` or `value` attribute of +`@TestPropertySource`. + +Both traditional and XML-based properties file formats are supported -- for example, +`"classpath:/com/example/test.properties"` or `"file:///path/to/file.xml"`. + +Each path is interpreted as a Spring `Resource`. A plain path (for example, +`"test.properties"`) is treated as a classpath resource that is relative to the package +in which the test class is defined. A path starting with a slash is treated as an +absolute classpath resource (for example: `"/org/example/test.xml"`). A path that +references a URL (for example, a path prefixed with `classpath:`, `file:`, or `http:`) is +loaded by using the specified resource protocol. Resource location wildcards (such as +`**/*.properties`) are not permitted: Each location must evaluate to exactly one +`.properties` or `.xml` resource. + +The following example uses a test properties file: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @TestPropertySource("/test.properties") // <1> + class MyIntegrationTests { + // class body... + } +---- +<1> Specifying a properties file with an absolute path. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @TestPropertySource("/test.properties") // <1> + class MyIntegrationTests { + // class body... + } +---- +<1> Specifying a properties file with an absolute path. + + +You can configure inlined properties in the form of key-value pairs by using the +`properties` attribute of `@TestPropertySource`, as shown in the next example. All +key-value pairs are added to the enclosing `Environment` as a single test +`PropertySource` with the highest precedence. + +The supported syntax for key-value pairs is the same as the syntax defined for entries in +a Java properties file: + +* `key=value` +* `key:value` +* `key value` + +The following example sets two inlined properties: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) // <1> + class MyIntegrationTests { + // class body... + } +---- +<1> Setting two properties by using two variations of the key-value syntax. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) // <1> + class MyIntegrationTests { + // class body... + } +---- +<1> Setting two properties by using two variations of the key-value syntax. + +[NOTE] +==== +As of Spring Framework 5.2, `@TestPropertySource` can be used as _repeatable annotation_. +That means that you can have multiple declarations of `@TestPropertySource` on a single +test class, with the `locations` and `properties` from later `@TestPropertySource` +annotations overriding those from previous `@TestPropertySource` annotations. + +In addition, you may declare multiple composed annotations on a test class that are each +meta-annotated with `@TestPropertySource`, and all of those `@TestPropertySource` +declarations will contribute to your test property sources. + +Directly present `@TestPropertySource` annotations always take precedence over +meta-present `@TestPropertySource` annotations. In other words, `locations` and +`properties` from a directly present `@TestPropertySource` annotation will override the +`locations` and `properties` from a `@TestPropertySource` annotation used as a +meta-annotation. +==== + + +==== Default Properties File Detection + +If `@TestPropertySource` is declared as an empty annotation (that is, without explicit +values for the `locations` or `properties` attributes), an attempt is made to detect a +default properties file relative to the class that declared the annotation. For example, +if the annotated test class is `com.example.MyTest`, the corresponding default properties +file is `classpath:com/example/MyTest.properties`. If the default cannot be detected, an +`IllegalStateException` is thrown. + +==== Precedence + +Test properties have higher precedence than those defined in the operating system's +environment, Java system properties, or property sources added by the application +declaratively by using `@PropertySource` or programmatically. Thus, test properties can +be used to selectively override properties loaded from system and application property +sources. Furthermore, inlined properties have higher precedence than properties loaded +from resource locations. Note, however, that properties registered via +<> have +higher precedence than those loaded via `@TestPropertySource`. + +In the next example, the `timezone` and `port` properties and any properties defined in +`"/test.properties"` override any properties of the same name that are defined in system +and application property sources. Furthermore, if the `"/test.properties"` file defines +entries for the `timezone` and `port` properties those are overridden by the inlined +properties declared by using the `properties` attribute. The following example shows how +to specify properties both in a file and inline: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @TestPropertySource( + locations = "/test.properties", + properties = {"timezone = GMT", "port: 4242"} + ) + class MyIntegrationTests { + // class body... + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @TestPropertySource("/test.properties", + properties = ["timezone = GMT", "port: 4242"] + ) + class MyIntegrationTests { + // class body... + } +---- + +==== Inheriting and Overriding Test Property Sources + +`@TestPropertySource` supports boolean `inheritLocations` and `inheritProperties` +attributes that denote whether resource locations for properties files and inlined +properties declared by superclasses should be inherited. The default value for both flags +is `true`. This means that a test class inherits the locations and inlined properties +declared by any superclasses. Specifically, the locations and inlined properties for a +test class are appended to the locations and inlined properties declared by superclasses. +Thus, subclasses have the option of extending the locations and inlined properties. Note +that properties that appear later shadow (that is, override) properties of the same name +that appear earlier. In addition, the aforementioned precedence rules apply for inherited +test property sources as well. + +If the `inheritLocations` or `inheritProperties` attribute in `@TestPropertySource` is +set to `false`, the locations or inlined properties, respectively, for the test class +shadow and effectively replace the configuration defined by superclasses. + +NOTE: As of Spring Framework 5.3, test configuration may also be inherited from enclosing +classes. See <> for details. + +In the next example, the `ApplicationContext` for `BaseTest` is loaded by using only the +`base.properties` file as a test property source. In contrast, the `ApplicationContext` +for `ExtendedTest` is loaded by using the `base.properties` and `extended.properties` +files as test property source locations. The following example shows how to define +properties in both a subclass and its superclass by using `properties` files: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @TestPropertySource("base.properties") + @ContextConfiguration + class BaseTest { + // ... + } + + @TestPropertySource("extended.properties") + @ContextConfiguration + class ExtendedTest extends BaseTest { + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @TestPropertySource("base.properties") + @ContextConfiguration + open class BaseTest { + // ... + } + + @TestPropertySource("extended.properties") + @ContextConfiguration + class ExtendedTest : BaseTest() { + // ... + } +---- + +In the next example, the `ApplicationContext` for `BaseTest` is loaded by using only the +inlined `key1` property. In contrast, the `ApplicationContext` for `ExtendedTest` is +loaded by using the inlined `key1` and `key2` properties. The following example shows how +to define properties in both a subclass and its superclass by using inline properties: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @TestPropertySource(properties = "key1 = value1") + @ContextConfiguration + class BaseTest { + // ... + } + + @TestPropertySource(properties = "key2 = value2") + @ContextConfiguration + class ExtendedTest extends BaseTest { + // ... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @TestPropertySource(properties = ["key1 = value1"]) + @ContextConfiguration + open class BaseTest { + // ... + } + + @TestPropertySource(properties = ["key2 = value2"]) + @ContextConfiguration + class ExtendedTest : BaseTest() { + // ... + } +---- + +[[testcontext-ctx-management-dynamic-property-sources]] +=== Context Configuration with Dynamic Property Sources + +As of Spring Framework 5.2.5, the TestContext framework provides support for _dynamic_ +properties via the `@DynamicPropertySource` annotation. This annotation can be used in +integration tests that need to add properties with dynamic values to the set of +`PropertySources` in the `Environment` for the `ApplicationContext` loaded for the +integration test. + +[NOTE] +==== +The `@DynamicPropertySource` annotation and its supporting infrastructure were +originally designed to allow properties from +https://www.testcontainers.org/[Testcontainers] based tests to be exposed easily to +Spring integration tests. However, this feature may also be used with any form of +external resource whose lifecycle is maintained outside the test's `ApplicationContext`. +==== + +In contrast to the <> +annotation that is applied at the class level, `@DynamicPropertySource` must be applied +to a `static` method that accepts a single `DynamicPropertyRegistry` argument which is +used to add _name-value_ pairs to the `Environment`. Values are dynamic and provided via +a `Supplier` which is only invoked when the property is resolved. Typically, method +references are used to supply values, as can be seen in the following example which uses +the Testcontainers project to manage a Redis container outside of the Spring +`ApplicationContext`. The IP address and port of the managed Redis container are made +available to components within the test's `ApplicationContext` via the `redis.host` and +`redis.port` properties. These properties can be accessed via Spring's `Environment` +abstraction or injected directly into Spring-managed components – for example, via +`@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively. + +[TIP] +==== +If you use `@DynamicPropertySource` in a base class and discover that tests in subclasses +fail because the dynamic properties change between subclasses, you may need to annotate +your base class with <> to +ensure that each subclass gets its own `ApplicationContext` with the correct dynamic +properties. +==== + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(/* ... */) + @Testcontainers + class ExampleIntegrationTests { + + @Container + static RedisContainer redis = new RedisContainer(); + + @DynamicPropertySource + static void redisProperties(DynamicPropertyRegistry registry) { + registry.add("redis.host", redis::getHost); + registry.add("redis.port", redis::getMappedPort); + } + + // tests ... + + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(/* ... */) + @Testcontainers + class ExampleIntegrationTests { + + companion object { + + @Container + @JvmStatic + val redis: RedisContainer = RedisContainer() + + @DynamicPropertySource + @JvmStatic + fun redisProperties(registry: DynamicPropertyRegistry) { + registry.add("redis.host", redis::getHost) + registry.add("redis.port", redis::getMappedPort) + } + } + + // tests ... + + } +---- + +==== Precedence + +Dynamic properties have higher precedence than those loaded from `@TestPropertySource`, +the operating system's environment, Java system properties, or property sources added by +the application declaratively by using `@PropertySource` or programmatically. Thus, +dynamic properties can be used to selectively override properties loaded via +`@TestPropertySource`, system property sources, and application property sources. + +[[testcontext-ctx-management-web]] +=== Loading a `WebApplicationContext` + +To instruct the TestContext framework to load a `WebApplicationContext` instead of a +standard `ApplicationContext`, you can annotate the respective test class with +`@WebAppConfiguration`. + +The presence of `@WebAppConfiguration` on your test class instructs the TestContext +framework (TCF) that a `WebApplicationContext` (WAC) should be loaded for your +integration tests. In the background, the TCF makes sure that a `MockServletContext` is +created and supplied to your test's WAC. By default, the base resource path for your +`MockServletContext` is set to `src/main/webapp`. This is interpreted as a path relative +to the root of your JVM (normally the path to your project). If you are familiar with the +directory structure of a web application in a Maven project, you know that +`src/main/webapp` is the default location for the root of your WAR. If you need to +override this default, you can provide an alternate path to the `@WebAppConfiguration` +annotation (for example, `@WebAppConfiguration("src/test/webapp")`). If you wish to +reference a base resource path from the classpath instead of the file system, you can use +Spring's `classpath:` prefix. + +Note that Spring's testing support for `WebApplicationContext` implementations is on par +with its support for standard `ApplicationContext` implementations. When testing with a +`WebApplicationContext`, you are free to declare XML configuration files, Groovy scripts, +or `@Configuration` classes by using `@ContextConfiguration`. You are also free to use +any other test annotations, such as `@ActiveProfiles`, `@TestExecutionListeners`, `@Sql`, +`@Rollback`, and others. + +The remaining examples in this section show some of the various configuration options for +loading a `WebApplicationContext`. The following example shows the TestContext +framework's support for convention over configuration: + +.Conventions +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + + // defaults to "file:src/main/webapp" + @WebAppConfiguration + + // detects "WacTests-context.xml" in the same package + // or static nested @Configuration classes + @ContextConfiguration + class WacTests { + //... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + + // defaults to "file:src/main/webapp" + @WebAppConfiguration + + // detects "WacTests-context.xml" in the same package + // or static nested @Configuration classes + @ContextConfiguration + class WacTests { + //... + } +---- + +If you annotate a test class with `@WebAppConfiguration` without specifying a resource +base path, the resource path effectively defaults to `file:src/main/webapp`. Similarly, +if you declare `@ContextConfiguration` without specifying resource `locations`, component +`classes`, or context `initializers`, Spring tries to detect the presence of your +configuration by using conventions (that is, `WacTests-context.xml` in the same package +as the `WacTests` class or static nested `@Configuration` classes). + +The following example shows how to explicitly declare a resource base path with +`@WebAppConfiguration` and an XML resource location with `@ContextConfiguration`: + +.Default resource semantics +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + + // file system resource + @WebAppConfiguration("webapp") + + // classpath resource + @ContextConfiguration("/spring/test-servlet-config.xml") + class WacTests { + //... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + + // file system resource + @WebAppConfiguration("webapp") + + // classpath resource + @ContextConfiguration("/spring/test-servlet-config.xml") + class WacTests { + //... + } +---- + +The important thing to note here is the different semantics for paths with these two +annotations. By default, `@WebAppConfiguration` resource paths are file system based, +whereas `@ContextConfiguration` resource locations are classpath based. + +The following example shows that we can override the default resource semantics for both +annotations by specifying a Spring resource prefix: + +.Explicit resource semantics +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + + // classpath resource + @WebAppConfiguration("classpath:test-web-resources") + + // file system resource + @ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml") + class WacTests { + //... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + + // classpath resource + @WebAppConfiguration("classpath:test-web-resources") + + // file system resource + @ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml") + class WacTests { + //... + } +---- + +Contrast the comments in this example with the previous example. + +[[testcontext-ctx-management-web-mocks]] +=== Working with Web Mocks + +To provide comprehensive web testing support, the TestContext framework has a +`ServletTestExecutionListener` that is enabled by default. When testing against a +`WebApplicationContext`, this <> +sets up default thread-local state by using Spring Web's `RequestContextHolder` before +each test method and creates a `MockHttpServletRequest`, a `MockHttpServletResponse`, and +a `ServletWebRequest` based on the base resource path configured with +`@WebAppConfiguration`. `ServletTestExecutionListener` also ensures that the +`MockHttpServletResponse` and `ServletWebRequest` can be injected into the test instance, +and, once the test is complete, it cleans up thread-local state. + +Once you have a `WebApplicationContext` loaded for your test, you might find that you +need to interact with the web mocks -- for example, to set up your test fixture or to +perform assertions after invoking your web component. The following example shows which +mocks can be autowired into your test instance. Note that the `WebApplicationContext` and +`MockServletContext` are both cached across the test suite, whereas the other mocks are +managed per test method by the `ServletTestExecutionListener`. + +.Injecting mocks +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitWebConfig + class WacTests { + + @Autowired + WebApplicationContext wac; // cached + + @Autowired + MockServletContext servletContext; // cached + + @Autowired + MockHttpSession session; + + @Autowired + MockHttpServletRequest request; + + @Autowired + MockHttpServletResponse response; + + @Autowired + ServletWebRequest webRequest; + + //... + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitWebConfig + class WacTests { + + @Autowired + lateinit var wac: WebApplicationContext // cached + + @Autowired + lateinit var servletContext: MockServletContext // cached + + @Autowired + lateinit var session: MockHttpSession + + @Autowired + lateinit var request: MockHttpServletRequest + + @Autowired + lateinit var response: MockHttpServletResponse + + @Autowired + lateinit var webRequest: ServletWebRequest + + //... + } +---- + +[[testcontext-ctx-management-caching]] +=== Context Caching + +Once the TestContext framework loads an `ApplicationContext` (or `WebApplicationContext`) +for a test, that context is cached and reused for all subsequent tests that declare the +same unique context configuration within the same test suite. To understand how caching +works, it is important to understand what is meant by "`unique`" and "`test suite.`" + +An `ApplicationContext` can be uniquely identified by the combination of configuration +parameters that is used to load it. Consequently, the unique combination of configuration +parameters is used to generate a key under which the context is cached. The TestContext +framework uses the following configuration parameters to build the context cache key: + +* `locations` (from `@ContextConfiguration`) +* `classes` (from `@ContextConfiguration`) +* `contextInitializerClasses` (from `@ContextConfiguration`) +* `contextCustomizers` (from `ContextCustomizerFactory`) – this includes + `@DynamicPropertySource` methods as well as various features from Spring Boot's + testing support such as `@MockBean` and `@SpyBean`. +* `contextLoader` (from `@ContextConfiguration`) +* `parent` (from `@ContextHierarchy`) +* `activeProfiles` (from `@ActiveProfiles`) +* `propertySourceLocations` (from `@TestPropertySource`) +* `propertySourceProperties` (from `@TestPropertySource`) +* `resourceBasePath` (from `@WebAppConfiguration`) + +For example, if `TestClassA` specifies `{"app-config.xml", "test-config.xml"}` for the +`locations` (or `value`) attribute of `@ContextConfiguration`, the TestContext framework +loads the corresponding `ApplicationContext` and stores it in a `static` context cache +under a key that is based solely on those locations. So, if `TestClassB` also defines +`{"app-config.xml", "test-config.xml"}` for its locations (either explicitly or +implicitly through inheritance) but does not define `@WebAppConfiguration`, a different +`ContextLoader`, different active profiles, different context initializers, different +test property sources, or a different parent context, then the same `ApplicationContext` +is shared by both test classes. This means that the setup cost for loading an application +context is incurred only once (per test suite), and subsequent test execution is much +faster. + +.Test suites and forked processes +[NOTE] +==== +The Spring TestContext framework stores application contexts in a static cache. This +means that the context is literally stored in a `static` variable. In other words, if +tests run in separate processes, the static cache is cleared between each test +execution, which effectively disables the caching mechanism. + +To benefit from the caching mechanism, all tests must run within the same process or test +suite. This can be achieved by executing all tests as a group within an IDE. Similarly, +when executing tests with a build framework such as Ant, Maven, or Gradle, it is +important to make sure that the build framework does not fork between tests. For example, +if the +https://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html#forkMode[`forkMode`] +for the Maven Surefire plug-in is set to `always` or `pertest`, the TestContext framework +cannot cache application contexts between test classes, and the build process runs +significantly more slowly as a result. +==== + +The size of the context cache is bounded with a default maximum size of 32. Whenever the +maximum size is reached, a least recently used (LRU) eviction policy is used to evict and +close stale contexts. You can configure the maximum size from the command line or a build +script by setting a JVM system property named `spring.test.context.cache.maxSize`. As an +alternative, you can set the same property via the +<> mechanism. + +Since having a large number of application contexts loaded within a given test suite can +cause the suite to take an unnecessarily long time to run, it is often beneficial to +know exactly how many contexts have been loaded and cached. To view the statistics for +the underlying context cache, you can set the log level for the +`org.springframework.test.context.cache` logging category to `DEBUG`. + +In the unlikely case that a test corrupts the application context and requires reloading +(for example, by modifying a bean definition or the state of an application object), you +can annotate your test class or test method with `@DirtiesContext` (see the discussion of +`@DirtiesContext` in <>). This instructs Spring to remove the context from the cache and rebuild +the application context before running the next test that requires the same application +context. Note that support for the `@DirtiesContext` annotation is provided by the +`DirtiesContextBeforeModesTestExecutionListener` and the +`DirtiesContextTestExecutionListener`, which are enabled by default. + +.ApplicationContext lifecycle and console logging +[NOTE] +==== +When you need to debug a test executed with the Spring TestContext Framework, it can be +useful to analyze the console output (that is, output to the `SYSOUT` and `SYSERR` +streams). Some build tools and IDEs are able to associate console output with a given +test; however, some console output cannot be easily associated with a given test. + +With regard to console logging triggered by the Spring Framework itself or by components +registered in the `ApplicationContext`, it is important to understand the lifecycle of an +`ApplicationContext` that has been loaded by the Spring TestContext Framework within a +test suite. + +The `ApplicationContext` for a test is typically loaded when an instance of the test +class is being prepared -- for example, to perform dependency injection into `@Autowired` +fields of the test instance. This means that any console logging triggered during the +initialization of the `ApplicationContext` typically cannot be associated with an +individual test method. However, if the context is closed immediately before the +execution of a test method according to <> +semantics, a new instance of the context will be loaded just prior to execution of the +test method. In the latter scenario, an IDE or build tool may potentially associate +console logging with the individual test method. + +The `ApplicationContext` for a test can be closed via one of the following scenarios. + +* The context is closed according to `@DirtiesContext` semantics. +* The context is closed because it has been automatically evicted from the cache + according to the LRU eviction policy. +* The context is closed via a JVM shutdown hook when the JVM for the test suite + terminates. + +If the context is closed according to `@DirtiesContext` semantics after a particular test +method, an IDE or build tool may potentially associate console logging with the +individual test method. If the context is closed according to `@DirtiesContext` semantics +after a test class, any console logging triggered during the shutdown of the +`ApplicationContext` cannot be associated with an individual test method. Similarly, any +console logging triggered during the shutdown phase via a JVM shutdown hook cannot be +associated with an individual test method. + +When a Spring `ApplicationContext` is closed via a JVM shutdown hook, callbacks executed +during the shutdown phase are executed on a thread named `SpringContextShutdownHook`. So, +if you wish to disable console logging triggered when the `ApplicationContext` is closed +via a JVM shutdown hook, you may be able to register a custom filter with your logging +framework that allows you to ignore any logging initiated by that thread. +==== + +[[testcontext-ctx-management-ctx-hierarchies]] +=== Context Hierarchies + +When writing integration tests that rely on a loaded Spring `ApplicationContext`, it is +often sufficient to test against a single context. However, there are times when it is +beneficial or even necessary to test against a hierarchy of `ApplicationContext` +instances. For example, if you are developing a Spring MVC web application, you typically +have a root `WebApplicationContext` loaded by Spring's `ContextLoaderListener` and a +child `WebApplicationContext` loaded by Spring's `DispatcherServlet`. This results in a +parent-child context hierarchy where shared components and infrastructure configuration +are declared in the root context and consumed in the child context by web-specific +components. Another use case can be found in Spring Batch applications, where you often +have a parent context that provides configuration for shared batch infrastructure and a +child context for the configuration of a specific batch job. + +You can write integration tests that use context hierarchies by declaring context +configuration with the `@ContextHierarchy` annotation, either on an individual test class +or within a test class hierarchy. If a context hierarchy is declared on multiple classes +within a test class hierarchy, you can also merge or override the context configuration +for a specific, named level in the context hierarchy. When merging configuration for a +given level in the hierarchy, the configuration resource type (that is, XML configuration +files or component classes) must be consistent. Otherwise, it is perfectly acceptable to +have different levels in a context hierarchy configured using different resource types. + +The remaining JUnit Jupiter based examples in this section show common configuration +scenarios for integration tests that require the use of context hierarchies. + +**Single test class with context hierarchy** +-- +`ControllerIntegrationTests` represents a typical integration testing scenario for a +Spring MVC web application by declaring a context hierarchy that consists of two levels, +one for the root `WebApplicationContext` (loaded by using the `TestAppConfig` +`@Configuration` class) and one for the dispatcher servlet `WebApplicationContext` +(loaded by using the `WebConfig` `@Configuration` class). The `WebApplicationContext` +that is autowired into the test instance is the one for the child context (that is, the +lowest context in the hierarchy). The following listing shows this configuration scenario: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + @WebAppConfiguration + @ContextHierarchy({ + @ContextConfiguration(classes = TestAppConfig.class), + @ContextConfiguration(classes = WebConfig.class) + }) + class ControllerIntegrationTests { + + @Autowired + WebApplicationContext wac; + + // ... + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + @WebAppConfiguration + @ContextHierarchy( + ContextConfiguration(classes = [TestAppConfig::class]), + ContextConfiguration(classes = [WebConfig::class])) + class ControllerIntegrationTests { + + @Autowired + lateinit var wac: WebApplicationContext + + // ... + } +---- +-- + +**Class hierarchy with implicit parent context** +-- +The test classes in this example define a context hierarchy within a test class +hierarchy. `AbstractWebTests` declares the configuration for a root +`WebApplicationContext` in a Spring-powered web application. Note, however, that +`AbstractWebTests` does not declare `@ContextHierarchy`. Consequently, subclasses of +`AbstractWebTests` can optionally participate in a context hierarchy or follow the +standard semantics for `@ContextConfiguration`. `SoapWebServiceTests` and +`RestWebServiceTests` both extend `AbstractWebTests` and define a context hierarchy by +using `@ContextHierarchy`. The result is that three application contexts are loaded (one +for each declaration of `@ContextConfiguration`), and the application context loaded +based on the configuration in `AbstractWebTests` is set as the parent context for each of +the contexts loaded for the concrete subclasses. The following listing shows this +configuration scenario: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + @WebAppConfiguration + @ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml") + public abstract class AbstractWebTests {} + + @ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml")) + public class SoapWebServiceTests extends AbstractWebTests {} + + @ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml")) + public class RestWebServiceTests extends AbstractWebTests {} +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + @WebAppConfiguration + @ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml") + abstract class AbstractWebTests + + @ContextHierarchy(ContextConfiguration("/spring/soap-ws-config.xml")) + class SoapWebServiceTests : AbstractWebTests() + + @ContextHierarchy(ContextConfiguration("/spring/rest-ws-config.xml")) + class RestWebServiceTests : AbstractWebTests() + +---- +-- + +**Class hierarchy with merged context hierarchy configuration** +-- +The classes in this example show the use of named hierarchy levels in order to merge the +configuration for specific levels in a context hierarchy. `BaseTests` defines two levels +in the hierarchy, `parent` and `child`. `ExtendedTests` extends `BaseTests` and instructs +the Spring TestContext Framework to merge the context configuration for the `child` +hierarchy level, by ensuring that the names declared in the `name` attribute in +`@ContextConfiguration` are both `child`. The result is that three application contexts +are loaded: one for `/app-config.xml`, one for `/user-config.xml`, and one for +`{"/user-config.xml", "/order-config.xml"}`. As with the previous example, the +application context loaded from `/app-config.xml` is set as the parent context for the +contexts loaded from `/user-config.xml` and `{"/user-config.xml", "/order-config.xml"}`. +The following listing shows this configuration scenario: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + @ContextHierarchy({ + @ContextConfiguration(name = "parent", locations = "/app-config.xml"), + @ContextConfiguration(name = "child", locations = "/user-config.xml") + }) + class BaseTests {} + + @ContextHierarchy( + @ContextConfiguration(name = "child", locations = "/order-config.xml") + ) + class ExtendedTests extends BaseTests {} +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + @ContextHierarchy( + ContextConfiguration(name = "parent", locations = ["/app-config.xml"]), + ContextConfiguration(name = "child", locations = ["/user-config.xml"])) + open class BaseTests {} + + @ContextHierarchy( + ContextConfiguration(name = "child", locations = ["/order-config.xml"]) + ) + class ExtendedTests : BaseTests() {} +---- +-- + +**Class hierarchy with overridden context hierarchy configuration** +-- +In contrast to the previous example, this example demonstrates how to override the +configuration for a given named level in a context hierarchy by setting the +`inheritLocations` flag in `@ContextConfiguration` to `false`. Consequently, the +application context for `ExtendedTests` is loaded only from `/test-user-config.xml` and +has its parent set to the context loaded from `/app-config.xml`. The following listing +shows this configuration scenario: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + @ContextHierarchy({ + @ContextConfiguration(name = "parent", locations = "/app-config.xml"), + @ContextConfiguration(name = "child", locations = "/user-config.xml") + }) + class BaseTests {} + + @ContextHierarchy( + @ContextConfiguration( + name = "child", + locations = "/test-user-config.xml", + inheritLocations = false + )) + class ExtendedTests extends BaseTests {} +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + @ContextHierarchy( + ContextConfiguration(name = "parent", locations = ["/app-config.xml"]), + ContextConfiguration(name = "child", locations = ["/user-config.xml"])) + open class BaseTests {} + + @ContextHierarchy( + ContextConfiguration( + name = "child", + locations = ["/test-user-config.xml"], + inheritLocations = false + )) + class ExtendedTests : BaseTests() {} +---- + +.Dirtying a context within a context hierarchy +NOTE: If you use `@DirtiesContext` in a test whose context is configured as part of a +context hierarchy, you can use the `hierarchyMode` flag to control how the context cache +is cleared. For further details, see the discussion of `@DirtiesContext` in +<> and the +{api-spring-framework}/test/annotation/DirtiesContext.html[`@DirtiesContext`] javadoc. +-- + +[[testcontext-fixture-di]] +== Dependency Injection of Test Fixtures + +When you use the `DependencyInjectionTestExecutionListener` (which is configured by +default), the dependencies of your test instances are injected from beans in the +application context that you configured with `@ContextConfiguration` or related +annotations. You may use setter injection, field injection, or both, depending on +which annotations you choose and whether you place them on setter methods or fields. +If you are using JUnit Jupiter you may also optionally use constructor injection +(see <>). For consistency with Spring's annotation-based +injection support, you may also use Spring's `@Autowired` annotation or the `@Inject` +annotation from JSR-330 for field and setter injection. + +TIP: For testing frameworks other than JUnit Jupiter, the TestContext framework does not +participate in instantiation of the test class. Thus, the use of `@Autowired` or +`@Inject` for constructors has no effect for test classes. + +NOTE: Although field injection is discouraged in production code, field injection is +actually quite natural in test code. The rationale for the difference is that you will +never instantiate your test class directly. Consequently, there is no need to be able to +invoke a `public` constructor or setter method on your test class. + +Because `@Autowired` is used to perform <>, if you have multiple bean definitions of the same type, you cannot rely on this +approach for those particular beans. In that case, you can use `@Autowired` in +conjunction with `@Qualifier`. You can also choose to use `@Inject` in conjunction with +`@Named`. Alternatively, if your test class has access to its `ApplicationContext`, you +can perform an explicit lookup by using (for example) a call to +`applicationContext.getBean("titleRepository", TitleRepository.class)`. + +If you do not want dependency injection applied to your test instances, do not annotate +fields or setter methods with `@Autowired` or `@Inject`. Alternatively, you can disable +dependency injection altogether by explicitly configuring your class with +`@TestExecutionListeners` and omitting `DependencyInjectionTestExecutionListener.class` +from the list of listeners. + +Consider the scenario of testing a `HibernateTitleRepository` class, as outlined in the +<> section. The next two code listings demonstrate the +use of `@Autowired` on fields and setter methods. The application context configuration +is presented after all sample code listings. + +[NOTE] +==== +The dependency injection behavior in the following code listings is not specific to JUnit +Jupiter. The same DI techniques can be used in conjunction with any supported testing +framework. + +The following examples make calls to static assertion methods, such as `assertNotNull()`, +but without prepending the call with `Assertions`. In such cases, assume that the method +was properly imported through an `import static` declaration that is not shown in the +example. +==== + +The first code listing shows a JUnit Jupiter based implementation of the test class that +uses `@Autowired` for field injection: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // specifies the Spring configuration to load for this test fixture + @ContextConfiguration("repository-config.xml") + class HibernateTitleRepositoryTests { + + // this instance will be dependency injected by type + @Autowired + HibernateTitleRepository titleRepository; + + @Test + void findById() { + Title title = titleRepository.findById(new Long(10)); + assertNotNull(title); + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // specifies the Spring configuration to load for this test fixture + @ContextConfiguration("repository-config.xml") + class HibernateTitleRepositoryTests { + + // this instance will be dependency injected by type + @Autowired + lateinit var titleRepository: HibernateTitleRepository + + @Test + fun findById() { + val title = titleRepository.findById(10) + assertNotNull(title) + } + } +---- + +Alternatively, you can configure the class to use `@Autowired` for setter injection, as +follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + // specifies the Spring configuration to load for this test fixture + @ContextConfiguration("repository-config.xml") + class HibernateTitleRepositoryTests { + + // this instance will be dependency injected by type + HibernateTitleRepository titleRepository; + + @Autowired + void setTitleRepository(HibernateTitleRepository titleRepository) { + this.titleRepository = titleRepository; + } + + @Test + void findById() { + Title title = titleRepository.findById(new Long(10)); + assertNotNull(title); + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + // specifies the Spring configuration to load for this test fixture + @ContextConfiguration("repository-config.xml") + class HibernateTitleRepositoryTests { + + // this instance will be dependency injected by type + lateinit var titleRepository: HibernateTitleRepository + + @Autowired + fun setTitleRepository(titleRepository: HibernateTitleRepository) { + this.titleRepository = titleRepository + } + + @Test + fun findById() { + val title = titleRepository.findById(10) + assertNotNull(title) + } + } +---- + +The preceding code listings use the same XML context file referenced by the +`@ContextConfiguration` annotation (that is, `repository-config.xml`). The following +shows this configuration: + +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + + + + + +---- + +[NOTE] +===== +If you are extending from a Spring-provided test base class that happens to use +`@Autowired` on one of its setter methods, you might have multiple beans of the affected +type defined in your application context (for example, multiple `DataSource` beans). In +such a case, you can override the setter method and use the `@Qualifier` annotation to +indicate a specific target bean, as follows (but make sure to delegate to the overridden +method in the superclass as well): + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // ... + + @Autowired + @Override + public void setDataSource(@Qualifier("myDataSource") DataSource dataSource) { + super.setDataSource(dataSource); + } + + // ... +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // ... + + @Autowired + override fun setDataSource(@Qualifier("myDataSource") dataSource: DataSource) { + super.setDataSource(dataSource) + } + + // ... +---- + +The specified qualifier value indicates the specific `DataSource` bean to inject, +narrowing the set of type matches to a specific bean. Its value is matched against +`` declarations within the corresponding `` definitions. The bean name +is used as a fallback qualifier value, so you can effectively also point to a specific +bean by name there (as shown earlier, assuming that `myDataSource` is the bean `id`). +===== + + +[[testcontext-web-scoped-beans]] +== Testing Request- and Session-scoped Beans + +Spring has supported <> since the early years, and you can test your request-scoped and session-scoped +beans by following these steps: + +* Ensure that a `WebApplicationContext` is loaded for your test by annotating your test + class with `@WebAppConfiguration`. +* Inject the mock request or session into your test instance and prepare your test + fixture as appropriate. +* Invoke your web component that you retrieved from the configured + `WebApplicationContext` (with dependency injection). +* Perform assertions against the mocks. + +The next code snippet shows the XML configuration for a login use case. Note that the +`userService` bean has a dependency on a request-scoped `loginAction` bean. Also, the +`LoginAction` is instantiated by using <> that +retrieve the username and password from the current HTTP request. In our test, we want to +configure these request parameters through the mock managed by the TestContext framework. +The following listing shows the configuration for this use case: + +.Request-scoped bean configuration +[source,xml,indent=0] +---- + + + + + + + + + +---- + +In `RequestScopedBeanTests`, we inject both the `UserService` (that is, the subject under +test) and the `MockHttpServletRequest` into our test instance. Within our +`requestScope()` test method, we set up our test fixture by setting request parameters in +the provided `MockHttpServletRequest`. When the `loginUser()` method is invoked on our +`userService`, we are assured that the user service has access to the request-scoped +`loginAction` for the current `MockHttpServletRequest` (that is, the one in which we just +set parameters). We can then perform assertions against the results based on the known +inputs for the username and password. The following listing shows how to do so: + +.Request-scoped bean test +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitWebConfig + class RequestScopedBeanTests { + + @Autowired UserService userService; + @Autowired MockHttpServletRequest request; + + @Test + void requestScope() { + request.setParameter("user", "enigma"); + request.setParameter("pswd", "$pr!ng"); + + LoginResults results = userService.loginUser(); + // assert results + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitWebConfig + class RequestScopedBeanTests { + + @Autowired lateinit var userService: UserService + @Autowired lateinit var request: MockHttpServletRequest + + @Test + fun requestScope() { + request.setParameter("user", "enigma") + request.setParameter("pswd", "\$pr!ng") + + val results = userService.loginUser() + // assert results + } + } +---- + +The following code snippet is similar to the one we saw earlier for a request-scoped +bean. However, this time, the `userService` bean has a dependency on a session-scoped +`userPreferences` bean. Note that the `UserPreferences` bean is instantiated by using a +SpEL expression that retrieves the theme from the current HTTP session. In our test, we +need to configure a theme in the mock session managed by the TestContext framework. The +following example shows how to do so: + +.Session-scoped bean configuration +[source,xml,indent=0,subs="verbatim,quotes"] +---- + + + + + + + + + +---- + +In `SessionScopedBeanTests`, we inject the `UserService` and the `MockHttpSession` into +our test instance. Within our `sessionScope()` test method, we set up our test fixture by +setting the expected `theme` attribute in the provided `MockHttpSession`. When the +`processUserPreferences()` method is invoked on our `userService`, we are assured that +the user service has access to the session-scoped `userPreferences` for the current +`MockHttpSession`, and we can perform assertions against the results based on the +configured theme. The following example shows how to do so: + +.Session-scoped bean test +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitWebConfig + class SessionScopedBeanTests { + + @Autowired UserService userService; + @Autowired MockHttpSession session; + + @Test + void sessionScope() throws Exception { + session.setAttribute("theme", "blue"); + + Results results = userService.processUserPreferences(); + // assert results + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitWebConfig + class SessionScopedBeanTests { + + @Autowired lateinit var userService: UserService + @Autowired lateinit var session: MockHttpSession + + @Test + fun sessionScope() { + session.setAttribute("theme", "blue") + + val results = userService.processUserPreferences() + // assert results + } + } +---- + +[[testcontext-tx]] +== Transaction Management + +In the TestContext framework, transactions are managed by the +`TransactionalTestExecutionListener`, which is configured by default, even if you do not +explicitly declare `@TestExecutionListeners` on your test class. To enable support for +transactions, however, you must configure a `PlatformTransactionManager` bean in the +`ApplicationContext` that is loaded with `@ContextConfiguration` semantics (further +details are provided later). In addition, you must declare Spring's `@Transactional` +annotation either at the class or the method level for your tests. + +[[testcontext-tx-test-managed-transactions]] +=== Test-managed Transactions + +Test-managed transactions are transactions that are managed declaratively by using the +`TransactionalTestExecutionListener` or programmatically by using `TestTransaction` +(described later). You should not confuse such transactions with Spring-managed +transactions (those managed directly by Spring within the `ApplicationContext` loaded for +tests) or application-managed transactions (those managed programmatically within +application code that is invoked by tests). Spring-managed and application-managed +transactions typically participate in test-managed transactions. However, you should use +caution if Spring-managed or application-managed transactions are configured with any +propagation type other than `REQUIRED` or `SUPPORTS` (see the discussion on +<> for details). + +.Preemptive timeouts and test-managed transactions +[WARNING] +==== +Caution must be taken when using any form of preemptive timeouts from a testing framework +in conjunction with Spring's test-managed transactions. + +Specifically, Spring’s testing support binds transaction state to the current thread (via +a `java.lang.ThreadLocal` variable) _before_ the current test method is invoked. If a +testing framework invokes the current test method in a new thread in order to support a +preemptive timeout, any actions performed within the current test method will _not_ be +invoked within the test-managed transaction. Consequently, the result of any such actions +will not be rolled back with the test-managed transaction. On the contrary, such actions +will be committed to the persistent store -- for example, a relational database -- even +though the test-managed transaction is properly rolled back by Spring. + +Situations in which this can occur include but are not limited to the following. + +* JUnit 4's `@Test(timeout = ...)` support and `TimeOut` rule +* JUnit Jupiter's `assertTimeoutPreemptively(...)` methods in the + `org.junit.jupiter.api.Assertions` class +* TestNG's `@Test(timeOut = ...)` support +==== + +[[testcontext-tx-enabling-transactions]] +=== Enabling and Disabling Transactions + +Annotating a test method with `@Transactional` causes the test to be run within a +transaction that is, by default, automatically rolled back after completion of the test. +If a test class is annotated with `@Transactional`, each test method within that class +hierarchy runs within a transaction. Test methods that are not annotated with +`@Transactional` (at the class or method level) are not run within a transaction. Note +that `@Transactional` is not supported on test lifecycle methods — for example, methods +annotated with JUnit Jupiter's `@BeforeAll`, `@BeforeEach`, etc. Furthermore, tests that +are annotated with `@Transactional` but have the `propagation` attribute set to +`NOT_SUPPORTED` or `NEVER` are not run within a transaction. + +[[testcontext-tx-attribute-support]] +.`@Transactional` attribute support +|=== +|Attribute |Supported for test-managed transactions + +|`value` and `transactionManager` |yes + +|`propagation` |only `Propagation.NOT_SUPPORTED` and `Propagation.NEVER` are supported + +|`isolation` |no + +|`timeout` |no + +|`readOnly` |no + +|`rollbackFor` and `rollbackForClassName` |no: use `TestTransaction.flagForRollback()` instead + +|`noRollbackFor` and `noRollbackForClassName` |no: use `TestTransaction.flagForCommit()` instead +|=== + +[TIP] +==== +Method-level lifecycle methods — for example, methods annotated with JUnit Jupiter's +`@BeforeEach` or `@AfterEach` — are run within a test-managed transaction. On the other +hand, suite-level and class-level lifecycle methods — for example, methods annotated with +JUnit Jupiter's `@BeforeAll` or `@AfterAll` and methods annotated with TestNG's +`@BeforeSuite`, `@AfterSuite`, `@BeforeClass`, or `@AfterClass` — are _not_ run within a +test-managed transaction. + +If you need to run code in a suite-level or class-level lifecycle method within a +transaction, you may wish to inject a corresponding `PlatformTransactionManager` into +your test class and then use that with a `TransactionTemplate` for programmatic +transaction management. +==== + +Note that <> and +<> +are preconfigured for transactional support at the class level. + +The following example demonstrates a common scenario for writing an integration test for +a Hibernate-based `UserRepository`: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(TestConfig.class) + @Transactional + class HibernateUserRepositoryTests { + + @Autowired + HibernateUserRepository repository; + + @Autowired + SessionFactory sessionFactory; + + JdbcTemplate jdbcTemplate; + + @Autowired + void setDataSource(DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Test + void createUser() { + // track initial state in test database: + final int count = countRowsInTable("user"); + + User user = new User(...); + repository.save(user); + + // Manual flush is required to avoid false positive in test + sessionFactory.getCurrentSession().flush(); + assertNumUsers(count + 1); + } + + private int countRowsInTable(String tableName) { + return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); + } + + private void assertNumUsers(int expected) { + assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")); + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestConfig::class) + @Transactional + class HibernateUserRepositoryTests { + + @Autowired + lateinit var repository: HibernateUserRepository + + @Autowired + lateinit var sessionFactory: SessionFactory + + lateinit var jdbcTemplate: JdbcTemplate + + @Autowired + fun setDataSource(dataSource: DataSource) { + this.jdbcTemplate = JdbcTemplate(dataSource) + } + + @Test + fun createUser() { + // track initial state in test database: + val count = countRowsInTable("user") + + val user = User() + repository.save(user) + + // Manual flush is required to avoid false positive in test + sessionFactory.getCurrentSession().flush() + assertNumUsers(count + 1) + } + + private fun countRowsInTable(tableName: String): Int { + return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName) + } + + private fun assertNumUsers(expected: Int) { + assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")) + } + } +---- + +As explained in <>, there is no need to +clean up the database after the `createUser()` method runs, since any changes made to the +database are automatically rolled back by the `TransactionalTestExecutionListener`. + +[[testcontext-tx-rollback-and-commit-behavior]] +=== Transaction Rollback and Commit Behavior + +By default, test transactions will be automatically rolled back after completion of the +test; however, transactional commit and rollback behavior can be configured declaratively +via the `@Commit` and `@Rollback` annotations. See the corresponding entries in the +<> section for further details. + +[[testcontext-tx-programmatic-tx-mgt]] +=== Programmatic Transaction Management + +You can interact with test-managed transactions programmatically by using the static +methods in `TestTransaction`. For example, you can use `TestTransaction` within test +methods, before methods, and after methods to start or end the current test-managed +transaction or to configure the current test-managed transaction for rollback or commit. +Support for `TestTransaction` is automatically available whenever the +`TransactionalTestExecutionListener` is enabled. + +The following example demonstrates some of the features of `TestTransaction`. See the +javadoc for {api-spring-framework}/test/context/transaction/TestTransaction.html[`TestTransaction`] +for further details. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration(classes = TestConfig.class) + public class ProgrammaticTransactionManagementTests extends + AbstractTransactionalJUnit4SpringContextTests { + + @Test + public void transactionalTest() { + // assert initial state in test database: + assertNumUsers(2); + + deleteFromTables("user"); + + // changes to the database will be committed! + TestTransaction.flagForCommit(); + TestTransaction.end(); + assertFalse(TestTransaction.isActive()); + assertNumUsers(0); + + TestTransaction.start(); + // perform other actions against the database that will + // be automatically rolled back after the test completes... + } + + protected void assertNumUsers(int expected) { + assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")); + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration(classes = [TestConfig::class]) + class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() { + + @Test + fun transactionalTest() { + // assert initial state in test database: + assertNumUsers(2) + + deleteFromTables("user") + + // changes to the database will be committed! + TestTransaction.flagForCommit() + TestTransaction.end() + assertFalse(TestTransaction.isActive()) + assertNumUsers(0) + + TestTransaction.start() + // perform other actions against the database that will + // be automatically rolled back after the test completes... + } + + protected fun assertNumUsers(expected: Int) { + assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")) + } + } +---- + +[[testcontext-tx-before-and-after-tx]] +=== Running Code Outside of a Transaction + +Occasionally, you may need to run certain code before or after a transactional test +method but outside the transactional context -- for example, to verify the initial +database state prior to running your test or to verify expected transactional commit +behavior after your test runs (if the test was configured to commit the transaction). +`TransactionalTestExecutionListener` supports the `@BeforeTransaction` and +`@AfterTransaction` annotations for exactly such scenarios. You can annotate any `void` +method in a test class or any `void` default method in a test interface with one of these +annotations, and the `TransactionalTestExecutionListener` ensures that your before +transaction method or after transaction method runs at the appropriate time. + +TIP: Any before methods (such as methods annotated with JUnit Jupiter's `@BeforeEach`) +and any after methods (such as methods annotated with JUnit Jupiter's `@AfterEach`) are +run within a transaction. In addition, methods annotated with `@BeforeTransaction` or +`@AfterTransaction` are not run for test methods that are not configured to run within a +transaction. + +[[testcontext-tx-mgr-config]] +=== Configuring a Transaction Manager + +`TransactionalTestExecutionListener` expects a `PlatformTransactionManager` bean to be +defined in the Spring `ApplicationContext` for the test. If there are multiple instances +of `PlatformTransactionManager` within the test's `ApplicationContext`, you can declare a +qualifier by using `@Transactional("myTxMgr")` or `@Transactional(transactionManager = +"myTxMgr")`, or `TransactionManagementConfigurer` can be implemented by an +`@Configuration` class. Consult the +{api-spring-framework}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager-org.springframework.test.context.TestContext-java.lang.String-[javadoc +for `TestContextTransactionUtils.retrieveTransactionManager()`] for details on the +algorithm used to look up a transaction manager in the test's `ApplicationContext`. + +[[testcontext-tx-annotation-demo]] +=== Demonstration of All Transaction-related Annotations + +The following JUnit Jupiter based example displays a fictitious integration testing +scenario that highlights all transaction-related annotations. The example is not intended +to demonstrate best practices but rather to demonstrate how these annotations can be +used. See the <> section for further +information and configuration examples. <> contains an additional example that uses `@Sql` for +declarative SQL script execution with default transaction rollback semantics. The +following example shows the relevant annotations: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig + @Transactional(transactionManager = "txMgr") + @Commit + class FictitiousTransactionalTest { + + @BeforeTransaction + void verifyInitialDatabaseState() { + // logic to verify the initial state before a transaction is started + } + + @BeforeEach + void setUpTestDataWithinTransaction() { + // set up test data within the transaction + } + + @Test + // overrides the class-level @Commit setting + @Rollback + void modifyDatabaseWithinTransaction() { + // logic which uses the test data and modifies database state + } + + @AfterEach + void tearDownWithinTransaction() { + // run "tear down" logic within the transaction + } + + @AfterTransaction + void verifyFinalDatabaseState() { + // logic to verify the final state after transaction has rolled back + } + + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig + @Transactional(transactionManager = "txMgr") + @Commit + class FictitiousTransactionalTest { + + @BeforeTransaction + fun verifyInitialDatabaseState() { + // logic to verify the initial state before a transaction is started + } + + @BeforeEach + fun setUpTestDataWithinTransaction() { + // set up test data within the transaction + } + + @Test + // overrides the class-level @Commit setting + @Rollback + fun modifyDatabaseWithinTransaction() { + // logic which uses the test data and modifies database state + } + + @AfterEach + fun tearDownWithinTransaction() { + // run "tear down" logic within the transaction + } + + @AfterTransaction + fun verifyFinalDatabaseState() { + // logic to verify the final state after transaction has rolled back + } + + } +---- + +[[testcontext-tx-false-positives]] +.Avoid false positives when testing ORM code +[NOTE] +===== +When you test application code that manipulates the state of a Hibernate session or JPA +persistence context, make sure to flush the underlying unit of work within test methods +that run that code. Failing to flush the underlying unit of work can produce false +positives: Your test passes, but the same code throws an exception in a live, production +environment. Note that this applies to any ORM framework that maintains an in-memory unit +of work. In the following Hibernate-based example test case, one method demonstrates a +false positive, and the other method correctly exposes the results of flushing the +session: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // ... + + @Autowired + SessionFactory sessionFactory; + + @Transactional + @Test // no expected exception! + public void falsePositive() { + updateEntityInHibernateSession(); + // False positive: an exception will be thrown once the Hibernate + // Session is finally flushed (i.e., in production code) + } + + @Transactional + @Test(expected = ...) + public void updateWithSessionFlush() { + updateEntityInHibernateSession(); + // Manual flush is required to avoid false positive in test + sessionFactory.getCurrentSession().flush(); + } + + // ... +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // ... + + @Autowired + lateinit var sessionFactory: SessionFactory + + @Transactional + @Test // no expected exception! + fun falsePositive() { + updateEntityInHibernateSession() + // False positive: an exception will be thrown once the Hibernate + // Session is finally flushed (i.e., in production code) + } + + @Transactional + @Test(expected = ...) + fun updateWithSessionFlush() { + updateEntityInHibernateSession() + // Manual flush is required to avoid false positive in test + sessionFactory.getCurrentSession().flush() + } + + // ... +---- + +The following example shows matching methods for JPA: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // ... + + @PersistenceContext + EntityManager entityManager; + + @Transactional + @Test // no expected exception! + public void falsePositive() { + updateEntityInJpaPersistenceContext(); + // False positive: an exception will be thrown once the JPA + // EntityManager is finally flushed (i.e., in production code) + } + + @Transactional + @Test(expected = ...) + public void updateWithEntityManagerFlush() { + updateEntityInJpaPersistenceContext(); + // Manual flush is required to avoid false positive in test + entityManager.flush(); + } + + // ... +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // ... + + @PersistenceContext + lateinit var entityManager:EntityManager + + @Transactional + @Test // no expected exception! + fun falsePositive() { + updateEntityInJpaPersistenceContext() + // False positive: an exception will be thrown once the JPA + // EntityManager is finally flushed (i.e., in production code) + } + + @Transactional + @Test(expected = ...) + void updateWithEntityManagerFlush() { + updateEntityInJpaPersistenceContext() + // Manual flush is required to avoid false positive in test + entityManager.flush() + } + + // ... +---- +===== + +[[testcontext-tx-orm-lifecycle-callbacks]] +.Testing ORM entity lifecycle callbacks +[NOTE] +===== +Similar to the note about avoiding <> +when testing ORM code, if your application makes use of entity lifecycle callbacks (also +known as entity listeners), make sure to flush the underlying unit of work within test +methods that run that code. Failing to _flush_ or _clear_ the underlying unit of work can +result in certain lifecycle callbacks not being invoked. + +For example, when using JPA, `@PostPersist`, `@PreUpdate`, and `@PostUpdate` callbacks +will not be called unless `entityManager.flush()` is invoked after an entity has been +saved or updated. Similarly, if an entity is already attached to the current unit of work +(associated with the current persistence context), an attempt to reload the entity will +not result in a `@PostLoad` callback unless `entityManager.clear()` is invoked before the +attempt to reload the entity. + +The following example shows how to flush the `EntityManager` to ensure that +`@PostPersist` callbacks are invoked when an entity is persisted. An entity listener with +a `@PostPersist` callback method has been registered for the `Person` entity used in the +example. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // ... + + @Autowired + JpaPersonRepository repo; + + @PersistenceContext + EntityManager entityManager; + + @Transactional + @Test + void savePerson() { + // EntityManager#persist(...) results in @PrePersist but not @PostPersist + repo.save(new Person("Jane")); + + // Manual flush is required for @PostPersist callback to be invoked + entityManager.flush(); + + // Test code that relies on the @PostPersist callback + // having been invoked... + } + + // ... +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // ... + + @Autowired + lateinit var repo: JpaPersonRepository + + @PersistenceContext + lateinit var entityManager: EntityManager + + @Transactional + @Test + fun savePerson() { + // EntityManager#persist(...) results in @PrePersist but not @PostPersist + repo.save(Person("Jane")) + + // Manual flush is required for @PostPersist callback to be invoked + entityManager.flush() + + // Test code that relies on the @PostPersist callback + // having been invoked... + } + + // ... +---- + +See +https://github.com/spring-projects/spring-framework/blob/main/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/orm/JpaEntityListenerTests.java[JpaEntityListenerTests] +in the Spring Framework test suite for working examples using all JPA lifecycle callbacks. +===== + + +[[testcontext-executing-sql]] +== Executing SQL Scripts + +When writing integration tests against a relational database, it is often beneficial to +run SQL scripts to modify the database schema or insert test data into tables. The +`spring-jdbc` module provides support for _initializing_ an embedded or existing database +by executing SQL scripts when the Spring `ApplicationContext` is loaded. See +<> and +<> for details. + +Although it is very useful to initialize a database for testing _once_ when the +`ApplicationContext` is loaded, sometimes it is essential to be able to modify the +database _during_ integration tests. The following sections explain how to run SQL +scripts programmatically and declaratively during integration tests. + +[[testcontext-executing-sql-programmatically]] +=== Executing SQL scripts programmatically + +Spring provides the following options for executing SQL scripts programmatically within +integration test methods. + +* `org.springframework.jdbc.datasource.init.ScriptUtils` +* `org.springframework.jdbc.datasource.init.ResourceDatabasePopulator` +* `org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests` +* `org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests` + +`ScriptUtils` provides a collection of static utility methods for working with SQL +scripts and is mainly intended for internal use within the framework. However, if you +require full control over how SQL scripts are parsed and run, `ScriptUtils` may suit +your needs better than some of the other alternatives described later. See the +{api-spring-framework}/jdbc/datasource/init/ScriptUtils.html[javadoc] for individual +methods in `ScriptUtils` for further details. + +`ResourceDatabasePopulator` provides an object-based API for programmatically populating, +initializing, or cleaning up a database by using SQL scripts defined in external +resources. `ResourceDatabasePopulator` provides options for configuring the character +encoding, statement separator, comment delimiters, and error handling flags used when +parsing and running the scripts. Each of the configuration options has a reasonable +default value. See the +{api-spring-framework}/jdbc/datasource/init/ResourceDatabasePopulator.html[javadoc] for +details on default values. To run the scripts configured in a +`ResourceDatabasePopulator`, you can invoke either the `populate(Connection)` method to +run the populator against a `java.sql.Connection` or the `execute(DataSource)` method +to run the populator against a `javax.sql.DataSource`. The following example +specifies SQL scripts for a test schema and test data, sets the statement separator to +`@@`, and run the scripts against a `DataSource`: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Test + void databaseTest() { + ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); + populator.addScripts( + new ClassPathResource("test-schema.sql"), + new ClassPathResource("test-data.sql")); + populator.setSeparator("@@"); + populator.execute(this.dataSource); + // run code that uses the test schema and data + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Test + fun databaseTest() { + val populator = ResourceDatabasePopulator() + populator.addScripts( + ClassPathResource("test-schema.sql"), + ClassPathResource("test-data.sql")) + populator.setSeparator("@@") + populator.execute(dataSource) + // run code that uses the test schema and data + } +---- + +Note that `ResourceDatabasePopulator` internally delegates to `ScriptUtils` for parsing +and running SQL scripts. Similarly, the `executeSqlScript(..)` methods in +<> +and <> +internally use a `ResourceDatabasePopulator` to run SQL scripts. See the Javadoc for the +various `executeSqlScript(..)` methods for further details. + +[[testcontext-executing-sql-declaratively]] +=== Executing SQL scripts declaratively with @Sql + +In addition to the aforementioned mechanisms for running SQL scripts programmatically, +you can declaratively configure SQL scripts in the Spring TestContext Framework. +Specifically, you can declare the `@Sql` annotation on a test class or test method to +configure individual SQL statements or the resource paths to SQL scripts that should be +run against a given database before or after an integration test method. Support for +`@Sql` is provided by the `SqlScriptsTestExecutionListener`, which is enabled by default. + +NOTE: Method-level `@Sql` declarations override class-level declarations by default. As +of Spring Framework 5.2, however, this behavior may be configured per test class or per +test method via `@SqlMergeMode`. See +<> for further details. + +[[testcontext-executing-sql-declaratively-script-resources]] +==== Path Resource Semantics + +Each path is interpreted as a Spring `Resource`. A plain path (for example, +`"schema.sql"`) is treated as a classpath resource that is relative to the package in +which the test class is defined. A path starting with a slash is treated as an absolute +classpath resource (for example, `"/org/example/schema.sql"`). A path that references a +URL (for example, a path prefixed with `classpath:`, `file:`, `http:`) is loaded by using +the specified resource protocol. + +The following example shows how to use `@Sql` at the class level and at the method level +within a JUnit Jupiter based integration test class: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig + @Sql("/test-schema.sql") + class DatabaseTests { + + @Test + void emptySchemaTest() { + // run code that uses the test schema without any test data + } + + @Test + @Sql({"/test-schema.sql", "/test-user-data.sql"}) + void userTest() { + // run code that uses the test schema and test data + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig + @Sql("/test-schema.sql") + class DatabaseTests { + + @Test + fun emptySchemaTest() { + // run code that uses the test schema without any test data + } + + @Test + @Sql("/test-schema.sql", "/test-user-data.sql") + fun userTest() { + // run code that uses the test schema and test data + } + } +---- + +[[testcontext-executing-sql-declaratively-script-detection]] +==== Default Script Detection + +If no SQL scripts or statements are specified, an attempt is made to detect a `default` +script, depending on where `@Sql` is declared. If a default cannot be detected, an +`IllegalStateException` is thrown. + +* Class-level declaration: If the annotated test class is `com.example.MyTest`, the + corresponding default script is `classpath:com/example/MyTest.sql`. +* Method-level declaration: If the annotated test method is named `testMethod()` and is + defined in the class `com.example.MyTest`, the corresponding default script is + `classpath:com/example/MyTest.testMethod.sql`. + +[[testcontext-executing-sql-declaratively-multiple-annotations]] +==== Declaring Multiple `@Sql` Sets + +If you need to configure multiple sets of SQL scripts for a given test class or test +method but with different syntax configuration, different error handling rules, or +different execution phases per set, you can declare multiple instances of `@Sql`. With +Java 8, you can use `@Sql` as a repeatable annotation. Otherwise, you can use the +`@SqlGroup` annotation as an explicit container for declaring multiple instances of +`@Sql`. + +The following example shows how to use `@Sql` as a repeatable annotation with Java 8: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Test + @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")) + @Sql("/test-user-data.sql") + void userTest() { + // run code that uses the test schema and test data + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin +---- + +In the scenario presented in the preceding example, the `test-schema.sql` script uses a +different syntax for single-line comments. + +The following example is identical to the preceding example, except that the `@Sql` +declarations are grouped together within `@SqlGroup`. With Java 8 and above, the use of +`@SqlGroup` is optional, but you may need to use `@SqlGroup` for compatibility with +other JVM languages such as Kotlin. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Test + @SqlGroup({ + @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")), + @Sql("/test-user-data.sql") + )} + void userTest() { + // run code that uses the test schema and test data + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Test + @SqlGroup( + Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")), + Sql("/test-user-data.sql")) + fun userTest() { + // Run code that uses the test schema and test data + } +---- + +[[testcontext-executing-sql-declaratively-script-execution-phases]] +==== Script Execution Phases + +By default, SQL scripts are run before the corresponding test method. However, if +you need to run a particular set of scripts after the test method (for example, to clean +up database state), you can use the `executionPhase` attribute in `@Sql`, as the +following example shows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Test + @Sql( + scripts = "create-test-data.sql", + config = @SqlConfig(transactionMode = ISOLATED) + ) + @Sql( + scripts = "delete-test-data.sql", + config = @SqlConfig(transactionMode = ISOLATED), + executionPhase = AFTER_TEST_METHOD + ) + void userTest() { + // run code that needs the test data to be committed + // to the database outside of the test's transaction + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Test + @SqlGroup( + Sql("create-test-data.sql", + config = SqlConfig(transactionMode = ISOLATED)), + Sql("delete-test-data.sql", + config = SqlConfig(transactionMode = ISOLATED), + executionPhase = AFTER_TEST_METHOD)) + fun userTest() { + // run code that needs the test data to be committed + // to the database outside of the test's transaction + } +---- + +Note that `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from +`Sql.TransactionMode` and `Sql.ExecutionPhase`, respectively. + +[[testcontext-executing-sql-declaratively-script-configuration]] +==== Script Configuration with `@SqlConfig` + +You can configure script parsing and error handling by using the `@SqlConfig` annotation. +When declared as a class-level annotation on an integration test class, `@SqlConfig` +serves as global configuration for all SQL scripts within the test class hierarchy. When +declared directly by using the `config` attribute of the `@Sql` annotation, `@SqlConfig` +serves as local configuration for the SQL scripts declared within the enclosing `@Sql` +annotation. Every attribute in `@SqlConfig` has an implicit default value, which is +documented in the javadoc of the corresponding attribute. Due to the rules defined for +annotation attributes in the Java Language Specification, it is, unfortunately, not +possible to assign a value of `null` to an annotation attribute. Thus, in order to +support overrides of inherited global configuration, `@SqlConfig` attributes have an +explicit default value of either `""` (for Strings), `{}` (for arrays), or `DEFAULT` (for +enumerations). This approach lets local declarations of `@SqlConfig` selectively override +individual attributes from global declarations of `@SqlConfig` by providing a value other +than `""`, `{}`, or `DEFAULT`. Global `@SqlConfig` attributes are inherited whenever +local `@SqlConfig` attributes do not supply an explicit value other than `""`, `{}`, or +`DEFAULT`. Explicit local configuration, therefore, overrides global configuration. + +The configuration options provided by `@Sql` and `@SqlConfig` are equivalent to those +supported by `ScriptUtils` and `ResourceDatabasePopulator` but are a superset of those +provided by the `` XML namespace element. See the javadoc of +individual attributes in {api-spring-framework}/test/context/jdbc/Sql.html[`@Sql`] and +{api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] for details. + +[[testcontext-executing-sql-declaratively-tx]] +*Transaction management for `@Sql`* + +By default, the `SqlScriptsTestExecutionListener` infers the desired transaction +semantics for scripts configured by using `@Sql`. Specifically, SQL scripts are run +without a transaction, within an existing Spring-managed transaction (for example, a +transaction managed by the `TransactionalTestExecutionListener` for a test annotated with +`@Transactional`), or within an isolated transaction, depending on the configured value +of the `transactionMode` attribute in `@SqlConfig` and the presence of a +`PlatformTransactionManager` in the test's `ApplicationContext`. As a bare minimum, +however, a `javax.sql.DataSource` must be present in the test's `ApplicationContext`. + +If the algorithms used by `SqlScriptsTestExecutionListener` to detect a `DataSource` and +`PlatformTransactionManager` and infer the transaction semantics do not suit your needs, +you can specify explicit names by setting the `dataSource` and `transactionManager` +attributes of `@SqlConfig`. Furthermore, you can control the transaction propagation +behavior by setting the `transactionMode` attribute of `@SqlConfig` (for example, whether +scripts should be run in an isolated transaction). Although a thorough discussion of all +supported options for transaction management with `@Sql` is beyond the scope of this +reference manual, the javadoc for +{api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] and +{api-spring-framework}/test/context/jdbc/SqlScriptsTestExecutionListener.html[`SqlScriptsTestExecutionListener`] +provide detailed information, and the following example shows a typical testing scenario +that uses JUnit Jupiter and transactional tests with `@Sql`: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(TestDatabaseConfig.class) + @Transactional + class TransactionalSqlScriptsTests { + + final JdbcTemplate jdbcTemplate; + + @Autowired + TransactionalSqlScriptsTests(DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Test + @Sql("/test-data.sql") + void usersTest() { + // verify state in test database: + assertNumUsers(2); + // run code that uses the test data... + } + + int countRowsInTable(String tableName) { + return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); + } + + void assertNumUsers(int expected) { + assertEquals(expected, countRowsInTable("user"), + "Number of rows in the [user] table."); + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestDatabaseConfig::class) + @Transactional + class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) { + + val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource) + + @Test + @Sql("/test-data.sql") + fun usersTest() { + // verify state in test database: + assertNumUsers(2) + // run code that uses the test data... + } + + fun countRowsInTable(tableName: String): Int { + return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName) + } + + fun assertNumUsers(expected: Int) { + assertEquals(expected, countRowsInTable("user"), + "Number of rows in the [user] table.") + } + } +---- + +Note that there is no need to clean up the database after the `usersTest()` method is +run, since any changes made to the database (either within the test method or within the +`/test-data.sql` script) are automatically rolled back by the +`TransactionalTestExecutionListener` (see <> for +details). + +[[testcontext-executing-sql-declaratively-script-merging]] +==== Merging and Overriding Configuration with `@SqlMergeMode` + +As of Spring Framework 5.2, it is possible to merge method-level `@Sql` declarations with +class-level declarations. For example, this allows you to provide the configuration for a +database schema or some common test data once per test class and then provide additional, +use case specific test data per test method. To enable `@Sql` merging, annotate either +your test class or test method with `@SqlMergeMode(MERGE)`. To disable merging for a +specific test method (or specific test subclass), you can switch back to the default mode +via `@SqlMergeMode(OVERRIDE)`. Consult the <> for examples and further details. + + +[[testcontext-parallel-test-execution]] +== Parallel Test Execution + +Spring Framework 5.0 introduced basic support for executing tests in parallel within a +single JVM when using the Spring TestContext Framework. In general, this means that most +test classes or test methods can be run in parallel without any changes to test code +or configuration. + +TIP: For details on how to set up parallel test execution, see the documentation for your +testing framework, build tool, or IDE. + +Keep in mind that the introduction of concurrency into your test suite can result in +unexpected side effects, strange runtime behavior, and tests that fail intermittently or +seemingly randomly. The Spring Team therefore provides the following general guidelines +for when not to run tests in parallel. + +Do not run tests in parallel if the tests: + +* Use Spring Framework's `@DirtiesContext` support. +* Use Spring Boot's `@MockBean` or `@SpyBean` support. +* Use JUnit 4's `@FixMethodOrder` support or any testing framework feature + that is designed to ensure that test methods run in a particular order. Note, + however, that this does not apply if entire test classes are run in parallel. +* Change the state of shared services or systems such as a database, message broker, + filesystem, and others. This applies to both embedded and external systems. + +[TIP] +==== +If parallel test execution fails with an exception stating that the `ApplicationContext` +for the current test is no longer active, this typically means that the +`ApplicationContext` was removed from the `ContextCache` in a different thread. + +This may be due to the use of `@DirtiesContext` or due to automatic eviction from the +`ContextCache`. If `@DirtiesContext` is the culprit, you either need to find a way to +avoid using `@DirtiesContext` or exclude such tests from parallel execution. If the +maximum size of the `ContextCache` has been exceeded, you can increase the maximum size +of the cache. See the discussion on <> +for details. +==== + +WARNING: Parallel test execution in the Spring TestContext Framework is only possible if +the underlying `TestContext` implementation provides a copy constructor, as explained in +the javadoc for {api-spring-framework}/test/context/TestContext.html[`TestContext`]. The +`DefaultTestContext` used in Spring provides such a constructor. However, if you use a +third-party library that provides a custom `TestContext` implementation, you need to +verify that it is suitable for parallel test execution. + + +[[testcontext-support-classes]] +== TestContext Framework Support Classes + +This section describes the various classes that support the Spring TestContext Framework. + +[[testcontext-junit4-runner]] +=== Spring JUnit 4 Runner + +The Spring TestContext Framework offers full integration with JUnit 4 through a custom +runner (supported on JUnit 4.12 or higher). By annotating test classes with +`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)` +variant, developers can implement standard JUnit 4-based unit and integration tests and +simultaneously reap the benefits of the TestContext framework, such as support for +loading application contexts, dependency injection of test instances, transactional test +method execution, and so on. If you want to use the Spring TestContext Framework with an +alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners +(such as the `MockitoJUnitRunner`), you can, optionally, use +<> instead. + +The following code listing shows the minimal requirements for configuring a test class to +run with the custom Spring `Runner`: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @RunWith(SpringRunner.class) + @TestExecutionListeners({}) + public class SimpleTest { + + @Test + public void testMethod() { + // test logic... + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RunWith(SpringRunner::class) + @TestExecutionListeners + class SimpleTest { + + @Test + fun testMethod() { + // test logic... + } + } +---- + +In the preceding example, `@TestExecutionListeners` is configured with an empty list, to +disable the default listeners, which otherwise would require an `ApplicationContext` to +be configured through `@ContextConfiguration`. + +[[testcontext-junit4-rules]] +=== Spring JUnit 4 Rules + +The `org.springframework.test.context.junit4.rules` package provides the following JUnit +4 rules (supported on JUnit 4.12 or higher): + +* `SpringClassRule` +* `SpringMethodRule` + +`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring +TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports +instance-level and method-level features of the Spring TestContext Framework. + +In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of +being independent of any `org.junit.runner.Runner` implementation and can, therefore, be +combined with existing alternative runners (such as JUnit 4's `Parameterized`) or +third-party runners (such as the `MockitoJUnitRunner`). + +To support the full functionality of the TestContext framework, you must combine a +`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way +to declare these rules in an integration test: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // Optionally specify a non-Spring Runner via @RunWith(...) + @ContextConfiguration + public class IntegrationTest { + + @ClassRule + public static final SpringClassRule springClassRule = new SpringClassRule(); + + @Rule + public final SpringMethodRule springMethodRule = new SpringMethodRule(); + + @Test + public void testMethod() { + // test logic... + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Optionally specify a non-Spring Runner via @RunWith(...) + @ContextConfiguration + class IntegrationTest { + + @Rule + val springMethodRule = SpringMethodRule() + + @Test + fun testMethod() { + // test logic... + } + + companion object { + @ClassRule + val springClassRule = SpringClassRule() + } + } +---- + +[[testcontext-support-classes-junit4]] +=== JUnit 4 Support Classes + +The `org.springframework.test.context.junit4` package provides the following support +classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher): + +* `AbstractJUnit4SpringContextTests` +* `AbstractTransactionalJUnit4SpringContextTests` + +`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the +Spring TestContext Framework with explicit `ApplicationContext` testing support in a +JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a +`protected` `applicationContext` instance variable that you can use to perform explicit +bean lookups or to test the state of the context as a whole. + +`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of +`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC +access. This class expects a `javax.sql.DataSource` bean and a +`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you +extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected` +`jdbcTemplate` instance variable that you can use to run SQL statements to query the +database. You can use such queries to confirm database state both before and after +running database-related application code, and Spring ensures that such queries run in +the scope of the same transaction as the application code. When used in conjunction with +an ORM tool, be sure to avoid <>. +As mentioned in <>, +`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that +delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`. +Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an +`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`. + +TIP: These classes are a convenience for extension. If you do not want your test classes +to be tied to a Spring-specific class hierarchy, you can configure your own custom test +classes by using `@RunWith(SpringRunner.class)` or <>. + +[[testcontext-junit-jupiter-extension]] +=== SpringExtension for JUnit Jupiter + +The Spring TestContext Framework offers full integration with the JUnit Jupiter testing +framework, introduced in JUnit 5. By annotating test classes with +`@ExtendWith(SpringExtension.class)`, you can implement standard JUnit Jupiter-based unit +and integration tests and simultaneously reap the benefits of the TestContext framework, +such as support for loading application contexts, dependency injection of test instances, +transactional test method execution, and so on. + +Furthermore, thanks to the rich extension API in JUnit Jupiter, Spring provides the +following features above and beyond the feature set that Spring supports for JUnit 4 and +TestNG: + +* Dependency injection for test constructors, test methods, and test lifecycle callback + methods. See <> for further details. +* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional + test execution] based on SpEL expressions, environment variables, system properties, + and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in + <> for further details and examples. +* Custom composed annotations that combine annotations from Spring and JUnit Jupiter. See + the `@TransactionalDevTestConfig` and `@TransactionalIntegrationTest` examples in + <> for further details. + +The following code listing shows how to configure a test class to use the +`SpringExtension` in conjunction with `@ContextConfiguration`: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // Instructs JUnit Jupiter to extend the test with Spring support. + @ExtendWith(SpringExtension.class) + // Instructs Spring to load an ApplicationContext from TestConfig.class + @ContextConfiguration(classes = TestConfig.class) + class SimpleTests { + + @Test + void testMethod() { + // test logic... + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Instructs JUnit Jupiter to extend the test with Spring support. + @ExtendWith(SpringExtension::class) + // Instructs Spring to load an ApplicationContext from TestConfig::class + @ContextConfiguration(classes = [TestConfig::class]) + class SimpleTests { + + @Test + fun testMethod() { + // test logic... + } + } +---- + +Since you can also use annotations in JUnit 5 as meta-annotations, Spring provides the +`@SpringJUnitConfig` and `@SpringJUnitWebConfig` composed annotations to simplify the +configuration of the test `ApplicationContext` and JUnit Jupiter. + +The following example uses `@SpringJUnitConfig` to reduce the amount of configuration +used in the previous example: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // Instructs Spring to register the SpringExtension with JUnit + // Jupiter and load an ApplicationContext from TestConfig.class + @SpringJUnitConfig(TestConfig.class) + class SimpleTests { + + @Test + void testMethod() { + // test logic... + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Instructs Spring to register the SpringExtension with JUnit + // Jupiter and load an ApplicationContext from TestConfig.class + @SpringJUnitConfig(TestConfig::class) + class SimpleTests { + + @Test + fun testMethod() { + // test logic... + } + } +---- + +Similarly, the following example uses `@SpringJUnitWebConfig` to create a +`WebApplicationContext` for use with JUnit Jupiter: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + // Instructs Spring to register the SpringExtension with JUnit + // Jupiter and load a WebApplicationContext from TestWebConfig.class + @SpringJUnitWebConfig(TestWebConfig.class) + class SimpleWebTests { + + @Test + void testMethod() { + // test logic... + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + // Instructs Spring to register the SpringExtension with JUnit + // Jupiter and load a WebApplicationContext from TestWebConfig::class + @SpringJUnitWebConfig(TestWebConfig::class) + class SimpleWebTests { + + @Test + fun testMethod() { + // test logic... + } + } +---- + +See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in +<> for further details. + +[[testcontext-junit-jupiter-di]] +==== Dependency Injection with `SpringExtension` + +`SpringExtension` implements the +link:https://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`] +extension API from JUnit Jupiter, which lets Spring provide dependency injection for test +constructors, test methods, and test lifecycle callback methods. + +Specifically, `SpringExtension` can inject dependencies from the test's +`ApplicationContext` into test constructors and methods that are annotated with +`@BeforeAll`, `@AfterAll`, `@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, +`@ParameterizedTest`, and others. + +[[testcontext-junit-jupiter-di-constructor]] +===== Constructor Injection + +If a specific parameter in a constructor for a JUnit Jupiter test class is of type +`ApplicationContext` (or a sub-type thereof) or is annotated or meta-annotated with +`@Autowired`, `@Qualifier`, or `@Value`, Spring injects the value for that specific +parameter with the corresponding bean or value from the test's `ApplicationContext`. + +Spring can also be configured to autowire all arguments for a test class constructor if +the constructor is considered to be _autowirable_. A constructor is considered to be +autowirable if one of the following conditions is met (in order of precedence). + +* The constructor is annotated with `@Autowired`. +* `@TestConstructor` is present or meta-present on the test class with the `autowireMode` + attribute set to `ALL`. +* The default _test constructor autowire mode_ has been changed to `ALL`. + +See <> for details on the use of +`@TestConstructor` and how to change the global _test constructor autowire mode_. + +WARNING: If the constructor for a test class is considered to be _autowirable_, Spring +assumes the responsibility for resolving arguments for all parameters in the constructor. +Consequently, no other `ParameterResolver` registered with JUnit Jupiter can resolve +parameters for such a constructor. + +[WARNING] +==== +Constructor injection for test classes must not be used in conjunction with JUnit +Jupiter's `@TestInstance(PER_CLASS)` support if `@DirtiesContext` is used to close the +test's `ApplicationContext` before or after test methods. + +The reason is that `@TestInstance(PER_CLASS)` instructs JUnit Jupiter to cache the test +instance between test method invocations. Consequently, the test instance will retain +references to beans that were originally injected from an `ApplicationContext` that has +been subsequently closed. Since the constructor for the test class will only be invoked +once in such scenarios, dependency injection will not occur again, and subsequent tests +will interact with beans from the closed `ApplicationContext` which may result in errors. + +To use `@DirtiesContext` with "before test method" or "after test method" modes in +conjunction with `@TestInstance(PER_CLASS)`, one must configure dependencies from Spring +to be supplied via field or setter injection so that they can be re-injected between test +method invocations. +==== + +In the following example, Spring injects the `OrderService` bean from the +`ApplicationContext` loaded from `TestConfig.class` into the +`OrderServiceIntegrationTests` constructor. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(TestConfig.class) + class OrderServiceIntegrationTests { + + private final OrderService orderService; + + @Autowired + OrderServiceIntegrationTests(OrderService orderService) { + this.orderService = orderService; + } + + // tests that use the injected OrderService + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestConfig::class) + class OrderServiceIntegrationTests @Autowired constructor(private val orderService: OrderService){ + // tests that use the injected OrderService + } + +---- + +Note that this feature lets test dependencies be `final` and therefore immutable. + +If the `spring.test.constructor.autowire.mode` property is to `all` (see +<>), we can omit the declaration of +`@Autowired` on the constructor in the previous example, resulting in the following. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(TestConfig.class) + class OrderServiceIntegrationTests { + + private final OrderService orderService; + + OrderServiceIntegrationTests(OrderService orderService) { + this.orderService = orderService; + } + + // tests that use the injected OrderService + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestConfig::class) + class OrderServiceIntegrationTests(val orderService:OrderService) { + // tests that use the injected OrderService + } +---- + +[[testcontext-junit-jupiter-di-method]] +===== Method Injection + +If a parameter in a JUnit Jupiter test method or test lifecycle callback method is of +type `ApplicationContext` (or a sub-type thereof) or is annotated or meta-annotated with +`@Autowired`, `@Qualifier`, or `@Value`, Spring injects the value for that specific +parameter with the corresponding bean from the test's `ApplicationContext`. + +In the following example, Spring injects the `OrderService` from the `ApplicationContext` +loaded from `TestConfig.class` into the `deleteOrder()` test method: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(TestConfig.class) + class OrderServiceIntegrationTests { + + @Test + void deleteOrder(@Autowired OrderService orderService) { + // use orderService from the test's ApplicationContext + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestConfig::class) + class OrderServiceIntegrationTests { + + @Test + fun deleteOrder(@Autowired orderService: OrderService) { + // use orderService from the test's ApplicationContext + } + } +---- + +Due to the robustness of the `ParameterResolver` support in JUnit Jupiter, you can also +have multiple dependencies injected into a single method, not only from Spring but also +from JUnit Jupiter itself or other third-party extensions. + +The following example shows how to have both Spring and JUnit Jupiter inject dependencies +into the `placeOrderRepeatedly()` test method simultaneously. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(TestConfig.class) + class OrderServiceIntegrationTests { + + @RepeatedTest(10) + void placeOrderRepeatedly(RepetitionInfo repetitionInfo, + @Autowired OrderService orderService) { + + // use orderService from the test's ApplicationContext + // and repetitionInfo from JUnit Jupiter + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestConfig::class) + class OrderServiceIntegrationTests { + + @RepeatedTest(10) + fun placeOrderRepeatedly(repetitionInfo:RepetitionInfo, @Autowired orderService:OrderService) { + + // use orderService from the test's ApplicationContext + // and repetitionInfo from JUnit Jupiter + } + } +---- + +Note that the use of `@RepeatedTest` from JUnit Jupiter lets the test method gain access +to the `RepetitionInfo`. + +[[testcontext-junit-jupiter-nested-test-configuration]] +==== `@Nested` test class configuration + +The _Spring TestContext Framework_ has supported the use of test-related annotations on +`@Nested` test classes in JUnit Jupiter since Spring Framework 5.0; however, until Spring +Framework 5.3 class-level test configuration annotations were not _inherited_ from +enclosing classes like they are from superclasses. + +Spring Framework 5.3 introduces first-class support for inheriting test class +configuration from enclosing classes, and such configuration will be inherited by +default. To change from the default `INHERIT` mode to `OVERRIDE` mode, you may annotate +an individual `@Nested` test class with +`@NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)`. An explicit +`@NestedTestConfiguration` declaration will apply to the annotated test class as well as +any of its subclasses and nested classes. Thus, you may annotate a top-level test class +with `@NestedTestConfiguration`, and that will apply to all of its nested test classes +recursively. + +In order to allow development teams to change the default to `OVERRIDE` – for example, +for compatibility with Spring Framework 5.0 through 5.2 – the default mode can be changed +globally via a JVM system property or a `spring.properties` file in the root of the +classpath. See the <> note for details. + +Although the following "Hello World" example is very simplistic, it shows how to declare +common configuration on a top-level class that is inherited by its `@Nested` test +classes. In this particular example, only the `TestConfig` configuration class is +inherited. Each nested test class provides its own set of active profiles, resulting in a +distinct `ApplicationContext` for each nested test class (see +<> for details). Consult the list of +<> to see +which annotations can be inherited in `@Nested` test classes. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(TestConfig.class) + class GreetingServiceTests { + + @Nested + @ActiveProfiles("lang_en") + class EnglishGreetings { + + @Test + void hello(@Autowired GreetingService service) { + assertThat(service.greetWorld()).isEqualTo("Hello World"); + } + } + + @Nested + @ActiveProfiles("lang_de") + class GermanGreetings { + + @Test + void hello(@Autowired GreetingService service) { + assertThat(service.greetWorld()).isEqualTo("Hallo Welt"); + } + } + } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestConfig::class) + class GreetingServiceTests { + + @Nested + @ActiveProfiles("lang_en") + inner class EnglishGreetings { + + @Test + fun hello(@Autowired service:GreetingService) { + assertThat(service.greetWorld()).isEqualTo("Hello World") + } + } + + @Nested + @ActiveProfiles("lang_de") + inner class GermanGreetings { + + @Test + fun hello(@Autowired service:GreetingService) { + assertThat(service.greetWorld()).isEqualTo("Hallo Welt") + } + } + } +---- + +[[testcontext-support-classes-testng]] +=== TestNG Support Classes + +The `org.springframework.test.context.testng` package provides the following support +classes for TestNG based test cases: + +* `AbstractTestNGSpringContextTests` +* `AbstractTransactionalTestNGSpringContextTests` + +`AbstractTestNGSpringContextTests` is an abstract base test class that integrates the +Spring TestContext Framework with explicit `ApplicationContext` testing support in a +TestNG environment. When you extend `AbstractTestNGSpringContextTests`, you can access a +`protected` `applicationContext` instance variable that you can use to perform explicit +bean lookups or to test the state of the context as a whole. + +`AbstractTransactionalTestNGSpringContextTests` is an abstract transactional extension of +`AbstractTestNGSpringContextTests` that adds some convenience functionality for JDBC +access. This class expects a `javax.sql.DataSource` bean and a +`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you +extend `AbstractTransactionalTestNGSpringContextTests`, you can access a `protected` +`jdbcTemplate` instance variable that you can use to run SQL statements to query the +database. You can use such queries to confirm database state both before and after +running database-related application code, and Spring ensures that such queries run in +the scope of the same transaction as the application code. When used in conjunction with +an ORM tool, be sure to avoid <>. +As mentioned in <>, +`AbstractTransactionalTestNGSpringContextTests` also provides convenience methods that +delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`. +Furthermore, `AbstractTransactionalTestNGSpringContextTests` provides an +`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`. + +TIP: These classes are a convenience for extension. If you do not want your test classes +to be tied to a Spring-specific class hierarchy, you can configure your own custom test +classes by using `@ContextConfiguration`, `@TestExecutionListeners`, and so on and by +manually instrumenting your test class with a `TestContextManager`. See the source code +of `AbstractTestNGSpringContextTests` for an example of how to instrument your test class. + +[[testcontext-aot]] +== Ahead of Time Support for Tests + +This chapter covers Spring's Ahead of Time (AOT) support for integration tests using the +Spring TestContext Framework. + +The testing support extends Spring's <> with the +following features. + +* Build-time detection of all integration tests in the current project that use the + TestContext framework to load an `ApplicationContext`. + - Provides explicit support for test classes based on JUnit Jupiter and JUnit 4 as well + as implicit support for TestNG and other testing frameworks that use Spring's core + testing annotations -- as long as the tests are run using a JUnit Platform + `TestEngine` that is registered for the current project. +* Build-time AOT processing: each unique test `ApplicationContext` in the current project + will be <>. +* Runtime AOT support: when executing in AOT runtime mode, a Spring integration test will + use an AOT-optimized `ApplicationContext` that participates transparently with the + <>. + +[WARNING] +==== +The `@ContextHierarchy` annotation is currently not supported in AOT mode. +==== + +To provide test-specific runtime hints for use within a GraalVM native image, you have +the following options. + +* Implement a custom + {api-spring-framework}/test/context/aot/TestRuntimeHintsRegistrar.html[`TestRuntimeHintsRegistrar`] + and register it globally via `META-INF/spring/aot.factories`. +* Implement a custom {api-spring-framework}/aot/hint/RuntimeHintsRegistrar.html[`RuntimeHintsRegistrar`] + and register it globally via `META-INF/spring/aot.factories` or locally on a test class + via {api-spring-framework}/context/annotation/ImportRuntimeHints.html[`@ImportRuntimeHints`]. +* Annotate a test class with {api-spring-framework}/aot/hint/annotation/Reflective.html[`@Reflective`] or + {api-spring-framework}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`]. +* See <> for details on Spring's core runtime hints + and annotation support. + +[TIP] +==== +The `TestRuntimeHintsRegistrar` API serves as a companion to the core +`RuntimeHintsRegistrar` API. If you need to register global hints for testing support +that are not specific to particular test classes, favor implementing +`RuntimeHintsRegistrar` over the test-specific API. +==== + +If you implement a custom `ContextLoader`, it must implement +{api-spring-framework}/test/context/aot/AotContextLoader.html[`AotContextLoader`] in +order to provide AOT build-time processing and AOT runtime execution support. Note, +however, that all context loader implementations provided by the Spring Framework and +Spring Boot already implement `AotContextLoader`. + +If you implement a custom `TestExecutionListener`, it must implement +{api-spring-framework}/test/context/aot/AotTestExecutionListener.html[`AotTestExecutionListener`] +in order to participate in AOT processing. See the `SqlScriptsTestExecutionListener` in +the `spring-test` module for an example. diff --git a/framework-docs/src/docs/asciidoc/testing/testing-annotations.adoc b/framework-docs/src/docs/asciidoc/testing/testing-annotations.adoc new file mode 100644 index 000000000000..9e6edbeb9b45 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/testing/testing-annotations.adoc @@ -0,0 +1,1930 @@ +[[integration-testing-annotations]] += Annotations + +This section covers annotations that you can use when you test Spring applications. +It includes the following topics: + +* <> +* <> +* <> +* <> +* <> + + + +[[integration-testing-annotations-standard]] +== Standard Annotation Support + +The following annotations are supported with standard semantics for all configurations of +the Spring TestContext Framework. Note that these annotations are not specific to tests +and can be used anywhere in the Spring Framework. + +* `@Autowired` +* `@Qualifier` +* `@Value` +* `@Resource` (jakarta.annotation) if JSR-250 is present +* `@ManagedBean` (jakarta.annotation) if JSR-250 is present +* `@Inject` (jakarta.inject) if JSR-330 is present +* `@Named` (jakarta.inject) if JSR-330 is present +* `@PersistenceContext` (jakarta.persistence) if JPA is present +* `@PersistenceUnit` (jakarta.persistence) if JPA is present +* `@Transactional` (org.springframework.transaction.annotation) + _with <>_ + +.JSR-250 Lifecycle Annotations +[NOTE] +==== +In the Spring TestContext Framework, you can use `@PostConstruct` and `@PreDestroy` with +standard semantics on any application components configured in the `ApplicationContext`. +However, these lifecycle annotations have limited usage within an actual test class. + +If a method within a test class is annotated with `@PostConstruct`, that method runs +before any before methods of the underlying test framework (for example, methods +annotated with JUnit Jupiter's `@BeforeEach`), and that applies for every test method in +the test class. On the other hand, if a method within a test class is annotated with +`@PreDestroy`, that method never runs. Therefore, within a test class, we recommend that +you use test lifecycle callbacks from the underlying test framework instead of +`@PostConstruct` and `@PreDestroy`. +==== + + + +[[integration-testing-annotations-spring]] +== Spring Testing Annotations + +The Spring Framework provides the following set of Spring-specific annotations that you +can use in your unit and integration tests in conjunction with the TestContext framework. +See the corresponding javadoc for further information, including default attribute +values, attribute aliases, and other details. + +Spring's testing annotations include the following: + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> + +[[spring-testing-annotation-bootstrapwith]] +=== `@BootstrapWith` + +`@BootstrapWith` is a class-level annotation that you can use to configure how the Spring +TestContext Framework is bootstrapped. Specifically, you can use `@BootstrapWith` to +specify a custom `TestContextBootstrapper`. See the section on +<> for further details. + +[[spring-testing-annotation-contextconfiguration]] +=== `@ContextConfiguration` + +`@ContextConfiguration` defines class-level metadata that is used to determine how to +load and configure an `ApplicationContext` for integration tests. Specifically, +`@ContextConfiguration` declares the application context resource `locations` or the +component `classes` used to load the context. + +Resource locations are typically XML configuration files or Groovy scripts located in the +classpath, while component classes are typically `@Configuration` classes. However, +resource locations can also refer to files and scripts in the file system, and component +classes can be `@Component` classes, `@Service` classes, and so on. See +<> for further details. + +The following example shows a `@ContextConfiguration` annotation that refers to an XML +file: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration("/test-config.xml") // <1> + class XmlApplicationContextTests { + // class body... + } +---- +<1> Referring to an XML file. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration("/test-config.xml") // <1> + class XmlApplicationContextTests { + // class body... + } +---- +<1> Referring to an XML file. + + +The following example shows a `@ContextConfiguration` annotation that refers to a class: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration(classes = TestConfig.class) // <1> + class ConfigClassApplicationContextTests { + // class body... + } +---- +<1> Referring to a class. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration(classes = [TestConfig::class]) // <1> + class ConfigClassApplicationContextTests { + // class body... + } +---- +<1> Referring to a class. + + +As an alternative or in addition to declaring resource locations or component classes, +you can use `@ContextConfiguration` to declare `ApplicationContextInitializer` classes. +The following example shows such a case: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration(initializers = CustomContextInitializer.class) // <1> + class ContextInitializerTests { + // class body... + } +---- +<1> Declaring an initializer class. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration(initializers = [CustomContextInitializer::class]) // <1> + class ContextInitializerTests { + // class body... + } +---- +<1> Declaring an initializer class. + + +You can optionally use `@ContextConfiguration` to declare the `ContextLoader` strategy as +well. Note, however, that you typically do not need to explicitly configure the loader, +since the default loader supports `initializers` and either resource `locations` or +component `classes`. + +The following example uses both a location and a loader: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) // <1> + class CustomLoaderXmlApplicationContextTests { + // class body... + } +---- +<1> Configuring both a location and a custom loader. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration("/test-context.xml", loader = CustomContextLoader::class) // <1> + class CustomLoaderXmlApplicationContextTests { + // class body... + } +---- +<1> Configuring both a location and a custom loader. + + +NOTE: `@ContextConfiguration` provides support for inheriting resource locations or +configuration classes as well as context initializers that are declared by superclasses +or enclosing classes. + +See <>, +<>, and the `@ContextConfiguration` +javadocs for further details. + +[[spring-testing-annotation-webappconfiguration]] +=== `@WebAppConfiguration` + +`@WebAppConfiguration` is a class-level annotation that you can use to declare that the +`ApplicationContext` loaded for an integration test should be a `WebApplicationContext`. +The mere presence of `@WebAppConfiguration` on a test class ensures that a +`WebApplicationContext` is loaded for the test, using the default value of +`"file:src/main/webapp"` for the path to the root of the web application (that is, the +resource base path). The resource base path is used behind the scenes to create a +`MockServletContext`, which serves as the `ServletContext` for the test's +`WebApplicationContext`. + +The following example shows how to use the `@WebAppConfiguration` annotation: + +-- +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @WebAppConfiguration // <1> + class WebAppTests { + // class body... + } +---- +<1> The `@WebAppConfiguration` annotation. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @WebAppConfiguration // <1> + class WebAppTests { + // class body... + } +---- +<1> The `@WebAppConfiguration` annotation. +-- + + +To override the default, you can specify a different base resource path by using the +implicit `value` attribute. Both `classpath:` and `file:` resource prefixes are +supported. If no resource prefix is supplied, the path is assumed to be a file system +resource. The following example shows how to specify a classpath resource: + +-- +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @WebAppConfiguration("classpath:test-web-resources") // <1> + class WebAppTests { + // class body... + } +---- +<1> Specifying a classpath resource. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @WebAppConfiguration("classpath:test-web-resources") // <1> + class WebAppTests { + // class body... + } +---- +<1> Specifying a classpath resource. +-- + + +Note that `@WebAppConfiguration` must be used in conjunction with +`@ContextConfiguration`, either within a single test class or within a test class +hierarchy. See the +{api-spring-framework}/test/context/web/WebAppConfiguration.html[`@WebAppConfiguration`] +javadoc for further details. + +[[spring-testing-annotation-contexthierarchy]] +=== `@ContextHierarchy` + +`@ContextHierarchy` is a class-level annotation that is used to define a hierarchy of +`ApplicationContext` instances for integration tests. `@ContextHierarchy` should be +declared with a list of one or more `@ContextConfiguration` instances, each of which +defines a level in the context hierarchy. The following examples demonstrate the use of +`@ContextHierarchy` within a single test class (`@ContextHierarchy` can also be used +within a test class hierarchy): + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextHierarchy({ + @ContextConfiguration("/parent-config.xml"), + @ContextConfiguration("/child-config.xml") + }) + class ContextHierarchyTests { + // class body... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextHierarchy( + ContextConfiguration("/parent-config.xml"), + ContextConfiguration("/child-config.xml")) + class ContextHierarchyTests { + // class body... + } +---- + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @WebAppConfiguration + @ContextHierarchy({ + @ContextConfiguration(classes = AppConfig.class), + @ContextConfiguration(classes = WebConfig.class) + }) + class WebIntegrationTests { + // class body... + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @WebAppConfiguration + @ContextHierarchy( + ContextConfiguration(classes = [AppConfig::class]), + ContextConfiguration(classes = [WebConfig::class])) + class WebIntegrationTests { + // class body... + } +---- + +If you need to merge or override the configuration for a given level of the context +hierarchy within a test class hierarchy, you must explicitly name that level by supplying +the same value to the `name` attribute in `@ContextConfiguration` at each corresponding +level in the class hierarchy. See <> and the +{api-spring-framework}/test/context/ContextHierarchy.html[`@ContextHierarchy`] javadoc +for further examples. + +[[spring-testing-annotation-activeprofiles]] +=== `@ActiveProfiles` + +`@ActiveProfiles` is a class-level annotation that is used to declare which bean +definition profiles should be active when loading an `ApplicationContext` for an +integration test. + +The following example indicates that the `dev` profile should be active: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @ActiveProfiles("dev") // <1> + class DeveloperTests { + // class body... + } +---- +<1> Indicate that the `dev` profile should be active. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @ActiveProfiles("dev") // <1> + class DeveloperTests { + // class body... + } +---- +<1> Indicate that the `dev` profile should be active. + + +The following example indicates that both the `dev` and the `integration` profiles should +be active: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @ActiveProfiles({"dev", "integration"}) // <1> + class DeveloperIntegrationTests { + // class body... + } +---- +<1> Indicate that the `dev` and `integration` profiles should be active. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @ActiveProfiles(["dev", "integration"]) // <1> + class DeveloperIntegrationTests { + // class body... + } +---- +<1> Indicate that the `dev` and `integration` profiles should be active. + + +NOTE: `@ActiveProfiles` provides support for inheriting active bean definition profiles +declared by superclasses and enclosing classes by default. You can also resolve active +bean definition profiles programmatically by implementing a custom +<> +and registering it by using the `resolver` attribute of `@ActiveProfiles`. + +See <>, +<>, and the +{api-spring-framework}/test/context/ActiveProfiles.html[`@ActiveProfiles`] javadoc for +examples and further details. + +[[spring-testing-annotation-testpropertysource]] +=== `@TestPropertySource` + +`@TestPropertySource` is a class-level annotation that you can use to configure the +locations of properties files and inlined properties to be added to the set of +`PropertySources` in the `Environment` for an `ApplicationContext` loaded for an +integration test. + +The following example demonstrates how to declare a properties file from the classpath: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @TestPropertySource("/test.properties") // <1> + class MyIntegrationTests { + // class body... + } +---- +<1> Get properties from `test.properties` in the root of the classpath. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @TestPropertySource("/test.properties") // <1> + class MyIntegrationTests { + // class body... + } +---- +<1> Get properties from `test.properties` in the root of the classpath. + + +The following example demonstrates how to declare inlined properties: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) // <1> + class MyIntegrationTests { + // class body... + } +---- +<1> Declare `timezone` and `port` properties. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) // <1> + class MyIntegrationTests { + // class body... + } +---- +<1> Declare `timezone` and `port` properties. + +See <> for examples and further details. + +[[spring-testing-annotation-dynamicpropertysource]] +=== `@DynamicPropertySource` + +`@DynamicPropertySource` is a method-level annotation that you can use to register +_dynamic_ properties to be added to the set of `PropertySources` in the `Environment` for +an `ApplicationContext` loaded for an integration test. Dynamic properties are useful +when you do not know the value of the properties upfront – for example, if the properties +are managed by an external resource such as for a container managed by the +https://www.testcontainers.org/[Testcontainers] project. + +The following example demonstrates how to register a dynamic property: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + class MyIntegrationTests { + + static MyExternalServer server = // ... + + @DynamicPropertySource // <1> + static void dynamicProperties(DynamicPropertyRegistry registry) { // <2> + registry.add("server.port", server::getPort); // <3> + } + + // tests ... + } +---- +<1> Annotate a `static` method with `@DynamicPropertySource`. +<2> Accept a `DynamicPropertyRegistry` as an argument. +<3> Register a dynamic `server.port` property to be retrieved lazily from the server. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + class MyIntegrationTests { + + companion object { + + @JvmStatic + val server: MyExternalServer = // ... + + @DynamicPropertySource // <1> + @JvmStatic + fun dynamicProperties(registry: DynamicPropertyRegistry) { // <2> + registry.add("server.port", server::getPort) // <3> + } + } + + // tests ... + } +---- +<1> Annotate a `static` method with `@DynamicPropertySource`. +<2> Accept a `DynamicPropertyRegistry` as an argument. +<3> Register a dynamic `server.port` property to be retrieved lazily from the server. + +See <> for further details. + +[[spring-testing-annotation-dirtiescontext]] +=== `@DirtiesContext` + +`@DirtiesContext` indicates that the underlying Spring `ApplicationContext` has been +dirtied during the execution of a test (that is, the test modified or corrupted it in +some manner -- for example, by changing the state of a singleton bean) and should be +closed. When an application context is marked as dirty, it is removed from the testing +framework's cache and closed. As a consequence, the underlying Spring container is +rebuilt for any subsequent test that requires a context with the same configuration +metadata. + +You can use `@DirtiesContext` as both a class-level and a method-level annotation within +the same class or class hierarchy. In such scenarios, the `ApplicationContext` is marked +as dirty before or after any such annotated method as well as before or after the current +test class, depending on the configured `methodMode` and `classMode`. + +The following examples explain when the context would be dirtied for various +configuration scenarios: + +* Before the current test class, when declared on a class with class mode set to +`BEFORE_CLASS`. ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @DirtiesContext(classMode = BEFORE_CLASS) // <1> + class FreshContextTests { + // some tests that require a new Spring container + } +---- +<1> Dirty the context before the current test class. ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @DirtiesContext(classMode = BEFORE_CLASS) // <1> + class FreshContextTests { + // some tests that require a new Spring container + } +---- +<1> Dirty the context before the current test class. + +* After the current test class, when declared on a class with class mode set to +`AFTER_CLASS` (i.e., the default class mode). ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @DirtiesContext // <1> + class ContextDirtyingTests { + // some tests that result in the Spring container being dirtied + } +---- +<1> Dirty the context after the current test class. ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @DirtiesContext // <1> + class ContextDirtyingTests { + // some tests that result in the Spring container being dirtied + } +---- +<1> Dirty the context after the current test class. + + +* Before each test method in the current test class, when declared on a class with class +mode set to `BEFORE_EACH_TEST_METHOD.` ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) // <1> + class FreshContextTests { + // some tests that require a new Spring container + } +---- +<1> Dirty the context before each test method. ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) // <1> + class FreshContextTests { + // some tests that require a new Spring container + } +---- +<1> Dirty the context before each test method. + + +* After each test method in the current test class, when declared on a class with class +mode set to `AFTER_EACH_TEST_METHOD.` ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) // <1> + class ContextDirtyingTests { + // some tests that result in the Spring container being dirtied + } +---- +<1> Dirty the context after each test method. ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) // <1> + class ContextDirtyingTests { + // some tests that result in the Spring container being dirtied + } +---- +<1> Dirty the context after each test method. + + +* Before the current test, when declared on a method with the method mode set to +`BEFORE_METHOD`. ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @DirtiesContext(methodMode = BEFORE_METHOD) // <1> + @Test + void testProcessWhichRequiresFreshAppCtx() { + // some logic that requires a new Spring container + } +---- +<1> Dirty the context before the current test method. ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @DirtiesContext(methodMode = BEFORE_METHOD) // <1> + @Test + fun testProcessWhichRequiresFreshAppCtx() { + // some logic that requires a new Spring container + } +---- +<1> Dirty the context before the current test method. + +* After the current test, when declared on a method with the method mode set to +`AFTER_METHOD` (i.e., the default method mode). ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @DirtiesContext // <1> + @Test + void testProcessWhichDirtiesAppCtx() { + // some logic that results in the Spring container being dirtied + } +---- +<1> Dirty the context after the current test method. ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @DirtiesContext // <1> + @Test + fun testProcessWhichDirtiesAppCtx() { + // some logic that results in the Spring container being dirtied + } +---- +<1> Dirty the context after the current test method. + + +If you use `@DirtiesContext` in a test whose context is configured as part of a context +hierarchy with `@ContextHierarchy`, you can use the `hierarchyMode` flag to control how +the context cache is cleared. By default, an exhaustive algorithm is used to clear the +context cache, including not only the current level but also all other context +hierarchies that share an ancestor context common to the current test. All +`ApplicationContext` instances that reside in a sub-hierarchy of the common ancestor +context are removed from the context cache and closed. If the exhaustive algorithm is +overkill for a particular use case, you can specify the simpler current level algorithm, +as the following example shows. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextHierarchy({ + @ContextConfiguration("/parent-config.xml"), + @ContextConfiguration("/child-config.xml") + }) + class BaseTests { + // class body... + } + + class ExtendedTests extends BaseTests { + + @Test + @DirtiesContext(hierarchyMode = CURRENT_LEVEL) // <1> + void test() { + // some logic that results in the child context being dirtied + } + } +---- +<1> Use the current-level algorithm. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextHierarchy( + ContextConfiguration("/parent-config.xml"), + ContextConfiguration("/child-config.xml")) + open class BaseTests { + // class body... + } + + class ExtendedTests : BaseTests() { + + @Test + @DirtiesContext(hierarchyMode = CURRENT_LEVEL) // <1> + fun test() { + // some logic that results in the child context being dirtied + } + } +---- +<1> Use the current-level algorithm. + + +For further details regarding the `EXHAUSTIVE` and `CURRENT_LEVEL` algorithms, see the +{api-spring-framework}/test/annotation/DirtiesContext.HierarchyMode.html[`DirtiesContext.HierarchyMode`] +javadoc. + +[[spring-testing-annotation-testexecutionlisteners]] +=== `@TestExecutionListeners` + +`@TestExecutionListeners` is used to register listeners for a particular test class, its +subclasses, and its nested classes. If you wish to register a listener globally, you +should register it via the automatic discovery mechanism described in +<>. + +The following example shows how to register two `TestExecutionListener` implementations: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ContextConfiguration + @TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) // <1> + class CustomTestExecutionListenerTests { + // class body... + } +---- +<1> Register two `TestExecutionListener` implementations. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ContextConfiguration + @TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) // <1> + class CustomTestExecutionListenerTests { + // class body... + } +---- +<1> Register two `TestExecutionListener` implementations. + + +By default, `@TestExecutionListeners` provides support for inheriting listeners from +superclasses or enclosing classes. See +<> and the +{api-spring-framework}/test/context/TestExecutionListeners.html[`@TestExecutionListeners` +javadoc] for an example and further details. If you discover that you need to switch +back to using the default `TestExecutionListener` implementations, see the note +in <>. + +[[spring-testing-annotation-recordapplicationevents]] +=== `@RecordApplicationEvents` + +`@RecordApplicationEvents` is a class-level annotation that is used to instruct the +_Spring TestContext Framework_ to record all application events that are published in the +`ApplicationContext` during the execution of a single test. + +The recorded events can be accessed via the `ApplicationEvents` API within tests. + +See <> and the +{api-spring-framework}/test/context/event/RecordApplicationEvents.html[`@RecordApplicationEvents` +javadoc] for an example and further details. + +[[spring-testing-annotation-commit]] +=== `@Commit` + +`@Commit` indicates that the transaction for a transactional test method should be +committed after the test method has completed. You can use `@Commit` as a direct +replacement for `@Rollback(false)` to more explicitly convey the intent of the code. +Analogous to `@Rollback`, `@Commit` can also be declared as a class-level or method-level +annotation. + +The following example shows how to use the `@Commit` annotation: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Commit // <1> + @Test + void testProcessWithoutRollback() { + // ... + } +---- +<1> Commit the result of the test to the database. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Commit // <1> + @Test + fun testProcessWithoutRollback() { + // ... + } +---- +<1> Commit the result of the test to the database. + + +[[spring-testing-annotation-rollback]] +=== `@Rollback` + +`@Rollback` indicates whether the transaction for a transactional test method should be +rolled back after the test method has completed. If `true`, the transaction is rolled +back. Otherwise, the transaction is committed (see also +<>). Rollback for integration tests in the Spring +TestContext Framework defaults to `true` even if `@Rollback` is not explicitly declared. + +When declared as a class-level annotation, `@Rollback` defines the default rollback +semantics for all test methods within the test class hierarchy. When declared as a +method-level annotation, `@Rollback` defines rollback semantics for the specific test +method, potentially overriding class-level `@Rollback` or `@Commit` semantics. + +The following example causes a test method's result to not be rolled back (that is, the +result is committed to the database): + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Rollback(false) // <1> + @Test + void testProcessWithoutRollback() { + // ... + } +---- +<1> Do not roll back the result. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Rollback(false) // <1> + @Test + fun testProcessWithoutRollback() { + // ... + } +---- +<1> Do not roll back the result. + + +[[spring-testing-annotation-beforetransaction]] +=== `@BeforeTransaction` + +`@BeforeTransaction` indicates that the annotated `void` method should be run before a +transaction is started, for test methods that have been configured to run within a +transaction by using Spring's `@Transactional` annotation. `@BeforeTransaction` methods +are not required to be `public` and may be declared on Java 8-based interface default +methods. + +The following example shows how to use the `@BeforeTransaction` annotation: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @BeforeTransaction // <1> + void beforeTransaction() { + // logic to be run before a transaction is started + } +---- +<1> Run this method before a transaction. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @BeforeTransaction // <1> + fun beforeTransaction() { + // logic to be run before a transaction is started + } +---- +<1> Run this method before a transaction. + + +[[spring-testing-annotation-aftertransaction]] +=== `@AfterTransaction` + +`@AfterTransaction` indicates that the annotated `void` method should be run after a +transaction is ended, for test methods that have been configured to run within a +transaction by using Spring's `@Transactional` annotation. `@AfterTransaction` methods +are not required to be `public` and may be declared on Java 8-based interface default +methods. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @AfterTransaction // <1> + void afterTransaction() { + // logic to be run after a transaction has ended + } +---- +<1> Run this method after a transaction. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @AfterTransaction // <1> + fun afterTransaction() { + // logic to be run after a transaction has ended + } +---- +<1> Run this method after a transaction. + + +[[spring-testing-annotation-sql]] +=== `@Sql` + +`@Sql` is used to annotate a test class or test method to configure SQL scripts to be run +against a given database during integration tests. The following example shows how to use +it: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Test + @Sql({"/test-schema.sql", "/test-user-data.sql"}) // <1> + void userTest() { + // run code that relies on the test schema and test data + } +---- +<1> Run two scripts for this test. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Test + @Sql("/test-schema.sql", "/test-user-data.sql") // <1> + fun userTest() { + // run code that relies on the test schema and test data + } +---- +<1> Run two scripts for this test. + +See <> for further details. + + +[[spring-testing-annotation-sqlconfig]] +=== `@SqlConfig` + +`@SqlConfig` defines metadata that is used to determine how to parse and run SQL scripts +configured with the `@Sql` annotation. The following example shows how to use it: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Test + @Sql( + scripts = "/test-user-data.sql", + config = @SqlConfig(commentPrefix = "`", separator = "@@") // <1> + ) + void userTest() { + // run code that relies on the test data + } +---- +<1> Set the comment prefix and the separator in SQL scripts. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Test + @Sql("/test-user-data.sql", config = SqlConfig(commentPrefix = "`", separator = "@@")) // <1> + fun userTest() { + // run code that relies on the test data + } +---- +<1> Set the comment prefix and the separator in SQL scripts. + +[[spring-testing-annotation-sqlmergemode]] +=== `@SqlMergeMode` + +`@SqlMergeMode` is used to annotate a test class or test method to configure whether +method-level `@Sql` declarations are merged with class-level `@Sql` declarations. If +`@SqlMergeMode` is not declared on a test class or test method, the `OVERRIDE` merge mode +will be used by default. With the `OVERRIDE` mode, method-level `@Sql` declarations will +effectively override class-level `@Sql` declarations. + +Note that a method-level `@SqlMergeMode` declaration overrides a class-level declaration. + +The following example shows how to use `@SqlMergeMode` at the class level. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(TestConfig.class) + @Sql("/test-schema.sql") + @SqlMergeMode(MERGE) // <1> + class UserTests { + + @Test + @Sql("/user-test-data-001.sql") + void standardUserProfile() { + // run code that relies on test data set 001 + } + } +---- +<1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestConfig::class) + @Sql("/test-schema.sql") + @SqlMergeMode(MERGE) // <1> + class UserTests { + + @Test + @Sql("/user-test-data-001.sql") + fun standardUserProfile() { + // run code that relies on test data set 001 + } + } +---- +<1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class. + +The following example shows how to use `@SqlMergeMode` at the method level. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(TestConfig.class) + @Sql("/test-schema.sql") + class UserTests { + + @Test + @Sql("/user-test-data-001.sql") + @SqlMergeMode(MERGE) // <1> + void standardUserProfile() { + // run code that relies on test data set 001 + } + } +---- +<1> Set the `@Sql` merge mode to `MERGE` for a specific test method. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestConfig::class) + @Sql("/test-schema.sql") + class UserTests { + + @Test + @Sql("/user-test-data-001.sql") + @SqlMergeMode(MERGE) // <1> + fun standardUserProfile() { + // run code that relies on test data set 001 + } + } +---- +<1> Set the `@Sql` merge mode to `MERGE` for a specific test method. + + +[[spring-testing-annotation-sqlgroup]] +=== `@SqlGroup` + +`@SqlGroup` is a container annotation that aggregates several `@Sql` annotations. You can +use `@SqlGroup` natively to declare several nested `@Sql` annotations, or you can use it +in conjunction with Java 8's support for repeatable annotations, where `@Sql` can be +declared several times on the same class or method, implicitly generating this container +annotation. The following example shows how to declare an SQL group: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Test + @SqlGroup({ // <1> + @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")), + @Sql("/test-user-data.sql") + }) + void userTest() { + // run code that uses the test schema and test data + } +---- +<1> Declare a group of SQL scripts. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Test + @SqlGroup( // <1> + Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")), + Sql("/test-user-data.sql")) + fun userTest() { + // run code that uses the test schema and test data + } +---- +<1> Declare a group of SQL scripts. + + + +[[integration-testing-annotations-junit4]] +== Spring JUnit 4 Testing Annotations + +The following annotations are supported only when used in conjunction with the +<>, <>, or <>: + +* <> +* <> +* <> +* <> + +[[integration-testing-annotations-junit4-ifprofilevalue]] +=== `@IfProfileValue` + +`@IfProfileValue` indicates that the annotated test is enabled for a specific testing +environment. If the configured `ProfileValueSource` returns a matching `value` for the +provided `name`, the test is enabled. Otherwise, the test is disabled and, effectively, +ignored. + +You can apply `@IfProfileValue` at the class level, the method level, or both. +Class-level usage of `@IfProfileValue` takes precedence over method-level usage for any +methods within that class or its subclasses. Specifically, a test is enabled if it is +enabled both at the class level and at the method level. The absence of `@IfProfileValue` +means the test is implicitly enabled. This is analogous to the semantics of JUnit 4's +`@Ignore` annotation, except that the presence of `@Ignore` always disables a test. + +The following example shows a test that has an `@IfProfileValue` annotation: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @IfProfileValue(name="java.vendor", value="Oracle Corporation") // <1> + @Test + public void testProcessWhichRunsOnlyOnOracleJvm() { + // some logic that should run only on Java VMs from Oracle Corporation + } +---- +<1> Run this test only when the Java vendor is "Oracle Corporation". + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @IfProfileValue(name="java.vendor", value="Oracle Corporation") // <1> + @Test + fun testProcessWhichRunsOnlyOnOracleJvm() { + // some logic that should run only on Java VMs from Oracle Corporation + } +---- +<1> Run this test only when the Java vendor is "Oracle Corporation". + + +Alternatively, you can configure `@IfProfileValue` with a list of `values` (with `OR` +semantics) to achieve TestNG-like support for test groups in a JUnit 4 environment. +Consider the following example: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) // <1> + @Test + public void testProcessWhichRunsForUnitOrIntegrationTestGroups() { + // some logic that should run only for unit and integration test groups + } +---- +<1> Run this test for unit tests and integration tests. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @IfProfileValue(name="test-groups", values=["unit-tests", "integration-tests"]) // <1> + @Test + fun testProcessWhichRunsForUnitOrIntegrationTestGroups() { + // some logic that should run only for unit and integration test groups + } +---- +<1> Run this test for unit tests and integration tests. + + +[[integration-testing-annotations-junit4-profilevaluesourceconfiguration]] +=== `@ProfileValueSourceConfiguration` + +`@ProfileValueSourceConfiguration` is a class-level annotation that specifies what type +of `ProfileValueSource` to use when retrieving profile values configured through the +`@IfProfileValue` annotation. If `@ProfileValueSourceConfiguration` is not declared for a +test, `SystemProfileValueSource` is used by default. The following example shows how to +use `@ProfileValueSourceConfiguration`: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ProfileValueSourceConfiguration(CustomProfileValueSource.class) // <1> + public class CustomProfileValueSourceTests { + // class body... + } +---- +<1> Use a custom profile value source. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ProfileValueSourceConfiguration(CustomProfileValueSource::class) // <1> + class CustomProfileValueSourceTests { + // class body... + } +---- +<1> Use a custom profile value source. + + +[[integration-testing-annotations-junit4-timed]] +=== `@Timed` + +`@Timed` indicates that the annotated test method must finish execution in a specified +time period (in milliseconds). If the text execution time exceeds the specified time +period, the test fails. + +The time period includes running the test method itself, any repetitions of the test (see +`@Repeat`), as well as any setting up or tearing down of the test fixture. The following +example shows how to use it: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Timed(millis = 1000) // <1> + public void testProcessWithOneSecondTimeout() { + // some logic that should not take longer than 1 second to run + } +---- +<1> Set the time period for the test to one second. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Timed(millis = 1000) // <1> + fun testProcessWithOneSecondTimeout() { + // some logic that should not take longer than 1 second to run + } +---- +<1> Set the time period for the test to one second. + + +Spring's `@Timed` annotation has different semantics than JUnit 4's `@Test(timeout=...)` +support. Specifically, due to the manner in which JUnit 4 handles test execution timeouts +(that is, by executing the test method in a separate `Thread`), `@Test(timeout=...)` +preemptively fails the test if the test takes too long. Spring's `@Timed`, on the other +hand, does not preemptively fail the test but rather waits for the test to complete +before failing. + +[[integration-testing-annotations-junit4-repeat]] +=== `@Repeat` + +`@Repeat` indicates that the annotated test method must be run repeatedly. The number of +times that the test method is to be run is specified in the annotation. + +The scope of execution to be repeated includes execution of the test method itself as +well as any setting up or tearing down of the test fixture. When used with the +<>, the scope additionally includes +preparation of the test instance by `TestExecutionListener` implementations. The +following example shows how to use the `@Repeat` annotation: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Repeat(10) // <1> + @Test + public void testProcessRepeatedly() { + // ... + } +---- +<1> Repeat this test ten times. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Repeat(10) // <1> + @Test + fun testProcessRepeatedly() { + // ... + } +---- +<1> Repeat this test ten times. + + + +[[integration-testing-annotations-junit-jupiter]] +== Spring JUnit Jupiter Testing Annotations + +The following annotations are supported when used in conjunction with the +<> and JUnit Jupiter +(that is, the programming model in JUnit 5): + +* <> +* <> +* <> +* <> +* <> +* <> + +[[integration-testing-annotations-junit-jupiter-springjunitconfig]] +=== `@SpringJUnitConfig` + +`@SpringJUnitConfig` is a composed annotation that combines +`@ExtendWith(SpringExtension.class)` from JUnit Jupiter with `@ContextConfiguration` from +the Spring TestContext Framework. It can be used at the class level as a drop-in +replacement for `@ContextConfiguration`. With regard to configuration options, the only +difference between `@ContextConfiguration` and `@SpringJUnitConfig` is that component +classes may be declared with the `value` attribute in `@SpringJUnitConfig`. + +The following example shows how to use the `@SpringJUnitConfig` annotation to specify a +configuration class: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(TestConfig.class) // <1> + class ConfigurationClassJUnitJupiterSpringTests { + // class body... + } +---- +<1> Specify the configuration class. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(TestConfig::class) // <1> + class ConfigurationClassJUnitJupiterSpringTests { + // class body... + } +---- +<1> Specify the configuration class. + + +The following example shows how to use the `@SpringJUnitConfig` annotation to specify the +location of a configuration file: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitConfig(locations = "/test-config.xml") // <1> + class XmlJUnitJupiterSpringTests { + // class body... + } +---- +<1> Specify the location of a configuration file. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitConfig(locations = ["/test-config.xml"]) // <1> + class XmlJUnitJupiterSpringTests { + // class body... + } +---- +<1> Specify the location of a configuration file. + + +See <> as well as the javadoc for +{api-spring-framework}/test/context/junit/jupiter/SpringJUnitConfig.html[`@SpringJUnitConfig`] +and `@ContextConfiguration` for further details. + +[[integration-testing-annotations-junit-jupiter-springjunitwebconfig]] +=== `@SpringJUnitWebConfig` + +`@SpringJUnitWebConfig` is a composed annotation that combines +`@ExtendWith(SpringExtension.class)` from JUnit Jupiter with `@ContextConfiguration` and +`@WebAppConfiguration` from the Spring TestContext Framework. You can use it at the class +level as a drop-in replacement for `@ContextConfiguration` and `@WebAppConfiguration`. +With regard to configuration options, the only difference between `@ContextConfiguration` +and `@SpringJUnitWebConfig` is that you can declare component classes by using the +`value` attribute in `@SpringJUnitWebConfig`. In addition, you can override the `value` +attribute from `@WebAppConfiguration` only by using the `resourcePath` attribute in +`@SpringJUnitWebConfig`. + +The following example shows how to use the `@SpringJUnitWebConfig` annotation to specify +a configuration class: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitWebConfig(TestConfig.class) // <1> + class ConfigurationClassJUnitJupiterSpringWebTests { + // class body... + } +---- +<1> Specify the configuration class. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitWebConfig(TestConfig::class) // <1> + class ConfigurationClassJUnitJupiterSpringWebTests { + // class body... + } +---- +<1> Specify the configuration class. + + +The following example shows how to use the `@SpringJUnitWebConfig` annotation to specify the +location of a configuration file: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @SpringJUnitWebConfig(locations = "/test-config.xml") // <1> + class XmlJUnitJupiterSpringWebTests { + // class body... + } +---- +<1> Specify the location of a configuration file. + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @SpringJUnitWebConfig(locations = ["/test-config.xml"]) // <1> + class XmlJUnitJupiterSpringWebTests { + // class body... + } +---- +<1> Specify the location of a configuration file. + + +See <> as well as the javadoc for +{api-spring-framework}/test/context/junit/jupiter/web/SpringJUnitWebConfig.html[`@SpringJUnitWebConfig`], +{api-spring-framework}/test/context/ContextConfiguration.html[`@ContextConfiguration`], and +{api-spring-framework}/test/context/web/WebAppConfiguration.html[`@WebAppConfiguration`] +for further details. + +[[integration-testing-annotations-testconstructor]] +=== `@TestConstructor` + +`@TestConstructor` is a type-level annotation that is used to configure how the parameters +of a test class constructor are autowired from components in the test's +`ApplicationContext`. + +If `@TestConstructor` is not present or meta-present on a test class, the default _test +constructor autowire mode_ will be used. See the tip below for details on how to change +the default mode. Note, however, that a local declaration of `@Autowired` on a +constructor takes precedence over both `@TestConstructor` and the default mode. + +.Changing the default test constructor autowire mode +[TIP] +===== +The default _test constructor autowire mode_ can be changed by setting the +`spring.test.constructor.autowire.mode` JVM system property to `all`. Alternatively, the +default mode may be set via the +<> mechanism. + +As of Spring Framework 5.3, the default mode may also be configured as a +https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter]. + +If the `spring.test.constructor.autowire.mode` property is not set, test class +constructors will not be automatically autowired. +===== + +NOTE: As of Spring Framework 5.2, `@TestConstructor` is only supported in conjunction +with the `SpringExtension` for use with JUnit Jupiter. Note that the `SpringExtension` is +often automatically registered for you – for example, when using annotations such as +`@SpringJUnitConfig` and `@SpringJUnitWebConfig` or various test-related annotations from +Spring Boot Test. + +[[integration-testing-annotations-nestedtestconfiguration]] +=== `@NestedTestConfiguration` + +`@NestedTestConfiguration` is a type-level annotation that is used to configure how +Spring test configuration annotations are processed within enclosing class hierarchies +for inner test classes. + +If `@NestedTestConfiguration` is not present or meta-present on a test class, in its +supertype hierarchy, or in its enclosing class hierarchy, the default _enclosing +configuration inheritance mode_ will be used. See the tip below for details on how to +change the default mode. + +.Changing the default enclosing configuration inheritance mode +[TIP] +===== +The default _enclosing configuration inheritance mode_ is `INHERIT`, but it can be +changed by setting the `spring.test.enclosing.configuration` JVM system property to +`OVERRIDE`. Alternatively, the default mode may be set via the +<> mechanism. +===== + +The <> honors `@NestedTestConfiguration` semantics for the +following annotations. + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> + +NOTE: The use of `@NestedTestConfiguration` typically only makes sense in conjunction +with `@Nested` test classes in JUnit Jupiter; however, there may be other testing +frameworks with support for Spring and nested test classes that make use of this +annotation. + +See <> for an example and further +details. + +[[integration-testing-annotations-junit-jupiter-enabledif]] +=== `@EnabledIf` + +`@EnabledIf` is used to signal that the annotated JUnit Jupiter test class or test method +is enabled and should be run if the supplied `expression` evaluates to `true`. +Specifically, if the expression evaluates to `Boolean.TRUE` or a `String` equal to `true` +(ignoring case), the test is enabled. When applied at the class level, all test methods +within that class are automatically enabled by default as well. + +Expressions can be any of the following: + +* <> (SpEL) expression. For example: + `@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")` +* Placeholder for a property available in the Spring <>. + For example: `@EnabledIf("${smoke.tests.enabled}")` +* Text literal. For example: `@EnabledIf("true")` + +Note, however, that a text literal that is not the result of dynamic resolution of a +property placeholder is of zero practical value, since `@EnabledIf("false")` is +equivalent to `@Disabled` and `@EnabledIf("true")` is logically meaningless. + +You can use `@EnabledIf` as a meta-annotation to create custom composed annotations. For +example, you can create a custom `@EnabledOnMac` annotation as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @EnabledIf( + expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", + reason = "Enabled on Mac OS" + ) + public @interface EnabledOnMac {} +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) + @Retention(AnnotationRetention.RUNTIME) + @EnabledIf( + expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", + reason = "Enabled on Mac OS" + ) + annotation class EnabledOnMac {} +---- + +[NOTE] +==== +`@EnabledOnMac` is meant only as an example of what is possible. If you have that exact +use case, please use the built-in `@EnabledOnOs(MAC)` support in JUnit Jupiter. +==== + +[WARNING] +==== +Since JUnit 5.7, JUnit Jupiter also has a condition annotation named `@EnabledIf`. Thus, +if you wish to use Spring's `@EnabledIf` support make sure you import the annotation type +from the correct package. +==== + +[[integration-testing-annotations-junit-jupiter-disabledif]] +=== `@DisabledIf` + +`@DisabledIf` is used to signal that the annotated JUnit Jupiter test class or test +method is disabled and should not be run if the supplied `expression` evaluates to +`true`. Specifically, if the expression evaluates to `Boolean.TRUE` or a `String` equal +to `true` (ignoring case), the test is disabled. When applied at the class level, all +test methods within that class are automatically disabled as well. + +Expressions can be any of the following: + +* <> (SpEL) expression. For example: + `@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")` +* Placeholder for a property available in the Spring <>. + For example: `@DisabledIf("${smoke.tests.disabled}")` +* Text literal. For example: `@DisabledIf("true")` + +Note, however, that a text literal that is not the result of dynamic resolution of a +property placeholder is of zero practical value, since `@DisabledIf("true")` is +equivalent to `@Disabled` and `@DisabledIf("false")` is logically meaningless. + +You can use `@DisabledIf` as a meta-annotation to create custom composed annotations. For +example, you can create a custom `@DisabledOnMac` annotation as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @DisabledIf( + expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", + reason = "Disabled on Mac OS" + ) + public @interface DisabledOnMac {} +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) + @Retention(AnnotationRetention.RUNTIME) + @DisabledIf( + expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", + reason = "Disabled on Mac OS" + ) + annotation class DisabledOnMac {} +---- + +[NOTE] +==== +`@DisabledOnMac` is meant only as an example of what is possible. If you have that exact +use case, please use the built-in `@DisabledOnOs(MAC)` support in JUnit Jupiter. +==== + +[WARNING] +==== +Since JUnit 5.7, JUnit Jupiter also has a condition annotation named `@DisabledIf`. Thus, +if you wish to use Spring's `@DisabledIf` support make sure you import the annotation type +from the correct package. +==== + + + +[[integration-testing-annotations-meta]] +== Meta-Annotation Support for Testing + +You can use most test-related annotations as +<> to create custom composed +annotations and reduce configuration duplication across a test suite. + +You can use each of the following as a meta-annotation in conjunction with the +<>. + +* `@BootstrapWith` +* `@ContextConfiguration` +* `@ContextHierarchy` +* `@ActiveProfiles` +* `@TestPropertySource` +* `@DirtiesContext` +* `@WebAppConfiguration` +* `@TestExecutionListeners` +* `@Transactional` +* `@BeforeTransaction` +* `@AfterTransaction` +* `@Commit` +* `@Rollback` +* `@Sql` +* `@SqlConfig` +* `@SqlMergeMode` +* `@SqlGroup` +* `@Repeat` _(only supported on JUnit 4)_ +* `@Timed` _(only supported on JUnit 4)_ +* `@IfProfileValue` _(only supported on JUnit 4)_ +* `@ProfileValueSourceConfiguration` _(only supported on JUnit 4)_ +* `@SpringJUnitConfig` _(only supported on JUnit Jupiter)_ +* `@SpringJUnitWebConfig` _(only supported on JUnit Jupiter)_ +* `@TestConstructor` _(only supported on JUnit Jupiter)_ +* `@NestedTestConfiguration` _(only supported on JUnit Jupiter)_ +* `@EnabledIf` _(only supported on JUnit Jupiter)_ +* `@DisabledIf` _(only supported on JUnit Jupiter)_ + +Consider the following example: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @RunWith(SpringRunner.class) + @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) + @ActiveProfiles("dev") + @Transactional + public class OrderRepositoryTests { } + + @RunWith(SpringRunner.class) + @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) + @ActiveProfiles("dev") + @Transactional + public class UserRepositoryTests { } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RunWith(SpringRunner::class) + @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") + @ActiveProfiles("dev") + @Transactional + class OrderRepositoryTests { } + + @RunWith(SpringRunner::class) + @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") + @ActiveProfiles("dev") + @Transactional + class UserRepositoryTests { } +---- + +If we discover that we are repeating the preceding configuration across our JUnit 4-based +test suite, we can reduce the duplication by introducing a custom composed annotation +that centralizes the common test configuration for Spring, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) + @ActiveProfiles("dev") + @Transactional + public @interface TransactionalDevTestConfig { } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Target(AnnotationTarget.TYPE) + @Retention(AnnotationRetention.RUNTIME) + @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") + @ActiveProfiles("dev") + @Transactional + annotation class TransactionalDevTestConfig { } +---- + +Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the +configuration of individual JUnit 4 based test classes, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @RunWith(SpringRunner.class) + @TransactionalDevTestConfig + public class OrderRepositoryTests { } + + @RunWith(SpringRunner.class) + @TransactionalDevTestConfig + public class UserRepositoryTests { } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @RunWith(SpringRunner::class) + @TransactionalDevTestConfig + class OrderRepositoryTests + + @RunWith(SpringRunner::class) + @TransactionalDevTestConfig + class UserRepositoryTests +---- + +If we write tests that use JUnit Jupiter, we can reduce code duplication even further, +since annotations in JUnit 5 can also be used as meta-annotations. Consider the following +example: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) + @ActiveProfiles("dev") + @Transactional + class OrderRepositoryTests { } + + @ExtendWith(SpringExtension.class) + @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) + @ActiveProfiles("dev") + @Transactional + class UserRepositoryTests { } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension::class) + @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") + @ActiveProfiles("dev") + @Transactional + class OrderRepositoryTests { } + + @ExtendWith(SpringExtension::class) + @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") + @ActiveProfiles("dev") + @Transactional + class UserRepositoryTests { } +---- + +If we discover that we are repeating the preceding configuration across our JUnit +Jupiter-based test suite, we can reduce the duplication by introducing a custom composed +annotation that centralizes the common test configuration for Spring and JUnit Jupiter, +as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @ExtendWith(SpringExtension.class) + @ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"}) + @ActiveProfiles("dev") + @Transactional + public @interface TransactionalDevTestConfig { } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Target(AnnotationTarget.TYPE) + @Retention(AnnotationRetention.RUNTIME) + @ExtendWith(SpringExtension::class) + @ContextConfiguration("/app-config.xml", "/test-data-access-config.xml") + @ActiveProfiles("dev") + @Transactional + annotation class TransactionalDevTestConfig { } +---- + +Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the +configuration of individual JUnit Jupiter based test classes, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @TransactionalDevTestConfig + class OrderRepositoryTests { } + + @TransactionalDevTestConfig + class UserRepositoryTests { } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @TransactionalDevTestConfig + class OrderRepositoryTests { } + + @TransactionalDevTestConfig + class UserRepositoryTests { } +---- + +Since JUnit Jupiter supports the use of `@Test`, `@RepeatedTest`, `ParameterizedTest`, +and others as meta-annotations, you can also create custom composed annotations at the +test method level. For example, if we wish to create a composed annotation that combines +the `@Test` and `@Tag` annotations from JUnit Jupiter with the `@Transactional` +annotation from Spring, we could create an `@TransactionalIntegrationTest` annotation, as +follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @Transactional + @Tag("integration-test") // org.junit.jupiter.api.Tag + @Test // org.junit.jupiter.api.Test + public @interface TransactionalIntegrationTest { } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Target(AnnotationTarget.TYPE) + @Retention(AnnotationRetention.RUNTIME) + @Transactional + @Tag("integration-test") // org.junit.jupiter.api.Tag + @Test // org.junit.jupiter.api.Test + annotation class TransactionalIntegrationTest { } +---- + +Then we can use our custom `@TransactionalIntegrationTest` annotation to simplify the +configuration of individual JUnit Jupiter based test methods, as follows: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @TransactionalIntegrationTest + void saveOrder() { } + + @TransactionalIntegrationTest + void deleteOrder() { } +---- + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @TransactionalIntegrationTest + fun saveOrder() { } + + @TransactionalIntegrationTest + fun deleteOrder() { } +---- + +For further details, see the +https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model[Spring Annotation Programming Model] +wiki page. diff --git a/framework-docs/src/docs/asciidoc/testing/testing-appendix.adoc b/framework-docs/src/docs/asciidoc/testing/testing-appendix.adoc new file mode 100644 index 000000000000..f9fc1a1454b3 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/testing/testing-appendix.adoc @@ -0,0 +1,6 @@ +[[testing.appendix]] += Appendix + +include::testing-annotations.adoc[leveloffset=+1] + +include::testing-resources.adoc[leveloffset=+1] diff --git a/framework-docs/src/docs/asciidoc/testing/testing-introduction.adoc b/framework-docs/src/docs/asciidoc/testing/testing-introduction.adoc new file mode 100644 index 000000000000..1bc210f4b171 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/testing/testing-introduction.adoc @@ -0,0 +1,8 @@ +[[testing-introduction]] += Introduction to Spring Testing + +Testing is an integral part of enterprise software development. This chapter focuses on +the value added by the IoC principle to <> and on the benefits +of the Spring Framework's support for <>. (A +thorough treatment of testing in the enterprise is beyond the scope of this reference +manual.) diff --git a/framework-docs/src/docs/asciidoc/testing/testing-resources.adoc b/framework-docs/src/docs/asciidoc/testing/testing-resources.adoc new file mode 100644 index 000000000000..e274dd94f9f4 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/testing/testing-resources.adoc @@ -0,0 +1,32 @@ +[[testing-resources]] += Further Resources +See the following resources for more information about testing: + +* https://www.junit.org/[JUnit]: "A programmer-friendly testing framework for Java and the JVM". + Used by the Spring Framework in its test suite and supported in the + <>. +* https://testng.org/[TestNG]: A testing framework inspired by JUnit with added support + for test groups, data-driven testing, distributed testing, and other features. Supported + in the <> +* https://assertj.github.io/doc/[AssertJ]: "Fluent assertions for Java", + including support for Java 8 lambdas, streams, and numerous other features. +* https://en.wikipedia.org/wiki/Mock_Object[Mock Objects]: Article in Wikipedia. +* http://www.mockobjects.com/[MockObjects.com]: Web site dedicated to mock objects, a + technique for improving the design of code within test-driven development. +* https://mockito.github.io[Mockito]: Java mock library based on the + http://xunitpatterns.com/Test%20Spy.html[Test Spy] pattern. Used by the Spring Framework + in its test suite. +* https://easymock.org/[EasyMock]: Java library "that provides Mock Objects for + interfaces (and objects through the class extension) by generating them on the fly using + Java's proxy mechanism." +* https://jmock.org/[JMock]: Library that supports test-driven development of Java code + with mock objects. +* https://www.dbunit.org/[DbUnit]: JUnit extension (also usable with Ant and Maven) that + is targeted at database-driven projects and, among other things, puts your database into + a known state between test runs. +* https://www.testcontainers.org/[Testcontainers]: Java library that supports JUnit + tests, providing lightweight, throwaway instances of common databases, Selenium web + browsers, or anything else that can run in a Docker container. +* https://sourceforge.net/projects/grinder/[The Grinder]: Java load testing framework. +* https://github.com/Ninja-Squad/springmockk[SpringMockK]: Support for Spring Boot + integration tests written in Kotlin using https://mockk.io/[MockK] instead of Mockito. diff --git a/framework-docs/src/docs/asciidoc/testing/testing-support-jdbc.adoc b/framework-docs/src/docs/asciidoc/testing/testing-support-jdbc.adoc new file mode 100644 index 000000000000..8ae2a2be22b7 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/testing/testing-support-jdbc.adoc @@ -0,0 +1,35 @@ +[[integration-testing-support-jdbc]] += JDBC Testing Support + +[[integration-testing-support-jdbc-test-utils]] +== JdbcTestUtils + +The `org.springframework.test.jdbc` package contains `JdbcTestUtils`, which is a +collection of JDBC-related utility functions intended to simplify standard database +testing scenarios. Specifically, `JdbcTestUtils` provides the following static utility +methods. + +* `countRowsInTable(..)`: Counts the number of rows in the given table. +* `countRowsInTableWhere(..)`: Counts the number of rows in the given table by using the + provided `WHERE` clause. +* `deleteFromTables(..)`: Deletes all rows from the specified tables. +* `deleteFromTableWhere(..)`: Deletes rows from the given table by using the provided + `WHERE` clause. +* `dropTables(..)`: Drops the specified tables. + +[TIP] +==== +<> +and <> +provide convenience methods that delegate to the aforementioned methods in +`JdbcTestUtils`. +==== + +[[integration-testing-support-jdbc-embedded-database]] +== Embedded Databases + +The `spring-jdbc` module provides support for configuring and launching an embedded +database, which you can use in integration tests that interact with a database. +For details, see <> and <>. diff --git a/framework-docs/src/docs/asciidoc/testing/unit-testing.adoc b/framework-docs/src/docs/asciidoc/testing/unit-testing.adoc new file mode 100644 index 000000000000..6a3cbe027548 --- /dev/null +++ b/framework-docs/src/docs/asciidoc/testing/unit-testing.adoc @@ -0,0 +1,168 @@ +[[unit-testing]] += Unit Testing + +Dependency injection should make your code less dependent on the container than it would +be with traditional J2EE / Java EE development. The POJOs that make up your application +should be testable in JUnit or TestNG tests, with objects instantiated by using the `new` +operator, without Spring or any other container. You can use <> +(in conjunction with other valuable testing techniques) to test your code in isolation. +If you follow the architecture recommendations for Spring, the resulting clean layering +and componentization of your codebase facilitate easier unit testing. For example, +you can test service layer objects by stubbing or mocking DAO or repository interfaces, +without needing to access persistent data while running unit tests. + +True unit tests typically run extremely quickly, as there is no runtime infrastructure to +set up. Emphasizing true unit tests as part of your development methodology can boost +your productivity. You may not need this section of the testing chapter to help you write +effective unit tests for your IoC-based applications. For certain unit testing scenarios, +however, the Spring Framework provides mock objects and testing support classes, which +are described in this chapter. + + + +[[mock-objects]] +== Mock Objects + +Spring includes a number of packages dedicated to mocking: + +* <> +* <> +* <> +* <> + + +[[mock-objects-env]] +=== Environment + +The `org.springframework.mock.env` package contains mock implementations of the +`Environment` and `PropertySource` abstractions (see +<> +and <>). +`MockEnvironment` and `MockPropertySource` are useful for developing +out-of-container tests for code that depends on environment-specific properties. + + +[[mock-objects-jndi]] +=== JNDI + +The `org.springframework.mock.jndi` package contains a partial implementation of the JNDI +SPI, which you can use to set up a simple JNDI environment for test suites or stand-alone +applications. If, for example, JDBC `DataSource` instances get bound to the same JNDI +names in test code as they do in a Jakarta EE container, you can reuse both application code +and configuration in testing scenarios without modification. + +WARNING: The mock JNDI support in the `org.springframework.mock.jndi` package is +officially deprecated as of Spring Framework 5.2 in favor of complete solutions from third +parties such as https://github.com/h-thurow/Simple-JNDI[Simple-JNDI]. + + +[[mock-objects-servlet]] +=== Servlet API + +The `org.springframework.mock.web` package contains a comprehensive set of Servlet API +mock objects that are useful for testing web contexts, controllers, and filters. These +mock objects are targeted at usage with Spring's Web MVC framework and are generally more +convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock]) +or alternative Servlet API mock objects (such as http://www.mockobjects.com[MockObjects]). + +TIP: Since Spring Framework 6.0, the mock objects in `org.springframework.mock.web` are +based on the Servlet 6.0 API. + +The Spring MVC Test framework builds on the mock Servlet API objects to provide an +integration testing framework for Spring MVC. See <>. + + +[[mock-objects-web-reactive]] +=== Spring Web Reactive + +The `org.springframework.mock.http.server.reactive` package contains mock implementations +of `ServerHttpRequest` and `ServerHttpResponse` for use in WebFlux applications. The +`org.springframework.mock.web.server` package contains a mock `ServerWebExchange` that +depends on those mock request and response objects. + +Both `MockServerHttpRequest` and `MockServerHttpResponse` extend from the same abstract +base classes as server-specific implementations and share behavior with them. For +example, a mock request is immutable once created, but you can use the `mutate()` method +from `ServerHttpRequest` to create a modified instance. + +In order for the mock response to properly implement the write contract and return a +write completion handle (that is, `Mono`), it by default uses a `Flux` with +`cache().then()`, which buffers the data and makes it available for assertions in tests. +Applications can set a custom write function (for example, to test an infinite stream). + +The <> builds on the mock request and response to provide support for +testing WebFlux applications without an HTTP server. The client can also be used for +end-to-end tests with a running server. + + + +[[unit-testing-support-classes]] +== Unit Testing Support Classes + +Spring includes a number of classes that can help with unit testing. They fall into two +categories: + +* <> +* <> + + +[[unit-testing-utilities]] +=== General Testing Utilities + +The `org.springframework.test.util` package contains several general purpose utilities +for use in unit and integration testing. + +{api-spring-framework}/test/util/AopTestUtils.html[`AopTestUtils`] is a collection of +AOP-related utility methods. You can use these methods to obtain a reference to the +underlying target object hidden behind one or more Spring proxies. For example, if you +have configured a bean as a dynamic mock by using a library such as EasyMock or Mockito, +and the mock is wrapped in a Spring proxy, you may need direct access to the underlying +mock to configure expectations on it and perform verifications. For Spring's core AOP +utilities, see {api-spring-framework}/aop/support/AopUtils.html[`AopUtils`] and +{api-spring-framework}/aop/framework/AopProxyUtils.html[`AopProxyUtils`]. + +{api-spring-framework}/test/util/ReflectionTestUtils.html[`ReflectionTestUtils`] is a +collection of reflection-based utility methods. You can use these methods in testing +scenarios where you need to change the value of a constant, set a non-`public` field, +invoke a non-`public` setter method, or invoke a non-`public` configuration or lifecycle +callback method when testing application code for use cases such as the following: + +* ORM frameworks (such as JPA and Hibernate) that condone `private` or `protected` field + access as opposed to `public` setter methods for properties in a domain entity. +* Spring's support for annotations (such as `@Autowired`, `@Inject`, and `@Resource`), + that provide dependency injection for `private` or `protected` fields, setter methods, + and configuration methods. +* Use of annotations such as `@PostConstruct` and `@PreDestroy` for lifecycle callback + methods. + +{api-spring-framework}/test/util/TestSocketUtils.html[`TestSocketUtils`] is a simple +utility for finding available TCP ports on `localhost` for use in integration testing +scenarios. + +[NOTE] +==== +`TestSocketUtils` can be used in integration tests which start an external server on an +available random port. However, these utilities make no guarantee about the subsequent +availability of a given port and are therefore unreliable. Instead of using +`TestSocketUtils` to find an available local port for a server, it is recommended that +you rely on a server's ability to start on a random ephemeral port that it selects or is +assigned by the operating system. To interact with that server, you should query the +server for the port it is currently using. +==== + + +[[unit-testing-spring-mvc]] +=== Spring MVC Testing Utilities + +The `org.springframework.test.web` package contains +{api-spring-framework}/test/web/ModelAndViewAssert.html[`ModelAndViewAssert`], which you +can use in combination with JUnit, TestNG, or any other testing framework for unit tests +that deal with Spring MVC `ModelAndView` objects. + +.Unit testing Spring MVC Controllers +TIP: To unit test your Spring MVC `Controller` classes as POJOs, use `ModelAndViewAssert` +combined with `MockHttpServletRequest`, `MockHttpSession`, and so on from Spring's +<>. For thorough integration testing of your +Spring MVC and REST `Controller` classes in conjunction with your `WebApplicationContext` +configuration for Spring MVC, use the +<> instead. diff --git a/framework-docs/src/docs/asciidoc/web-reactive.adoc b/framework-docs/src/docs/asciidoc/web-reactive.adoc index c199e90e08e3..ea5d4c2b32bb 100644 --- a/framework-docs/src/docs/asciidoc/web-reactive.adoc +++ b/framework-docs/src/docs/asciidoc/web-reactive.adoc @@ -1,14 +1,12 @@ [[spring-web-reactive]] = Web on Reactive Stack -:toc: left -:toclevels: 4 -:tabsize: 4 -:docinfo1: +include::attributes.adoc[] +include::page-layout.adoc[] This part of the documentation covers support for reactive-stack web applications built on a https://www.reactive-streams.org/[Reactive Streams] API to run on non-blocking servers, such as Netty, Undertow, and Servlet containers. Individual chapters cover -the <> framework, +the <> framework, the reactive <>, support for <>, and <>. For Servlet-stack web applications, see <>. @@ -35,7 +33,7 @@ include::web/webflux-websocket.adoc[leveloffset=+1] [[webflux-test]] == Testing -[.small]#<># +[.small]#<># The `spring-test` module provides mock implementations of `ServerHttpRequest`, `ServerHttpResponse`, and `ServerWebExchange`. diff --git a/framework-docs/src/docs/asciidoc/web.adoc b/framework-docs/src/docs/asciidoc/web.adoc index 3aeb44518610..9a6fe6f541ec 100644 --- a/framework-docs/src/docs/asciidoc/web.adoc +++ b/framework-docs/src/docs/asciidoc/web.adoc @@ -1,9 +1,7 @@ [[spring-web]] = Web on Servlet Stack -:toc: left -:toclevels: 4 -:tabsize: 4 -:docinfo1: +include::attributes.adoc[] +include::page-layout.adoc[] This part of the documentation covers support for Servlet-stack web applications built on the Servlet API and deployed to Servlet containers. Individual chapters include <>, diff --git a/framework-docs/src/docs/asciidoc/web/integration.adoc b/framework-docs/src/docs/asciidoc/web/integration.adoc index 42c53776662a..5fdd1595dd6e 100644 --- a/framework-docs/src/docs/asciidoc/web/integration.adoc +++ b/framework-docs/src/docs/asciidoc/web/integration.adoc @@ -9,7 +9,7 @@ particular architecture, technology, or methodology (although it certainly recom some over others). This freedom to pick and choose the architecture, technology, or methodology that is most relevant to a developer and their development team is arguably most evident in the web area, where Spring provides its own web frameworks -(<> and <>) while, at the same time, +(<> and <>) while, at the same time, supporting integration with a number of popular third-party web frameworks. @@ -23,21 +23,21 @@ first take a look at common Spring configuration that is not specific to any one framework. (This section is equally applicable to Spring's own web framework variants.) One of the concepts (for want of a better word) espoused by Spring's lightweight -application model is that of a layered architecture. Remember that in a "`classic`" +application model is that of a layered architecture. Remember that in a "classic" layered architecture, the web layer is but one of many layers. It serves as one of the entry points into a server-side application, and it delegates to service objects (facades) that are defined in a service layer to satisfy business-specific (and presentation-technology agnostic) use cases. In Spring, these service objects, any other -business-specific objects, data-access objects, and others exist in a distinct "`business -context`", which contains no web or presentation layer objects (presentation objects, -such as Spring MVC controllers, are typically configured in a distinct "`presentation -context`"). This section details how you can configure a Spring container (a +business-specific objects, data-access objects, and others exist in a distinct "business +context", which contains no web or presentation layer objects (presentation objects, +such as Spring MVC controllers, are typically configured in a distinct "presentation +context"). This section details how you can configure a Spring container (a `WebApplicationContext`) that contains all of the 'business beans' in your application. Moving on to specifics, all you need to do is declare a {api-spring-framework}/web/context/ContextLoaderListener.html[`ContextLoaderListener`] in the standard Jakarta EE servlet `web.xml` file of your web application and add a -`contextConfigLocation` section (in the same file) that defines which +`contextConfigLocation` `` section (in the same file) that defines which set of Spring XML configuration files to load. Consider the following `` configuration: @@ -67,7 +67,7 @@ object based on the bean definitions and stores it in the `ServletContext` of th application. All Java web frameworks are built on top of the Servlet API, so you can use the -following code snippet to get access to this "`business context`" `ApplicationContext` +following code snippet to get access to this "business context" `ApplicationContext` created by the `ContextLoaderListener`. The following example shows how to get the `WebApplicationContext`: @@ -119,7 +119,7 @@ The key element in Spring's JSF integration is the JSF `ELResolver` mechanism. `SpringBeanFacesELResolver` is a JSF compliant `ELResolver` implementation, integrating with the standard Unified EL as used by JSF and JSP. It delegates to -Spring's "`business context`" `WebApplicationContext` first and then to the +Spring's "business context" `WebApplicationContext` first and then to the default resolver of the underlying JSF implementation. Configuration-wise, you can define `SpringBeanFacesELResolver` in your JSF @@ -157,27 +157,26 @@ The following example shows how to use `FacesContextUtils`: [[struts]] -== Apache Struts 2.x +== Apache Struts Invented by Craig McClanahan, https://struts.apache.org[Struts] is an open-source project -hosted by the Apache Software Foundation. At the time, it greatly simplified the +hosted by the Apache Software Foundation. Struts 1.x greatly simplified the JSP/Servlet programming paradigm and won over many developers who were using proprietary -frameworks. It simplified the programming model, it was open source (and thus free as in -beer), and it had a large community, which let the project grow and become popular among -Java web developers. +frameworks. It simplified the programming model; it was open source; and it had a large +community, which let the project grow and become popular among Java web developers. -As a successor to the original Struts 1.x, check out Struts 2.x and the Struts-provided -https://struts.apache.org/release/2.3.x/docs/spring-plugin.html[Spring Plugin] for the -built-in Spring integration. +As a successor to the original Struts 1.x, check out Struts 2.x or more recent versions +as well as the Struts-provided +https://struts.apache.org/plugins/spring/[Spring Plugin] for built-in Spring integration. [[tapestry]] -== Apache Tapestry 5.x +== Apache Tapestry -https://tapestry.apache.org/[Tapestry] is a ""Component oriented framework for creating -dynamic, robust, highly scalable web applications in Java."" +https://tapestry.apache.org/[Tapestry] is a "Component oriented framework for creating +dynamic, robust, highly scalable web applications in Java." While Spring has its own <>, there are a number of unique advantages to building an enterprise Java application by using a combination of Tapestry @@ -195,6 +194,6 @@ https://tapestry.apache.org/integrating-with-spring-framework.html[integration m The following links go to further resources about the various web frameworks described in this chapter. -* The https://www.oracle.com/technetwork/java/javaee/javaserverfaces-139869.html[JSF] homepage +* The https://www.oracle.com/java/technologies/javaserverfaces.html[JSF] homepage * The https://struts.apache.org/[Struts] homepage * The https://tapestry.apache.org/[Tapestry] homepage diff --git a/framework-docs/src/docs/asciidoc/web/web-uris.adoc b/framework-docs/src/docs/asciidoc/web/web-uris.adoc index 593fd97ffe07..26027a1a8d0d 100644 --- a/framework-docs/src/docs/asciidoc/web/web-uris.adoc +++ b/framework-docs/src/docs/asciidoc/web/web-uris.adoc @@ -8,12 +8,12 @@ .Java ---- UriComponents uriComponents = UriComponentsBuilder - .fromUriString("https://example.com/hotels/{hotel}") // <1> - .queryParam("q", "{q}") // <2> + .fromUriString("https://example.com/hotels/{hotel}") // <1> + .queryParam("q", "{q}") // <2> .encode() // <3> .build(); // <4> - URI uri = uriComponents.expand("Westin", "123").toUri(); // <5> + URI uri = uriComponents.expand("Westin", "123").toUri(); // <5> ---- <1> Static factory method with a URI template. <2> Add or replace URI components. @@ -25,12 +25,12 @@ .Kotlin ---- val uriComponents = UriComponentsBuilder - .fromUriString("https://example.com/hotels/{hotel}") // <1> - .queryParam("q", "{q}") // <2> + .fromUriString("https://example.com/hotels/{hotel}") // <1> + .queryParam("q", "{q}") // <2> .encode() // <3> .build() // <4> - val uri = uriComponents.expand("Westin", "123").toUri() // <5> + val uri = uriComponents.expand("Westin", "123").toUri() // <5> ---- <1> Static factory method with a URI template. <2> Add or replace URI components. diff --git a/framework-docs/src/docs/asciidoc/web/webflux-cors.adoc b/framework-docs/src/docs/asciidoc/web/webflux-cors.adoc index 91890ddb1f3b..2b1fb23b78d1 100644 --- a/framework-docs/src/docs/asciidoc/web/webflux-cors.adoc +++ b/framework-docs/src/docs/asciidoc/web/webflux-cors.adoc @@ -1,7 +1,6 @@ [[webflux-cors]] = CORS -:doc-spring-security: {doc-root}/spring-security/reference -[.small]#<># +[.small]#<># Spring WebFlux lets you handle CORS (Cross-Origin Resource Sharing). This section describes how to do so. @@ -11,7 +10,7 @@ describes how to do so. [[webflux-cors-intro]] == Introduction -[.small]#<># +[.small]#<># For security reasons, browsers prohibit AJAX calls to resources outside the current origin. For example, you could have your bank account in one tab and evil.com in another. Scripts @@ -28,7 +27,7 @@ powerful workarounds based on IFRAME or JSONP. [[webflux-cors-processing]] == Processing -[.small]#<># +[.small]#<># The CORS specification distinguishes between preflight, simple, and actual requests. To learn how CORS works, you can read @@ -78,12 +77,13 @@ To learn more from the source or to make advanced customizations, see: [[webflux-cors-controller]] == `@CrossOrigin` -[.small]#<># +[.small]#<># The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`] annotation enables cross-origin requests on annotated controller methods, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -122,6 +122,7 @@ following example shows: } } ---- +-- By default, `@CrossOrigin` allows: @@ -140,6 +141,7 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig `@CrossOrigin` is supported at the class level, too, and inherited by all methods. The following example specifies a certain domain and sets `maxAge` to an hour: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -178,10 +180,12 @@ The following example specifies a certain domain and sets `maxAge` to an hour: } } ---- +-- You can use `@CrossOrigin` at both the class and the method level, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -227,12 +231,13 @@ as the following example shows: ---- <1> Using `@CrossOrigin` at the class level. <2> Using `@CrossOrigin` at the method level. +-- [[webflux-cors-global]] == Global Configuration -[.small]#<># +[.small]#<># In addition to fine-grained, controller method-level configuration, you probably want to define some global CORS configuration, too. You can set URL-based `CorsConfiguration` @@ -303,14 +308,14 @@ as the following example shows: [[webflux-cors-webfilter]] == CORS `WebFilter` -[.small]#<># +[.small]#<># You can apply CORS support through the built-in {api-spring-framework}/web/cors/reactive/CorsWebFilter.html[`CorsWebFilter`], which is a good fit with <>. NOTE: If you try to use the `CorsFilter` with Spring Security, keep in mind that Spring -Security has {doc-spring-security}/servlet/integrations/cors.html[built-in support] for +Security has {docs-spring-security}/servlet/integrations/cors.html[built-in support] for CORS. To configure the filter, you can declare a `CorsWebFilter` bean and pass a diff --git a/framework-docs/src/docs/asciidoc/web/webflux-functional.adoc b/framework-docs/src/docs/asciidoc/web/webflux-functional.adoc index a7130097bab9..4ebf6b429465 100644 --- a/framework-docs/src/docs/asciidoc/web/webflux-functional.adoc +++ b/framework-docs/src/docs/asciidoc/web/webflux-functional.adoc @@ -1,6 +1,6 @@ [[webflux-fn]] = Functional Endpoints -[.small]#<># +[.small]#<># Spring WebFlux includes WebFlux.fn, a lightweight functional programming model in which functions are used to route and handle requests and contracts are designed for immutability. @@ -12,7 +12,7 @@ the same <> foundation. [[webflux-fn-overview]] == Overview -[.small]#<># +[.small]#<># In WebFlux.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes `ServerRequest` and returns a delayed `ServerResponse` (i.e. `Mono`). @@ -98,7 +98,7 @@ as the following example shows: } } ---- -<1> Create router using Coroutines router DSL, a Reactive alternative is also available via `router { }`. +<1> Create router using Coroutines router DSL; a Reactive alternative is also available via `router { }`. One way to run a `RouterFunction` is to turn it into an `HttpHandler` and install it through one of the built-in <>: @@ -113,7 +113,7 @@ Most applications can run through the WebFlux Java configuration, see <># +[.small]#<># `ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly access to the HTTP request and response. @@ -300,7 +300,6 @@ ServerResponse.created(location).build() Depending on the codec used, it is possible to pass hint parameters to customize how the body is serialized or deserialized. For example, to specify a https://www.baeldung.com/jackson-json-view-annotation[Jackson JSON view]: -==== [source,java,role="primary"] .Java ---- @@ -311,7 +310,6 @@ ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.clas ---- ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...) ---- -==== [[webflux-fn-handler-classes]] @@ -319,6 +317,7 @@ ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::cla We can write a handler function as a lambda, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -330,6 +329,7 @@ HandlerFunction helloWorld = ---- val helloWorld = HandlerFunction { ServerResponse.ok().bodyValue("Hello World") } ---- +-- That is convenient, but in an application we need multiple functions, and multiple inline lambda's can get messy. @@ -337,6 +337,7 @@ Therefore, it is useful to group related handler functions together into a handl has a similar role as `@Controller` in an annotation-based application. For example, the following class exposes a reactive `Person` repository: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -411,6 +412,7 @@ Note that `PersonRepository.savePerson(Person)` is a suspending function with no <3> `getPerson` is a handler function that returns a single person, identified by the `id` path variable. We retrieve that `Person` from the repository and create a JSON response, if it is found. If it is not found, we return a 404 Not Found response. +-- [[webflux-fn-handler-validation]] @@ -484,7 +486,7 @@ See <>. [[webflux-fn-router-functions]] == `RouterFunction` -[.small]#<># +[.small]#<># Router functions are used to route the requests to the corresponding `HandlerFunction`. Typically, you do not write router functions yourself, but rather use a method on the @@ -525,8 +527,8 @@ header: ---- val route = coRouter { GET("/hello-world", accept(TEXT_PLAIN)) { - ServerResponse.ok().bodyValueAndAwait("Hello World") - } + ServerResponse.ok().bodyValueAndAwait("Hello World") + } } ---- @@ -642,7 +644,7 @@ RouterFunction route = route() [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - val route = coRouter { + val route = coRouter { // <1> "/person".nest { GET("/{id}", accept(APPLICATION_JSON), handler::getPerson) GET(accept(APPLICATION_JSON), handler::listPeople) @@ -650,6 +652,7 @@ RouterFunction route = route() } } ---- +<1> Create router using Coroutines router DSL; a Reactive alternative is also available via `router { }`. Though path-based nesting is the most common, you can nest on any kind of predicate by using the `nest` method on the builder. @@ -684,7 +687,7 @@ We can further improve by using the `nest` method together with `accept`: [[webflux-fn-running]] == Running a Server -[.small]#<># +[.small]#<># How do you run a router function in an HTTP server? A simple option is to convert a router function to an `HttpHandler` by using one of the following: @@ -790,7 +793,7 @@ The following example shows a WebFlux Java configuration (see [[webflux-fn-handler-filter-function]] == Filtering Handler Functions -[.small]#<># +[.small]#<># You can filter handler functions by using the `before`, `after`, or `filter` methods on the routing function builder. diff --git a/framework-docs/src/docs/asciidoc/web/webflux-view.adoc b/framework-docs/src/docs/asciidoc/web/webflux-view.adoc index 04b9f8c1a0e1..9348d3ca561b 100644 --- a/framework-docs/src/docs/asciidoc/web/webflux-view.adoc +++ b/framework-docs/src/docs/asciidoc/web/webflux-view.adoc @@ -1,6 +1,6 @@ [[webflux-view]] = View Technologies -[.small]#<># +[.small]#<># The use of view technologies in Spring WebFlux is pluggable. Whether you decide to use Thymeleaf, FreeMarker, or some other view technology is primarily a matter of a @@ -12,7 +12,7 @@ WebFlux. We assume you are already familiar with <>. [[webflux-view-thymeleaf]] == Thymeleaf -[.small]#<># +[.small]#<># Thymeleaf is a modern server-side Java template engine that emphasizes natural HTML templates that can be previewed in a browser by double-clicking, which is very @@ -33,7 +33,7 @@ https://web.archive.org/web/20210623051330/http%3A//forum.thymeleaf.org/Thymelea [[webflux-view-freemarker]] == FreeMarker -[.small]#<># +[.small]#<># https://freemarker.apache.org/[Apache FreeMarker] is a template engine for generating any kind of text output from HTML to email and others. The Spring Framework has built-in @@ -43,7 +43,7 @@ integration for using Spring WebFlux with FreeMarker templates. [[webflux-view-freemarker-contextconfig]] === View Configuration -[.small]#<># +[.small]#<># The following example shows how to configure FreeMarker as a view technology: @@ -98,7 +98,7 @@ returns the view name, `welcome`, the resolver looks for the [[webflux-views-freemarker]] === FreeMarker Configuration -[.small]#<># +[.small]#<># You can pass FreeMarker 'Settings' and 'SharedVariables' directly to the FreeMarker `Configuration` object (which is managed by Spring) by setting the appropriate bean @@ -151,7 +151,7 @@ the `Configuration` object. [[webflux-view-freemarker-forms]] === Form Handling -[.small]#<># +[.small]#<># Spring provides a tag library for use in JSPs that contains, among others, a `` element. This element primarily lets forms display values from @@ -162,7 +162,7 @@ with additional convenience macros for generating form input elements themselves [[webflux-view-bind-macros]] ==== The Bind Macros -[.small]#<># +[.small]#<># A standard set of macros are maintained within the `spring-webflux.jar` file for FreeMarker, so they are always available to a suitably configured application. @@ -193,7 +193,7 @@ sections of the Spring MVC documentation. [[webflux-view-script]] == Script Views -[.small]#<># +[.small]#<># The Spring Framework has a built-in integration for using Spring WebFlux with any templating library that can run on top of the @@ -219,7 +219,7 @@ TIP: The basic rule for integrating any other script engine is that it must impl [[webflux-view-script-dependencies]] === Requirements -[.small]#<># +[.small]#<># You need to have the script engine on your classpath, the details of which vary by script engine: @@ -239,7 +239,7 @@ through https://www.webjars.org/[WebJars]. [[webflux-view-script-integrate]] === Script Templates -[.small]#<># +[.small]#<># You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use, the script files to load, what function to call to render templates, and so on. @@ -389,7 +389,7 @@ for more configuration examples. [[webflux-view-httpmessagewriter]] == JSON and XML -[.small]#<># +[.small]#<># For <> purposes, it is useful to be able to alternate between rendering a model with an HTML template or as other formats (such as JSON or XML), diff --git a/framework-docs/src/docs/asciidoc/web/webflux-webclient.adoc b/framework-docs/src/docs/asciidoc/web/webflux-webclient.adoc index e74d6f9d12e5..267a690d6283 100644 --- a/framework-docs/src/docs/asciidoc/web/webflux-webclient.adoc +++ b/framework-docs/src/docs/asciidoc/web/webflux-webclient.adoc @@ -156,6 +156,7 @@ application deployed as a WAR), you can declare a Spring-managed bean of type Netty global resources are shut down when the Spring `ApplicationContext` is closed, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -170,11 +171,13 @@ as the following example shows: @Bean fun reactorResourceFactory() = ReactorResourceFactory() ---- +-- You can also choose not to participate in the global Reactor Netty resources. However, in this mode, the burden is on you to ensure that all Reactor Netty client and server instances use shared resources, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -225,6 +228,7 @@ instances use shared resources, as the following example shows: <1> Create resources independent of global ones. <2> Use the `ReactorClientHttpConnector` constructor with resource factory. <3> Plug the connector into the `WebClient.Builder`. +-- [[webflux-client-builder-reactor-timeout]] @@ -253,7 +257,7 @@ To configure a connection timeout: .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000); val webClient = WebClient.builder() - .clientConnector(new ReactorClientHttpConnector(httpClient)) + .clientConnector(ReactorClientHttpConnector(httpClient)) .build(); ---- @@ -281,8 +285,8 @@ To configure a read or write timeout: val httpClient = HttpClient.create() .doOnConnected { conn -> conn - .addHandlerLast(new ReadTimeoutHandler(10)) - .addHandlerLast(new WriteTimeoutHandler(10)) + .addHandlerLast(ReadTimeoutHandler(10)) + .addHandlerLast(WriteTimeoutHandler(10)) } // Create WebClient... @@ -375,6 +379,7 @@ The following example shows how to customize the JDK `HttpClient`: The following example shows how to customize Jetty `HttpClient` settings: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -392,9 +397,10 @@ The following example shows how to customize Jetty `HttpClient` settings: httpClient.cookieStore = ... val webClient = WebClient.builder() - .clientConnector(new JettyClientHttpConnector(httpClient)) + .clientConnector(JettyClientHttpConnector(httpClient)) .build(); ---- +-- By default, `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`), which remain active until the process exits or `stop()` is called. @@ -404,6 +410,7 @@ ensure that the resources are shut down when the Spring `ApplicationContext` is declaring a Spring-managed bean of type `JettyResourceFactory`, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -446,6 +453,7 @@ shows: ---- <1> Use the `JettyClientHttpConnector` constructor with resource factory. <2> Plug the connector into the `WebClient.Builder`. +-- @@ -788,8 +796,8 @@ multipart request. The following example shows how to create a `MultiValueMap># +[.small]#<># This part of the reference documentation covers support for reactive-stack WebSocket messaging. @@ -12,7 +12,7 @@ include::websocket-intro.adoc[leveloffset=+1] [[webflux-websocket-server]] == WebSocket API -[.small]#<># +[.small]#<># The Spring Framework provides a WebSocket API that you can use to write client- and server-side applications that handle WebSocket messages. @@ -21,7 +21,7 @@ server-side applications that handle WebSocket messages. [[webflux-websocket-server-handler]] === Server -[.small]#<># +[.small]#<># To create a WebSocket server, you can first create a `WebSocketHandler`. The following example shows how to do so: @@ -339,7 +339,7 @@ subsequently use `DataBufferUtils.release(dataBuffer)` when the buffers are cons [[webflux-websocket-server-handshake]] === Handshake -[.small]#<># +[.small]#<># `WebSocketHandlerAdapter` delegates to a `WebSocketService`. By default, that is an instance of `HandshakeWebSocketService`, which performs basic checks on the WebSocket request and @@ -354,7 +354,7 @@ into the attributes of the `WebSocketSession`. [[webflux-websocket-server-config]] === Server Configuration -[.small]#<># +[.small]#<># The `RequestUpgradeStrategy` for each server exposes configuration specific to the underlying WebSocket server engine. When using the WebFlux Java config you can customize @@ -408,7 +408,7 @@ only Tomcat and Jetty expose such options. [[webflux-websocket-server-cors]] === CORS -[.small]#<># +[.small]#<># The easiest way to configure CORS and restrict access to a WebSocket endpoint is to have your `WebSocketHandler` implement `CorsConfigurationSource` and return a diff --git a/framework-docs/src/docs/asciidoc/web/webflux.adoc b/framework-docs/src/docs/asciidoc/web/webflux.adoc index 2123a530d18c..a0854315c4cf 100644 --- a/framework-docs/src/docs/asciidoc/web/webflux.adoc +++ b/framework-docs/src/docs/asciidoc/web/webflux.adoc @@ -1,7 +1,6 @@ [[webflux]] :chapter: webflux = Spring WebFlux -:doc-spring-security: {doc-root}/spring-security/reference The original web framework included in the Spring Framework, Spring Web MVC, was purpose-built for the Servlet API and Servlet containers. The reactive-stack web framework, @@ -249,20 +248,22 @@ current thread (and rely on callbacks instead) means that you do not need extra there are no blocking calls to absorb. +==== Invoking a Blocking API -.Invoking a Blocking API What if you do need to use a blocking library? Both Reactor and RxJava provide the `publishOn` operator to continue processing on a different thread. That means there is an easy escape hatch. Keep in mind, however, that blocking APIs are not a good fit for this concurrency model. -.Mutable State +==== Mutable State + In Reactor and RxJava, you declare logic through operators. At runtime, a reactive pipeline is formed where data is processed sequentially, in distinct stages. A key benefit of this is that it frees applications from having to protect mutable state because application code within that pipeline is never invoked concurrently. -.Threading Model +==== Threading Model + What threads should you expect to see on a server running with Spring WebFlux? * On a "`vanilla`" Spring WebFlux server (for example, no data access nor other optional @@ -286,7 +287,8 @@ specific thread pool `Scheduler` strategy. * Data access libraries and other third party dependencies can also create and use threads of their own. -.Configuring +==== Configuring + The Spring Framework does not provide support for starting and stopping <>. To configure the threading model for a server, you need to use server-specific configuration APIs, or, if you use Spring Boot, @@ -388,14 +390,14 @@ The code snippets below show using the `HttpHandler` adapters with each server A ---- HttpHandler handler = ... ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler); - HttpServer.create().host(host).port(port).handle(adapter).bind().block(); + HttpServer.create().host(host).port(port).handle(adapter).bindNow(); ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- val handler: HttpHandler = ... val adapter = ReactorHttpHandlerAdapter(handler) - HttpServer.create().host(host).port(port).handle(adapter).bind().block() + HttpServer.create().host(host).port(port).handle(adapter).bindNow() ---- *Undertow* @@ -595,7 +597,7 @@ The `DefaultServerWebExchange` uses the configured `HttpMessageReader` to parse [[webflux-multipart]] ==== Multipart Data -[.small]#<># +[.small]#<># `ServerWebExchange` exposes the following method for accessing multipart data: @@ -629,7 +631,7 @@ collecting to a `MultiValueMap`. [[webflux-forwarded-headers]] ==== Forwarded Headers -[.small]#<># +[.small]#<># As a request goes through proxies (such as load balancers), the host, port, and scheme may change. That makes it a challenge, from a client perspective, to create links that point to the correct @@ -660,7 +662,7 @@ filters, and `ForwardedHeaderTransformer` is used instead. [[webflux-filters]] === Filters -[.small]#<># +[.small]#<># In the <>, you can use a `WebFilter` to apply interception-style logic before and after the rest of the processing chain of filters and the target @@ -671,7 +673,7 @@ the bean declaration or by implementing `Ordered`. [[webflux-filters-cors]] ==== CORS -[.small]#<># +[.small]#<># Spring WebFlux provides fine-grained support for CORS configuration through annotations on controllers. However, when you use it with Spring Security, we advise relying on the built-in @@ -682,7 +684,7 @@ See the section on <> and the <> for more [[webflux-exception-handler]] === Exceptions -[.small]#<># +[.small]#<># In the <>, you can use a `WebExceptionHandler` to handle exceptions from the chain of `WebFilter` instances and the target `WebHandler`. When using the @@ -713,7 +715,7 @@ The following table describes the available `WebExceptionHandler` implementation [[webflux-codecs]] === Codecs -[.small]#<># +[.small]#<># The `spring-web` and `spring-core` modules provide support for serializing and deserializing byte content to and from higher level objects through non-blocking I/O with @@ -808,7 +810,7 @@ another `HttpMessageReader` for the actual parsing to a `Flux` and then si collects the parts into a `MultiValueMap`. By default, the `DefaultPartHttpMessageReader` is used, but this can be changed through the `ServerCodecConfigurer`. -For more information about the `DefaultPartHttpMessageReader`, refer to to the +For more information about the `DefaultPartHttpMessageReader`, refer to the {api-spring-framework}/http/codec/multipart/DefaultPartHttpMessageReader.html[javadoc of `DefaultPartHttpMessageReader`]. On the server side where multipart form content may need to be accessed from multiple @@ -853,7 +855,7 @@ To configure all three in WebFlux, you'll need to supply a pre-configured instan [[webflux-codecs-streaming]] ==== Streaming -[.small]#<># +[.small]#<># When streaming to the HTTP response (for example, `text/event-stream`, `application/x-ndjson`), it is important to send data periodically, in order to @@ -881,7 +883,7 @@ especially the section on <>. [[webflux-logging]] === Logging -[.small]#<># +[.small]#<># `DEBUG` level logging in Spring WebFlux is designed to be compact, minimal, and human-friendly. It focuses on high value bits of information that are useful over and @@ -913,7 +915,7 @@ while a fully formatted prefix based on that ID is available from [[webflux-logging-sensitive-data]] ==== Sensitive Data -[.small]#<># +[.small]#<># `DEBUG` and `TRACE` logging can log sensitive information. This is why form parameters and headers are masked by default and you must explicitly enable their logging in full. @@ -1015,7 +1017,7 @@ The following example shows how to do so for client-side requests: [[webflux-dispatcher-handler]] == `DispatcherHandler` -[.small]#<># +[.small]#<># Spring WebFlux, similarly to Spring MVC, is designed around the front controller pattern, where a central `WebHandler`, the `DispatcherHandler`, provides a shared algorithm for @@ -1058,7 +1060,7 @@ The resulting `HttpHandler` is ready for use with a <># +[.small]#<># The `DispatcherHandler` delegates to special beans to process requests and render the appropriate responses. By "`special beans,`" we mean Spring-managed `Object` instances that @@ -1100,7 +1102,7 @@ there are also some other beans detected at a lower level (see [[webflux-framework-config]] === WebFlux Config -[.small]#<># +[.small]#<># Applications can declare the infrastructure beans (listed under <> and @@ -1115,7 +1117,7 @@ many extra convenient options. [[webflux-dispatcher-handler-sequence]] === Processing -[.small]#<># +[.small]#<># `DispatcherHandler` processes requests as follows: @@ -1166,21 +1168,19 @@ as a `HandlerResult`, along with some additional context, and passed to the firs [[webflux-dispatcher-exceptions]] === Exceptions -[.small]#<># +[.small]#<># -The `HandlerResult` returned from a `HandlerAdapter` can expose a function for error -handling based on some handler-specific mechanism. This error function is called if: +`HandlerAdapter` implementations can handle internally exceptions from invoking a request +handler, such as a controller method. However, an exception may be deferred if the request +handler returns an asynchronous value. -* The handler (for example, `@Controller`) invocation fails. -* The handling of the handler return value through a `HandlerResultHandler` fails. +A `HandlerAdapter` may expose its exception handling mechanism as a +`DispatchExceptionHandler` set on the `HandlerResult` it returns. When that's set, +`DispatcherHandler` will also apply it to the handling of the result. -The error function can change the response (for example, to an error status), as long as an error -signal occurs before the reactive type returned from the handler produces any data items. - -This is how `@ExceptionHandler` methods in `@Controller` classes are supported. -By contrast, support for the same in Spring MVC is built on a `HandlerExceptionResolver`. -This generally should not matter. However, keep in mind that, in WebFlux, you cannot use a -`@ControllerAdvice` to handle exceptions that occur before a handler is chosen. +A `HandlerAdapter` may also choose to implement `DispatchExceptionHandler`. In that case +`DispatcherHandler` will apply it to exceptions that arise before a handler is mapped, +e.g. during handler mapping, or earlier, e.g. in a `WebFilter`. See also <> in the "`Annotated Controller`" section or <> in the WebHandler API section. @@ -1189,7 +1189,7 @@ See also <> in the "`Annotated Controller`" s [[webflux-viewresolution]] === View Resolution -[.small]#<># +[.small]#<># View resolution enables rendering to a browser with an HTML template and a model without tying you to a specific view technology. In Spring WebFlux, view resolution is @@ -1200,7 +1200,7 @@ instance. The `View` is then used to render the response. [[webflux-viewresolution-handling]] ==== Handling -[.small]#<># +[.small]#<># The `HandlerResult` passed into `ViewResolutionResultHandler` contains the return value from the handler and the model that contains attributes added during request @@ -1236,7 +1236,7 @@ See <> for more on the view technologies integrated with Spring We [[webflux-redirecting-redirect-prefix]] ==== Redirecting -[.small]#<># +[.small]#<># The special `redirect:` prefix in a view name lets you perform a redirect. The `UrlBasedViewResolver` (and sub-classes) recognize this as an instruction that a @@ -1251,7 +1251,7 @@ operate in terms of logical view names. A view name such as [[webflux-multiple-representations]] ==== Content Negotiation -[.small]#<># +[.small]#<># `ViewResolutionResultHandler` supports content negotiation. It compares the request media types with the media types supported by each selected `View`. The first `View` @@ -1268,7 +1268,7 @@ always selected and used if they match the requested media type. [[webflux-controller]] == Annotated Controllers -[.small]#<># +[.small]#<># Spring WebFlux provides an annotation-based programming model, where `@Controller` and `@RestController` components use annotations to express request mappings, request input, @@ -1306,7 +1306,7 @@ In the preceding example, the method returns a `String` to be written to the res [[webflux-ann-controller]] === `@Controller` -[.small]#<># +[.small]#<># You can define controller beans by using a standard Spring bean definition. The `@Controller` stereotype allows for auto-detection and is aligned with Spring general support @@ -1350,7 +1350,7 @@ directly to the response body versus view resolution and rendering with an HTML [[webflux-ann-requestmapping-proxying]] ==== AOP Proxies -[.small]#<># +[.small]#<># In some cases, you may need to decorate a controller with an AOP proxy at runtime. One example is if you choose to have `@Transactional` annotations directly on the @@ -1373,7 +1373,7 @@ Please, enable class based proxying, or otherwise the interface must also have a [[webflux-ann-requestmapping]] === Request Mapping -[.small]#<># +[.small]#<># The `@RequestMapping` annotation is used to map requests to controllers methods. It has various attributes to match by URL, HTTP method, request parameters, headers, and media @@ -1437,7 +1437,7 @@ The following example uses type and method level mappings: [[webflux-ann-requestmapping-uri-templates]] ==== URI Patterns -[.small]#<># +[.small]#<># You can map requests by using glob patterns and wildcards: @@ -1477,6 +1477,7 @@ You can map requests by using glob patterns and wildcards: Captured URI variables can be accessed with `@PathVariable`, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -1493,9 +1494,11 @@ Captured URI variables can be accessed with `@PathVariable`, as the following ex // ... } ---- +-- You can declare URI variables at the class and method levels, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -1527,6 +1530,7 @@ You can declare URI variables at the class and method levels, as the following e ---- <1> Class-level URI mapping. <2> Method-level URI mapping. +-- URI variables are automatically converted to the appropriate type or a `TypeMismatchException` @@ -1535,8 +1539,8 @@ register support for any other data type. See <> and <>. URI variables can be named explicitly (for example, `@PathVariable("customId")`), but you can -leave that detail out if the names are the same and you compile your code with debugging -information or with the `-parameters` compiler flag on Java 8. +leave that detail out if the names are the same and you compile your code with the `-parameters` +compiler flag. The syntax `{*varName}` declares a URI variable that matches zero or more remaining path segments. For example `/resources/{*path}` matches all files under `/resources/`, and the @@ -1546,6 +1550,7 @@ The syntax `{varName:regex}` declares a URI variable with a regular expression t syntax: `{varName:regex}`. For example, given a URL of `/spring-web-3.0.5.jar`, the following method extracts the name, version, and file extension: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -1562,10 +1567,11 @@ extracts the name, version, and file extension: // ... } ---- +-- URI path patterns can also have embedded `${...}` placeholders that are resolved on startup through `PropertySourcesPlaceholderConfigurer` against local, system, environment, and -other property sources. You ca use this to, for example, parameterize a base URL based on +other property sources. You can use this to, for example, parameterize a base URL based on some external configuration. NOTE: Spring WebFlux uses `PathPattern` and the `PathPatternParser` for URI path matching support. @@ -1580,7 +1586,7 @@ explicit, and less vulnerable to URL path based exploits. [[webflux-ann-requestmapping-pattern-comparison]] ==== Pattern Comparison -[.small]#<># +[.small]#<># When multiple patterns match a URL, they must be compared to find the best match. This is done with `PathPattern.SPECIFICITY_COMPARATOR`, which looks for patterns that are more specific. @@ -1595,7 +1601,7 @@ sorted last instead. If two patterns are both catch-all, the longer is chosen. [[webflux-ann-requestmapping-consumes]] ==== Consumable Media Types -[.small]#<># +[.small]#<># You can narrow the request mapping based on the `Content-Type` of the request, as the following example shows: @@ -1630,7 +1636,7 @@ TIP: `MediaType` provides constants for commonly used media types -- for example [[webflux-ann-requestmapping-produces]] ==== Producible Media Types -[.small]#<># +[.small]#<># You can narrow the request mapping based on the `Accept` request header and the list of content types that a controller method produces, as the following example shows: @@ -1667,7 +1673,7 @@ TIP: `MediaType` provides constants for commonly used media types -- e.g. [[webflux-ann-requestmapping-params-and-headers]] ==== Parameters and Headers -[.small]#<># +[.small]#<># You can narrow request mappings based on query parameter conditions. You can test for the presence of a query parameter (`myParam`), for its absence (`!myParam`), or for a @@ -1719,7 +1725,7 @@ You can also use the same with request header conditions, as the following examp [[webflux-ann-requestmapping-head-options]] ==== HTTP HEAD, OPTIONS -[.small]#<># +[.small]#<># `@GetMapping` and `@RequestMapping(method=HttpMethod.GET)` support HTTP HEAD transparently for request mapping purposes. Controller methods need not change. @@ -1740,7 +1746,7 @@ is not necessary in the common case. [[webflux-ann-requestmapping-composed]] ==== Custom Annotations -[.small]#<># +[.small]#<># Spring WebFlux supports the use of <> for request mapping. Those are annotations that are themselves meta-annotated with @@ -1761,7 +1767,7 @@ you can check the custom attribute and return your own `RequestCondition`. [[webflux-ann-requestmapping-registration]] ==== Explicit Registrations -[.small]#<># +[.small]#<># You can programmatically register Handler methods, which can be used for dynamic registrations or for advanced cases, such as different instances of the same handler @@ -1818,7 +1824,7 @@ under different URLs. The following example shows how to do so: [[webflux-ann-methods]] === Handler Methods -[.small]#<># +[.small]#<># `@RequestMapping` handler methods have a flexible signature and can choose from a range of supported controller method arguments and return values. @@ -1826,7 +1832,7 @@ supported controller method arguments and return values. [[webflux-ann-arguments]] ==== Method Arguments -[.small]#<># +[.small]#<># The following table shows the supported controller method arguments. @@ -1946,7 +1952,7 @@ and others) and is equivalent to `required=false`. [[webflux-ann-return-types]] ==== Return Values -[.small]#<># +[.small]#<># The following table shows the supported controller method return values. Note that reactive types from libraries such as Reactor, RxJava, <> are @@ -2007,7 +2013,7 @@ generally supported for all return values. value) is considered to have fully handled the response if it also has a `ServerHttpResponse`, a `ServerWebExchange` argument, or an `@ResponseStatus` annotation. The same is also true if the controller has made a positive ETag or `lastModified` timestamp check. - // TODO: See <> for details. + See <> for details. If none of the above is true, a `void` return type can also indicate "`no response body`" for REST controllers or default view name selection for HTML controllers. @@ -2017,10 +2023,9 @@ generally supported for all return values. to be written (however, `text/event-stream` must be requested or declared in the mapping through the `produces` attribute). -| Any other return value -| If a return value is not matched to any of the above, it is, by default, treated as a view - name, if it is `String` or `void` (default view name selection applies), or as a model - attribute to be added to the model, unless it is a simple type, as determined by +| Other return values +| If a return value remains unresolved in any other way, it is treated as a model + attribute, unless it is a simple type as determined by {api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty], in which case it remains unresolved. |=== @@ -2028,7 +2033,7 @@ generally supported for all return values. [[webflux-ann-typeconversion]] ==== Type Conversion -[.small]#<># +[.small]#<># Some annotated controller method arguments that represent String-based request input (for example, `@RequestParam`, `@RequestHeader`, `@PathVariable`, `@MatrixVariable`, and `@CookieValue`) @@ -2048,7 +2053,7 @@ argument as `@Nullable`. [[webflux-ann-matrix-variables]] ==== Matrix Variables -[.small]#<># +[.small]#<># https://tools.ietf.org/html/rfc3986#section-3.3[RFC 3986] discusses name-value pairs in path segments. In Spring WebFlux, we refer to those as "`matrix variables`" based on an @@ -2183,7 +2188,7 @@ To get all matrix variables, use a `MultiValueMap`, as the following example sho [[webflux-ann-requestparam]] ==== `@RequestParam` -[.small]#<># +[.small]#<># You can use the `@RequestParam` annotation to bind query parameters to a method argument in a controller. The following code snippet shows the usage: @@ -2258,7 +2263,7 @@ with `@RequestParam`. [[webflux-ann-requestheader]] ==== `@RequestHeader` -[.small]#<># +[.small]#<># You can use the `@RequestHeader` annotation to bind a request header to a method argument in a controller. @@ -2319,7 +2324,7 @@ example, a method parameter annotated with `@RequestHeader("Accept")` may be of [[webflux-ann-cookievalue]] ==== `@CookieValue` -[.small]#<># +[.small]#<># You can use the `@CookieValue` annotation to bind the value of an HTTP cookie to a method argument in a controller. @@ -2360,7 +2365,7 @@ Type conversion is applied automatically if the target method parameter type is [[webflux-ann-modelattrib-method-args]] ==== `@ModelAttribute` -[.small]#<># +[.small]#<># You can use the `@ModelAttribute` annotation on a method argument to access an attribute from the model or have it instantiated if not present. The model attribute is also overlaid with @@ -2507,7 +2512,7 @@ with `@ModelAttribute`. [[webflux-ann-sessionattributes]] ==== `@SessionAttributes` -[.small]#<># +[.small]#<># `@SessionAttributes` is used to store model attributes in the `WebSession` between requests. It is a type-level annotation that declares session attributes used by a @@ -2592,7 +2597,7 @@ as the following example shows: [[webflux-ann-sessionattribute]] ==== `@SessionAttribute` -[.small]#<># +[.small]#<># If you need access to pre-existing session attributes that are managed globally (that is, outside the controller -- for example, by a filter) and may or may not be present, @@ -2628,7 +2633,7 @@ workflow, consider using `SessionAttributes`, as described in [[webflux-ann-requestattrib]] ==== `@RequestAttribute` -[.small]#<># +[.small]#<># Similarly to `@SessionAttribute`, you can use the `@RequestAttribute` annotation to access pre-existing request attributes created earlier (for example, by a `WebFilter`), @@ -2657,13 +2662,14 @@ as the following example shows: [[webflux-multipart-forms]] ==== Multipart Content -[.small]#<># +[.small]#<># As explained in <>, `ServerWebExchange` provides access to multipart content. The best way to handle a file upload form (for example, from a browser) in a controller is through data binding to a <>, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -2704,6 +2710,7 @@ as the following example shows: } ---- +-- You can also submit multipart requests from non-browser clients in a RESTful service scenario. The following example uses a file along with JSON: @@ -2730,6 +2737,7 @@ Content-Transfer-Encoding: 8bit You can access individual parts with `@RequestPart`, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -2753,11 +2761,13 @@ You can access individual parts with `@RequestPart`, as the following example sh ---- <1> Using `@RequestPart` to get the metadata. <2> Using `@RequestPart` to get the file. +-- To deserialize the raw part content (for example, to JSON -- similar to `@RequestBody`), you can declare a concrete target `Object`, instead of `Part`, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -2777,6 +2787,7 @@ you can declare a concrete target `Object`, instead of `Part`, as the following } ---- <1> Using `@RequestPart` to get the metadata. +-- You can use `@RequestPart` in combination with `jakarta.validation.Valid` or Spring's `@Validated` annotation, which causes Standard Bean Validation to be applied. Validation @@ -2785,6 +2796,7 @@ The exception contains a `BindingResult` with the error details and can also be in the controller method by declaring the argument with an async wrapper and then using error related operators: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -2802,10 +2814,12 @@ error related operators: // ... } ---- +-- To access all multipart data as a `MultiValueMap`, you can use `@RequestBody`, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -2825,6 +2839,7 @@ as the following example shows: } ---- <1> Using `@RequestBody`. +-- ===== `PartEvent` @@ -2924,7 +2939,7 @@ See <>. [[webflux-ann-requestbody]] ==== `@RequestBody` -[.small]#<># +[.small]#<># You can use the `@RequestBody` annotation to have the request body read and deserialized into an `Object` through an <>. @@ -2998,7 +3013,7 @@ related operators: [[webflux-ann-httpentity]] ==== `HttpEntity` -[.small]#<># +[.small]#<># `HttpEntity` is more or less identical to using <> but is based on a container object that exposes request headers and the body. The following example uses an @@ -3024,7 +3039,7 @@ container object that exposes request headers and the body. The following exampl [[webflux-ann-responsebody]] ==== `@ResponseBody` -[.small]#<># +[.small]#<># You can use the `@ResponseBody` annotation on a method to have the return serialized to the response body through an <>. The following @@ -3067,7 +3082,7 @@ configure or customize message writing. [[webflux-ann-responseentity]] ==== `ResponseEntity` -[.small]#<># +[.small]#<># `ResponseEntity` is like <> but with status and headers. For example: @@ -3114,7 +3129,7 @@ Spring offers support for the Jackson JSON library. [[webflux-ann-jsonview]] ===== JSON Views -[.small]#<># +[.small]#<># Spring WebFlux provides built-in support for https://www.baeldung.com/jackson-json-view-annotation[Jackson's Serialization Views], @@ -3192,7 +3207,7 @@ controller method. Use a composite interface if you need to activate multiple vi [[webflux-ann-modelattrib-methods]] === `Model` -[.small]#<># +[.small]#<># You can use the `@ModelAttribute` annotation: @@ -3331,7 +3346,7 @@ as the following example shows: [[webflux-ann-initbinder]] === `DataBinder` -[.small]#<># +[.small]#<># `@Controller` or `@ControllerAdvice` classes can have `@InitBinder` methods, to initialize instances of `WebDataBinder`. Those, in turn, are used to: @@ -3351,6 +3366,7 @@ do, except for `@ModelAttribute` (command object) arguments. Typically, they are with a `WebDataBinder` argument, for registrations, and a `void` return value. The following example uses the `@InitBinder` annotation: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -3385,11 +3401,14 @@ The following example uses the `@InitBinder` annotation: // ... } ---- +<1> Using the `@InitBinder` annotation. +-- Alternatively, when using a `Formatter`-based setup through a shared `FormattingConversionService`, you could re-use the same approach and register controller-specific `Formatter` instances, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -3421,17 +3440,19 @@ controller-specific `Formatter` instances, as the following example shows: } ---- <1> Adding a custom formatter (a `DateFormatter`, in this case). +-- + [[webflux-ann-initbinder-model-design]] ==== Model Design -[.small]#<># +[.small]#<># include::web-data-binding-model-design.adoc[] [[webflux-ann-controller-exceptions]] === Exceptions -[.small]#<># +[.small]#<># `@Controller` and <> classes can have `@ExceptionHandler` methods to handle exceptions from controller methods. The following @@ -3493,7 +3514,7 @@ for more detail. [[webflux-ann-exceptionhandler-args]] ==== Method Arguments -[.small]#<># +[.small]#<># `@ExceptionHandler` methods support the same <> as `@RequestMapping` methods, except the request body might have been consumed already. @@ -3502,7 +3523,7 @@ as `@RequestMapping` methods, except the request body might have been consumed a [[webflux-ann-exceptionhandler-return-values]] ==== Return Values -[.small]#<># +[.small]#<># `@ExceptionHandler` methods support the same <> as `@RequestMapping` methods. @@ -3511,7 +3532,7 @@ as `@RequestMapping` methods. [[webflux-ann-controller-advice]] === Controller Advice -[.small]#<># +[.small]#<># Typically, the `@ExceptionHandler`, `@InitBinder`, and `@ModelAttribute` methods apply within the `@Controller` class (or class hierarchy) in which they are declared. If you @@ -3579,7 +3600,7 @@ include::webflux-functional.adoc[leveloffset=+1] [[webflux-uri-building]] == URI Links -[.small]#<># +[.small]#<># This section describes various options available in the Spring Framework to prepare URIs. @@ -3590,7 +3611,7 @@ include::webflux-cors.adoc[leveloffset=+1] [[webflux-ann-rest-exceptions]] == Error Responses -[.small]#<># +[.small]#<># A common requirement for REST services is to include details in the body of error responses. The Spring Framework supports the "Problem Details for HTTP APIs" @@ -3614,7 +3635,7 @@ and any `ErrorResponseException`, and renders an error response with a body. [[webflux-ann-rest-exceptions-render]] === Render -[.small]#<># +[.small]#<># You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows: @@ -3637,7 +3658,7 @@ use a protected method to map any exception to a `ProblemDetail`. [[webflux-ann-rest-exceptions-non-standard]] === Non-Standard Fields -[.small]#<># +[.small]#<># You can extend an RFC 7807 response with non-standard fields in one of two ways. @@ -3657,7 +3678,7 @@ from an existing `ProblemDetail`. This could be done centrally, e.g. from an [[webflux-ann-rest-exceptions-i18n]] === Internationalization -[.small]#<># +[.small]#<># It is a common requirement to internationalize error response details, and good practice to customize the problem details for Spring WebFlux exceptions. This is supported as follows: @@ -3728,7 +3749,7 @@ qualified exception class name. [[webflux-ann-rest-exceptions-client]] === Client Handling -[.small]#<># +[.small]#<># A client application can catch `WebClientResponseException`, when using the `WebClient`, or `RestClientResponseException` when using the `RestTemplate`, and use their @@ -3740,23 +3761,23 @@ or `RestClientResponseException` when using the `RestTemplate`, and use their [[webflux-web-security]] == Web Security -[.small]#<># +[.small]#<># The https://spring.io/projects/spring-security[Spring Security] project provides support for protecting web applications from malicious exploits. See the Spring Security reference documentation, including: -* {doc-spring-security}/reactive/configuration/webflux.html[WebFlux Security] -* {doc-spring-security}/reactive/test/index.html[WebFlux Testing Support] -* {doc-spring-security}/features/exploits/csrf.html#csrf-protection[CSRF protection] -* {doc-spring-security}/features/exploits/headers.html[Security Response Headers] +* {docs-spring-security}/reactive/configuration/webflux.html[WebFlux Security] +* {docs-spring-security}/reactive/test/index.html[WebFlux Testing Support] +* {docs-spring-security}/features/exploits/csrf.html#csrf-protection[CSRF protection] +* {docs-spring-security}/features/exploits/headers.html[Security Response Headers] [[webflux-caching]] == HTTP Caching -[.small]#<># +[.small]#<># HTTP caching can significantly improve the performance of a web application. HTTP caching revolves around the `Cache-Control` response header and subsequent conditional request @@ -3772,7 +3793,7 @@ This section describes the HTTP caching related options available in Spring WebF [[webflux-caching-cachecontrol]] === `CacheControl` -[.small]#<># +[.small]#<># {api-spring-framework}/http/CacheControl.html[`CacheControl`] provides support for configuring settings related to the `Cache-Control` header and is accepted as an argument @@ -3820,13 +3841,14 @@ use case-oriented approach that focuses on the common scenarios, as the followin [[webflux-caching-etag-lastmodified]] === Controllers -[.small]#<># +[.small]#<># Controllers can add explicit support for HTTP caching. We recommend doing so, since the `lastModified` or `ETag` value for a resource needs to be calculated before it can be compared against conditional request headers. A controller can add an `ETag` and `Cache-Control` settings to a `ResponseEntity`, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -3860,6 +3882,7 @@ settings to a `ResponseEntity`, as the following example shows: .body(book) } ---- +-- The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison to the conditional request headers indicates the content has not changed. Otherwise, the @@ -3868,6 +3891,7 @@ to the conditional request headers indicates the content has not changed. Otherw You can also make the check against conditional request headers in the controller, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -3907,6 +3931,7 @@ as the following example shows: <1> Application-specific calculation. <2> Response has been set to 304 (NOT_MODIFIED). No further processing. <3> Continue with request processing. +-- There are three variants for checking conditional requests against `eTag` values, `lastModified` values, or both. For conditional `GET` and `HEAD` requests, you can set the response to @@ -3917,7 +3942,7 @@ to 412 (PRECONDITION_FAILED) to prevent concurrent modification. [[webflux-caching-static-resources]] === Static Resources -[.small]#<># +[.small]#<># You should serve static resources with a `Cache-Control` and conditional response headers for optimal performance. See the section on configuring <>. @@ -3928,7 +3953,7 @@ include::webflux-view.adoc[leveloffset=+1] [[webflux-config]] == WebFlux Config -[.small]#<># +[.small]#<># The WebFlux Java configuration declares the components that are required to process requests with annotated controllers or functional endpoints, and it offers an API to @@ -3945,7 +3970,7 @@ gain full control over the configuration through the [[webflux-config-enable]] === Enabling WebFlux Config -[.small]#<># +[.small]#<># You can use the `@EnableWebFlux` annotation in your Java config, as the following example shows: @@ -3974,7 +3999,7 @@ available on the classpath -- for JSON, XML, and others. [[webflux-config-customize]] === WebFlux config API -[.small]#<># +[.small]#<># In your Java configuration, you can implement the `WebFluxConfigurer` interface, as the following example shows: @@ -4005,7 +4030,7 @@ class WebConfig : WebFluxConfigurer { [[webflux-config-conversion]] === Conversion, formatting -[.small]#<># +[.small]#<># By default, formatters for various number and date types are installed, along with support for customization via `@NumberFormat` and `@DateTimeFormat` on fields. @@ -4082,7 +4107,7 @@ use `FormatterRegistrar` implementations. [[webflux-config-validation]] === Validation -[.small]#<># +[.small]#<># By default, if <> is present on the classpath (for example, the Hibernate Validator), the `LocalValidatorFactoryBean` @@ -4157,7 +4182,7 @@ mark it with `@Primary` in order to avoid conflict with the one declared in the [[webflux-config-content-negotiation]] === Content Type Resolvers -[.small]#<># +[.small]#<># You can configure how Spring WebFlux determines the requested media types for `@Controller` instances from the request. By default, only the `Accept` header is checked, @@ -4195,7 +4220,7 @@ The following example shows how to customize the requested content type resoluti [[webflux-config-message-codecs]] === HTTP message codecs -[.small]#<># +[.small]#<># The following example shows how to customize how the request and response body are read and written: @@ -4246,7 +4271,7 @@ It also automatically registers the following well-known modules if they are det [[webflux-config-view-resolvers]] === View Resolvers -[.small]#<># +[.small]#<># The following example shows how to configure view resolution: @@ -4403,7 +4428,7 @@ See <> for more on the view technologies that are integrated with [[webflux-config-static-resources]] === Static Resources -[.small]#<># +[.small]#<># This option provides a convenient way to serve static resources from a list of {api-spring-framework}/core/io/Resource.html[`Resource`]-based locations. @@ -4412,7 +4437,7 @@ In the next example, given a request that starts with `/resources`, the relative used to find and serve static resources relative to `/static` on the classpath. Resources are served with a one-year future expiration to ensure maximum use of the browser cache and a reduction in HTTP requests made by the browser. The `Last-Modified` header is also -evaluated and, if present, a `304` status code is returned. The following list shows +evaluated and, if present, a `304` status code is returned. The following listing shows the example: [source,java,indent=0,subs="verbatim,quotes",role="primary"] @@ -4446,7 +4471,7 @@ the example: } ---- -// TODO: See also <>. +See also <>. The resource handler also supports a chain of {api-spring-framework}/web/reactive/resource/ResourceResolver.html[`ResourceResolver`] implementations and @@ -4527,7 +4552,7 @@ for fine-grained control, e.g. last-modified behavior and optimized resource res [[webflux-config-path-matching]] === Path Matching -[.small]#<># +[.small]#<># You can customize options related to path matching. For details on the individual options, see the {api-spring-framework}/web/reactive/config/PathMatchConfigurer.html[`PathMatchConfigurer`] javadoc. @@ -4628,7 +4653,7 @@ For example: [[webflux-config-advanced-java]] === Advanced Configuration Mode -[.small]#<># +[.small]#<># `@EnableWebFlux` imports `DelegatingWebFluxConfiguration` that: @@ -4668,7 +4693,7 @@ the classpath. [[webflux-http2]] == HTTP/2 -[.small]#<># +[.small]#<># HTTP/2 is supported with Reactor Netty, Tomcat, Jetty, and Undertow. However, there are considerations related to server configuration. For more details, see the diff --git a/framework-docs/src/docs/asciidoc/web/webmvc-cors.adoc b/framework-docs/src/docs/asciidoc/web/webmvc-cors.adoc index 007c8dada078..d5a3334a965a 100644 --- a/framework-docs/src/docs/asciidoc/web/webmvc-cors.adoc +++ b/framework-docs/src/docs/asciidoc/web/webmvc-cors.adoc @@ -1,7 +1,6 @@ [[mvc-cors]] = CORS -:doc-spring-security: {doc-root}/spring-security/reference -[.small]#<># +[.small]#<># Spring MVC lets you handle CORS (Cross-Origin Resource Sharing). This section describes how to do so. @@ -11,7 +10,7 @@ describes how to do so. [[mvc-cors-intro]] == Introduction -[.small]#<># +[.small]#<># For security reasons, browsers prohibit AJAX calls to resources outside the current origin. For example, you could have your bank account in one tab and evil.com in another. Scripts @@ -28,7 +27,7 @@ powerful workarounds based on IFRAME or JSONP. [[mvc-cors-processing]] == Processing -[.small]#<># +[.small]#<># The CORS specification distinguishes between preflight, simple, and actual requests. To learn how CORS works, you can read @@ -78,7 +77,7 @@ To learn more from the source or make advanced customizations, check the code be [[mvc-cors-controller]] == `@CrossOrigin` -[.small]#<># +[.small]#<># The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`] annotation enables cross-origin requests on annotated controller methods, @@ -227,7 +226,7 @@ as the following example shows: [[mvc-cors-global]] == Global Configuration -[.small]#<># +[.small]#<># In addition to fine-grained, controller method level configuration, you probably want to define some global CORS configuration, too. You can set URL-based `CorsConfiguration` @@ -253,7 +252,7 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig [[mvc-cors-global-java]] === Java Configuration -[.small]#<># +[.small]#<># To enable CORS in the MVC Java config, you can use the `CorsRegistry` callback, as the following example shows: @@ -330,13 +329,13 @@ as the following example shows: [[mvc-cors-filter]] == CORS Filter -[.small]#<># +[.small]#<># You can apply CORS support through the built-in {api-spring-framework}/web/filter/CorsFilter.html[`CorsFilter`]. NOTE: If you try to use the `CorsFilter` with Spring Security, keep in mind that Spring -Security has {doc-spring-security}/servlet/integrations/cors.html[built-in support] for +Security has {docs-spring-security}/servlet/integrations/cors.html[built-in support] for CORS. To configure the filter, pass a `CorsConfigurationSource` to its constructor, as the diff --git a/framework-docs/src/docs/asciidoc/web/webmvc-functional.adoc b/framework-docs/src/docs/asciidoc/web/webmvc-functional.adoc index 11fb793d4f08..07178eaecfe9 100644 --- a/framework-docs/src/docs/asciidoc/web/webmvc-functional.adoc +++ b/framework-docs/src/docs/asciidoc/web/webmvc-functional.adoc @@ -1,6 +1,6 @@ [[webmvc-fn]] = Functional Endpoints -[.small]#<># +[.small]#<># Spring Web MVC includes WebMvc.fn, a lightweight functional programming model in which functions are used to route and handle requests and contracts are designed for immutability. @@ -12,7 +12,7 @@ the same <>. [[webmvc-fn-overview]] == Overview -[.small]#<># +[.small]#<># In WebMvc.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes `ServerRequest` and returns a `ServerResponse`. @@ -40,7 +40,7 @@ as the following example shows: PersonRepository repository = ... PersonHandler handler = new PersonHandler(repository); - RouterFunction route = route() + RouterFunction route = route() // <1> .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) .GET("/person", accept(APPLICATION_JSON), handler::listPeople) .POST("/person", handler::createPerson) @@ -64,6 +64,7 @@ as the following example shows: } } ---- +<1> Create router using `route()`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin @@ -110,7 +111,7 @@ If you register the `RouterFunction` as a bean, for instance by exposing it in a [[webmvc-fn-handler-functions]] == HandlerFunction -[.small]#<># +[.small]#<># `ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly access to the HTTP request and response, including headers, body, method, and status code. @@ -237,22 +238,22 @@ allows you to send Strings, or other objects as JSON. For example: .Java ---- public RouterFunction sse() { - return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> { - // Save the sseBuilder object somewhere.. - })); + return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> { + // Save the sseBuilder object somewhere.. + })); } // In some other thread, sending a String sseBuilder.send("Hello world"); // Or an object, which will be transformed into JSON - Person person = ... + Person person = ... sseBuilder.send(person); - // Customize the event by using the other methods - sseBuilder.id("42") - .event("sse event") - .data(person); + // Customize the event by using the other methods + sseBuilder.id("42") + .event("sse event") + .data(person); // and done at some point sseBuilder.complete(); @@ -260,23 +261,23 @@ allows you to send Strings, or other objects as JSON. For example: [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - fun sse(): RouterFunction = router { - GET("/sse") { request -> ServerResponse.sse { sseBuilder -> - // Save the sseBuilder object somewhere.. - } - } + fun sse(): RouterFunction = router { + GET("/sse") { request -> ServerResponse.sse { sseBuilder -> + // Save the sseBuilder object somewhere.. + } + } // In some other thread, sending a String sseBuilder.send("Hello world") // Or an object, which will be transformed into JSON - val person = ... + val person = ... sseBuilder.send(person) - // Customize the event by using the other methods - sseBuilder.id("42") - .event("sse event") - .data(person) + // Customize the event by using the other methods + sseBuilder.id("42") + .event("sse event") + .data(person) // and done at some point sseBuilder.complete() @@ -289,6 +290,7 @@ allows you to send Strings, or other objects as JSON. For example: We can write a handler function as a lambda, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -301,6 +303,7 @@ HandlerFunction helloWorld = val helloWorld: (ServerRequest) -> ServerResponse = { ServerResponse.ok().body("Hello World") } ---- +-- That is convenient, but in an application we need multiple functions, and multiple inline lambda's can get messy. @@ -308,6 +311,7 @@ Therefore, it is useful to group related handler functions together into a handl has a similar role as `@Controller` in an annotation-based application. For example, the following class exposes a reactive `Person` repository: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -383,6 +387,7 @@ JSON. <3> `getPerson` is a handler function that returns a single person, identified by the `id` path variable. We retrieve that `Person` from the repository and create a JSON response, if it is found. If it is not found, we return a 404 Not Found response. +-- [[webmvc-fn-handler-validation]] @@ -458,7 +463,7 @@ See <>. [[webmvc-fn-router-functions]] == `RouterFunction` -[.small]#<># +[.small]#<># Router functions are used to route the requests to the corresponding `HandlerFunction`. Typically, you do not write router functions yourself, but rather use a method on the @@ -623,13 +628,14 @@ RouterFunction route = route() import org.springframework.web.servlet.function.router val route = router { - "/person".nest { + "/person".nest { // <1> GET("/{id}", accept(APPLICATION_JSON), handler::getPerson) GET(accept(APPLICATION_JSON), handler::listPeople) POST(handler::createPerson) } } ---- +<1> Using `nest` DSL. Though path-based nesting is the most common, you can nest on any kind of predicate by using the `nest` method on the builder. @@ -666,7 +672,7 @@ We can further improve by using the `nest` method together with `accept`: [[webmvc-fn-running]] == Running a Server -[.small]#<># +[.small]#<># You typically run router functions in a <>-based setup through the <>, which uses Spring configuration to declare the @@ -759,7 +765,7 @@ The following example shows a WebFlux Java configuration: [[webmvc-fn-handler-filter-function]] == Filtering Handler Functions -[.small]#<># +[.small]#<># You can filter handler functions by using the `before`, `after`, or `filter` methods on the routing function builder. diff --git a/framework-docs/src/docs/asciidoc/web/webmvc-test.adoc b/framework-docs/src/docs/asciidoc/web/webmvc-test.adoc index 87b6a5387458..d59d546fa5c2 100644 --- a/framework-docs/src/docs/asciidoc/web/webmvc-test.adoc +++ b/framework-docs/src/docs/asciidoc/web/webmvc-test.adoc @@ -1,6 +1,6 @@ [[webmvc.test]] = Testing -[.small]#<># +[.small]#<># This section summarizes the options available in `spring-test` for Spring MVC applications. @@ -25,4 +25,4 @@ See <> for more details. * `WebTestClient`: Built for testing WebFlux applications, but it can also be used for end-to-end integration testing, to any server, over an HTTP connection. It is a non-blocking, reactive client and is well suited for testing asynchronous and streaming -scenarios. +scenarios. See <> for more details. diff --git a/framework-docs/src/docs/asciidoc/web/webmvc-view.adoc b/framework-docs/src/docs/asciidoc/web/webmvc-view.adoc index b0725de35d8a..2b0b03c1399d 100644 --- a/framework-docs/src/docs/asciidoc/web/webmvc-view.adoc +++ b/framework-docs/src/docs/asciidoc/web/webmvc-view.adoc @@ -1,6 +1,6 @@ [[mvc-view]] = View Technologies -[.small]#<># +[.small]#<># The use of view technologies in Spring MVC is pluggable. Whether you decide to use Thymeleaf, Groovy Markup Templates, JSPs, or other technologies is primarily a matter of @@ -14,7 +14,7 @@ the templates are editable by external sources, since this can have security imp [[mvc-view-thymeleaf]] == Thymeleaf -[.small]#<># +[.small]#<># Thymeleaf is a modern server-side Java template engine that emphasizes natural HTML templates that can be previewed in a browser by double-clicking, which is very helpful @@ -34,7 +34,7 @@ See https://www.thymeleaf.org/documentation.html[Thymeleaf+Spring] for more deta [[mvc-view-freemarker]] == FreeMarker -[.small]#<># +[.small]#<># https://freemarker.apache.org/[Apache FreeMarker] is a template engine for generating any kind of text output from HTML to email and others. The Spring Framework has built-in @@ -44,7 +44,7 @@ integration for using Spring MVC with FreeMarker templates. [[mvc-view-freemarker-contextconfig]] === View Configuration -[.small]#<># +[.small]#<># The following example shows how to configure FreeMarker as a view technology: @@ -125,7 +125,7 @@ returns a view name of `welcome`, the resolver looks for the [[mvc-views-freemarker]] === FreeMarker Configuration -[.small]#<># +[.small]#<># You can pass FreeMarker 'Settings' and 'SharedVariables' directly to the FreeMarker `Configuration` object (which is managed by Spring) by setting the appropriate bean @@ -164,7 +164,7 @@ with additional convenience macros for generating form input elements themselves [[mvc-view-bind-macros]] ==== The Bind Macros -[.small]#<># +[.small]#<># A standard set of macros are maintained within the `spring-webmvc.jar` file for FreeMarker, so they are always available to a suitably configured application. @@ -288,7 +288,7 @@ as the value for the `fieldType` parameter. The parameters to any of the above macros have consistent meanings: -* `path`: The name of the field to bind to (ie "command.name") +* `path`: The name of the field to bind to (for example, "command.name") * `options`: A `Map` of all the available values that can be selected from in the input field. The keys to the map represent the values that are POSTed back from the form and bound to the command object. Map objects stored against the keys are the labels @@ -576,7 +576,7 @@ syntax. The following example shows a sample template for an HTML page: [[mvc-view-script]] == Script Views -[.small]#<># +[.small]#<># The Spring Framework has a built-in integration for using Spring MVC with any templating library that can run on top of the @@ -602,7 +602,7 @@ TIP: The basic rule for integrating any other script engine is that it must impl [[mvc-view-script-dependencies]] === Requirements -[.small]#<># +[.small]#<># You need to have the script engine on your classpath, the details of which vary by script engine: @@ -622,7 +622,7 @@ through https://www.webjars.org/[WebJars]. [[mvc-view-script-integrate]] === Script Templates -[.small]#<># +[.small]#<># You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use, the script files to load, what function to call to render templates, and so on. @@ -1965,7 +1965,7 @@ an external definition (by name) or as a `View` instance from the handler method [[mvc-view-jackson]] == Jackson -[.small]#<># +[.small]#<># Spring offers support for the Jackson JSON library. @@ -1973,7 +1973,7 @@ Spring offers support for the Jackson JSON library. [[mvc-view-json-mapping]] === Jackson-based JSON MVC Views -[.small]#<># +[.small]#<># The `MappingJackson2JsonView` uses the Jackson library's `ObjectMapper` to render the response content as JSON. By default, the entire contents of the model map (with the exception of @@ -1992,7 +1992,7 @@ serializers and deserializers for specific types. [[mvc-view-xml-mapping]] === Jackson-based XML Views -[.small]#<># +[.small]#<># `MappingJackson2XmlView` uses the https://github.com/FasterXML/jackson-dataformat-xml[Jackson XML extension's] `XmlMapper` diff --git a/framework-docs/src/docs/asciidoc/web/webmvc.adoc b/framework-docs/src/docs/asciidoc/web/webmvc.adoc index 2ca22c135e7b..fb8c2a12cca1 100644 --- a/framework-docs/src/docs/asciidoc/web/webmvc.adoc +++ b/framework-docs/src/docs/asciidoc/web/webmvc.adoc @@ -1,18 +1,17 @@ [[mvc]] :chapter: mvc = Spring Web MVC -:doc-spring-security: {doc-root}/spring-security/reference Spring Web MVC is the original web framework built on the Servlet API and has been included -in the Spring Framework from the very beginning. The formal name, "`Spring Web MVC,`" +in the Spring Framework from the very beginning. The formal name, "Spring Web MVC," comes from the name of its source module ({spring-framework-main-code}/spring-webmvc[`spring-webmvc`]), -but it is more commonly known as "`Spring MVC`". +but it is more commonly known as "Spring MVC". Parallel to Spring Web MVC, Spring Framework 5.0 introduced a reactive-stack web framework -whose name, "`Spring WebFlux,`" is also based on its source module +whose name, "Spring WebFlux," is also based on its source module ({spring-framework-main-code}/spring-webflux[`spring-webflux`]). -This section covers Spring Web MVC. The <> +This chapter covers Spring Web MVC. The <> covers Spring WebFlux. For baseline information and compatibility with Servlet container and Jakarta EE version @@ -24,7 +23,7 @@ https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versio [[mvc-servlet]] == DispatcherServlet -[.small]#<># +[.small]#<># Spring MVC, as many other web frameworks, is designed around the front controller pattern where a central `Servlet`, the `DispatcherServlet`, provides a shared algorithm @@ -241,7 +240,7 @@ TIP: If an application context hierarchy is not required, applications may confi [[mvc-servlet-special-bean-types]] === Special Bean Types -[.small]#<># +[.small]#<># The `DispatcherServlet` delegates to special beans to process requests and render the appropriate responses. By "`special beans`" we mean Spring-managed `Object` instances that @@ -301,7 +300,7 @@ The following table lists the special beans detected by the `DispatcherServlet`: [[mvc-servlet-config]] === Web MVC Config -[.small]#<># +[.small]#<># Applications can declare the infrastructure beans listed in <> that are required to process requests. The `DispatcherServlet` checks the @@ -504,7 +503,7 @@ override the `createDispatcherServlet` method. [[mvc-servlet-sequence]] === Processing -[.small]#<># +[.small]#<># The `DispatcherServlet` processes requests as follows: @@ -663,7 +662,7 @@ declare it as an <> bean or configure it directly on [[mvc-exceptionhandlers]] === Exceptions -[.small]#<># +[.small]#<># If an exception occurs during request mapping or is thrown from a request handler (such as a `@Controller`), the `DispatcherServlet` delegates to a chain of `HandlerExceptionResolver` @@ -774,7 +773,7 @@ however, use both a `WebApplicationInitializer` and a minimal `web.xml`. [[mvc-viewresolver]] === View Resolution -[.small]#<># +[.small]#<># Spring MVC defines the `ViewResolver` and `View` interfaces that let you render models in a browser without tying you to a specific view technology. `ViewResolver` @@ -826,7 +825,7 @@ The following table provides more details on the `ViewResolver` hierarchy: [[mvc-viewresolver-handling]] ==== Handling -[.small]#<># +[.small]#<># You can chain view resolvers by declaring more than one resolver bean and, if necessary, by setting the `order` property to specify ordering. Remember, the higher the order property, @@ -847,7 +846,7 @@ rendering without controller logic. [[mvc-redirecting-redirect-prefix]] ==== Redirecting -[.small]#<># +[.small]#<># The special `redirect:` prefix in a view name lets you perform a redirect. The `UrlBasedViewResolver` (and its subclasses) recognize this as an instruction that a @@ -877,7 +876,7 @@ Servlet/JSP engine. Note that you may also chain multiple view resolvers, instea [[mvc-multiple-representations]] ==== Content Negotiation -[.small]#<># +[.small]#<># {api-spring-framework}/web/servlet/view/ContentNegotiatingViewResolver.html[`ContentNegotiatingViewResolver`] does not resolve views itself but rather delegates @@ -979,7 +978,7 @@ The following table describes the properties `CookieLocaleResolver`: | Property | Default | Description | `cookieName` -| classname + LOCALE +| class name + LOCALE | The name of the cookie | `cookieMaxAge` @@ -1139,12 +1138,13 @@ request with a simple request parameter. [[mvc-multipart]] === Multipart Resolver -[.small]#<># +[.small]#<># `MultipartResolver` from the `org.springframework.web.multipart` package is a strategy -for parsing multipart requests including file uploads. There is one implementation -based on https://commons.apache.org/proper/commons-fileupload[Commons FileUpload] and -another based on Servlet multipart request parsing. +for parsing multipart requests including file uploads. There is a container-based +`StandardServletMultipartResolver` implementation for Servlet multipart request parsing. +Note that the outdated `CommonsMultipartResolver` based on Apache Commons FileUpload is +not available anymore, as of Spring Framework 6.0 with its new Servlet 5.0+ baseline. To enable multipart handling, you need to declare a `MultipartResolver` bean in your `DispatcherServlet` Spring configuration with a name of `multipartResolver`. @@ -1154,26 +1154,6 @@ content wraps the current `HttpServletRequest` as a `MultipartHttpServletRequest provide access to resolved files in addition to exposing parts as request parameters. -[[mvc-multipart-resolver-commons]] -==== Apache Commons `FileUpload` - -To use Apache Commons `FileUpload`, you can configure a bean of type -`CommonsMultipartResolver` with a name of `multipartResolver`. You also need to have -the `commons-fileupload` jar as a dependency on your classpath. - -This resolver variant delegates to a local library within the application, providing -maximum portability across Servlet containers. As an alternative, consider standard -Servlet multipart resolution through the container's own parser as discussed below. - -[NOTE] -==== -Commons FileUpload traditionally applies to POST requests only but accepts any -`multipart/` content type. See the -{api-spring-framework}/web/multipart/commons/CommonsMultipartResolver.html[`CommonsMultipartResolver`] -javadoc for details and configuration options. -==== - - [[mvc-multipart-resolver-standard]] ==== Servlet Multipart Parsing @@ -1234,7 +1214,7 @@ javadoc for details and configuration options. [[mvc-logging]] === Logging -[.small]#<># +[.small]#<># DEBUG-level logging in Spring MVC is designed to be compact, minimal, and human-friendly. It focuses on high-value bits of information that are useful over and @@ -1250,7 +1230,7 @@ not meet the stated goals, please let us know. [[mvc-logging-sensitive-data]] ==== Sensitive Data -[.small]#<># +[.small]#<># DEBUG and TRACE logging may log sensitive information. This is why request parameters and headers are masked by default and their logging in full must be enabled explicitly @@ -1314,7 +1294,7 @@ public class MyInitializer [[filters]] == Filters -[.small]#<># +[.small]#<># The `spring-web` module provides some useful filters: @@ -1341,7 +1321,7 @@ available through the `ServletRequest.getParameter{asterisk}()` family of method [[filters-forwarded-headers]] === Forwarded Headers -[.small]#<># +[.small]#<># As a request goes through proxies (such as load balancers) the host, port, and scheme may change, and that makes it a challenge to create links that point to the correct @@ -1402,7 +1382,7 @@ the filter via `web.xml` or in Spring Boot via a `FilterRegistrationBean` be sur [[filters-cors]] === CORS -[.small]#<># +[.small]#<># Spring MVC provides fine-grained support for CORS configuration through annotations on controllers. However, when used with Spring Security, we advise relying on the built-in @@ -1415,7 +1395,7 @@ See the sections on <> and the <> for more details. [[mvc-controller]] == Annotated Controllers -[.small]#<># +[.small]#<># Spring MVC provides an annotation-based programming model where `@Controller` and `@RestController` components use annotations to express request mappings, request input, @@ -1462,7 +1442,7 @@ programming model described in this section. [[mvc-ann-controller]] === Declaration -[.small]#<># +[.small]#<># You can define controller beans by using a standard Spring bean definition in the Servlet's `WebApplicationContext`. The `@Controller` stereotype allows for auto-detection, @@ -1524,7 +1504,7 @@ directly to the response body versus view resolution and rendering with an HTML [[mvc-ann-requestmapping-proxying]] ==== AOP Proxies -[.small]#<># +[.small]#<># In some cases, you may need to decorate a controller with an AOP proxy at runtime. One example is if you choose to have `@Transactional` annotations directly on the @@ -1546,7 +1526,7 @@ Please, enable class based proxying, or otherwise the interface must also have a [[mvc-ann-requestmapping]] === Request Mapping -[.small]#<># +[.small]#<># You can use the `@RequestMapping` annotation to map requests to controllers methods. It has various attributes to match by URL, HTTP method, request parameters, headers, and media @@ -1611,7 +1591,7 @@ The following example has type and method level mappings: [[mvc-ann-requestmapping-uri-templates]] ==== URI patterns -[.small]#<># +[.small]#<># `@RequestMapping` methods can be mapped using URL patterns. There are two alternatives: @@ -1700,8 +1680,8 @@ register support for any other data type. See <> and <>. You can explicitly name URI variables (for example, `@PathVariable("customId")`), but you can -leave that detail out if the names are the same and your code is compiled with debugging -information or with the `-parameters` compiler flag on Java 8. +leave that detail out if the names are the same and your code is compiled with the `-parameters` +compiler flag. The syntax `{varName:regex}` declares a URI variable with a regular expression that has syntax of `{varName:regex}`. For example, given URL `"/spring-web-3.0.5.jar"`, the following method @@ -1733,7 +1713,7 @@ some external configuration. [[mvc-ann-requestmapping-pattern-comparison]] ==== Pattern Comparison -[.small]#<># +[.small]#<># When multiple patterns match a URL, the best match must be selected. This is done with one of the following depending on whether use of parsed `PathPattern` is enabled for use or not: @@ -1814,7 +1794,7 @@ recommendations related to RFD. [[mvc-ann-requestmapping-consumes]] ==== Consumable Media Types -[.small]#<># +[.small]#<># You can narrow the request mapping based on the `Content-Type` of the request, as the following example shows: @@ -1852,7 +1832,7 @@ TIP: `MediaType` provides constants for commonly used media types, such as [[mvc-ann-requestmapping-produces]] ==== Producible Media Types -[.small]#<># +[.small]#<># You can narrow the request mapping based on the `Accept` request header and the list of content types that a controller method produces, as the following example shows: @@ -1892,7 +1872,7 @@ TIP: `MediaType` provides constants for commonly used media types, such as [[mvc-ann-requestmapping-params-and-headers]] ==== Parameters, headers -[.small]#<># +[.small]#<># You can narrow request mappings based on request parameter conditions. You can test for the presence of a request parameter (`myParam`), for the absence of one (`!myParam`), or for a @@ -1938,6 +1918,7 @@ You can also use the same with request header conditions, as the following examp // ... } ---- +<1> Testing whether `myHeader` equals `myValue`. TIP: You can match `Content-Type` and `Accept` with the headers condition, but it is better to use <> and <> @@ -1946,7 +1927,7 @@ instead. [[mvc-ann-requestmapping-head-options]] ==== HTTP HEAD, OPTIONS -[.small]#<># +[.small]#<># `@GetMapping` (and `@RequestMapping(method=HttpMethod.GET)`) support HTTP HEAD transparently for request mapping. Controller methods do not need to change. @@ -1972,7 +1953,7 @@ is not necessary in the common case. [[mvc-ann-requestmapping-composed]] ==== Custom Annotations -[.small]#<># +[.small]#<># Spring MVC supports the use of <> for request mapping. Those are annotations that are themselves meta-annotated with @@ -1993,7 +1974,7 @@ you can check the custom attribute and return your own `RequestCondition`. [[mvc-ann-requestmapping-registration]] ==== Explicit Registrations -[.small]#<># +[.small]#<># You can programmatically register handler methods, which you can use for dynamic registrations or for advanced cases, such as different instances of the same handler @@ -2046,7 +2027,7 @@ under different URLs. The following example registers a handler method: [[mvc-ann-methods]] === Handler Methods -[.small]#<># +[.small]#<># `@RequestMapping` handler methods have a flexible signature and can choose from a range of supported controller method arguments and return values. @@ -2054,7 +2035,7 @@ supported controller method arguments and return values. [[mvc-ann-arguments]] ==== Method Arguments -[.small]#<># +[.small]#<># The next table describes the supported controller method arguments. Reactive types are not supported for any arguments. @@ -2195,7 +2176,7 @@ and others) and is equivalent to `required=false`. [[mvc-ann-return-types]] ==== Return Values -[.small]#<># +[.small]#<># The next table describes the supported controller method return values. Reactive types are supported for all return values. @@ -2282,29 +2263,24 @@ supported for all return values. | Write to the response `OutputStream` asynchronously. Also supported as the body of a `ResponseEntity`. See <> and <>. -| Reactive types -- Reactor, RxJava, or others through `ReactiveAdapterRegistry` -| Alternative to `DeferredResult` with multi-value streams (for example, `Flux`, `Observable`) - collected to a `List`. - - For streaming scenarios (for example, `text/event-stream`, `application/json+stream`), - `SseEmitter` and `ResponseBodyEmitter` are used instead, where `ServletOutputStream` - blocking I/O is performed on a Spring MVC-managed thread and back pressure is applied - against the completion of each write. - - See <> and <>. - -| Any other return value -| Any return value that does not match any of the earlier values in this table and that - is a `String` or `void` is treated as a view name (default view name selection through - `RequestToViewNameTranslator` applies), provided it is not a simple type, as determined by - {api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]. - Values that are simple types remain unresolved. +| Reactor and other reactive types registered via `ReactiveAdapterRegistry` +| A single value type, e.g. `Mono`, is comparable to returning `DeferredResult`. + A multi-value type, e.g. `Flux`, may be treated as a stream depending on the requested + media type, e.g. "text/event-stream", "application/json+stream", or otherwise is + collected to a List and rendered as a single value. See <> and + <>. + +| Other return values +| If a return value remains unresolved in any other way, it is treated as a model + attribute, unless it is a simple type as determined by + {api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty], + in which case it remains unresolved. |=== [[mvc-ann-typeconversion]] ==== Type Conversion -[.small]#<># +[.small]#<># Some annotated controller method arguments that represent `String`-based request input (such as `@RequestParam`, `@RequestHeader`, `@PathVariable`, `@MatrixVariable`, and `@CookieValue`) @@ -2337,7 +2313,7 @@ an empty original value, so the corresponding `Missing...Exception` variants wil [[mvc-ann-matrix-variables]] ==== Matrix Variables -[.small]#<># +[.small]#<># https://tools.ietf.org/html/rfc3986#section-3.3[RFC 3986] discusses name-value pairs in path segments. In Spring MVC, we refer to those as "`matrix variables`" based on an @@ -2477,7 +2453,7 @@ you need to set a `UrlPathHelper` with `removeSemicolonContent=false` through [[mvc-ann-requestparam]] ==== `@RequestParam` -[.small]#<># +[.small]#<># You can use the `@RequestParam` annotation to bind Servlet request parameters (that is, query parameters or form data) to a method argument in a controller. @@ -2553,7 +2529,7 @@ with `@RequestParam`. [[mvc-ann-requestheader]] ==== `@RequestHeader` -[.small]#<># +[.small]#<># You can use the `@RequestHeader` annotation to bind a request header to a method argument in a controller. @@ -2614,7 +2590,7 @@ example, a method parameter annotated with `@RequestHeader("Accept")` can be of [[mvc-ann-cookievalue]] ==== `@CookieValue` -[.small]#<># +[.small]#<># You can use the `@CookieValue` annotation to bind the value of an HTTP cookie to a method argument in a controller. @@ -2654,7 +2630,7 @@ See <>. [[mvc-ann-modelattrib-method-args]] ==== `@ModelAttribute` -[.small]#<># +[.small]#<># You can use the `@ModelAttribute` annotation on a method argument to access an attribute from the model or have it be instantiated if not present. The model attribute is also overlain with @@ -2666,19 +2642,21 @@ query parameters and form fields. The following example shows how to do so: .Java ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") - public String processSubmit(@ModelAttribute Pet pet) { + public String processSubmit(@ModelAttribute Pet pet) { // <1> // method logic... } ---- +<1> Bind an instance of `Pet`. [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- @PostMapping("/owners/{ownerId}/pets/{petId}/edit") -fun processSubmit(@ModelAttribute pet: Pet): String { +fun processSubmit(@ModelAttribute pet: Pet): String { // <1> // method logic... } ---- +<1> Bind an instance of `Pet`. The `Pet` instance above is sourced in one of the following ways: @@ -2706,18 +2684,21 @@ could load the `Account` from a data store: .Java ---- @PutMapping("/accounts/{account}") - public String save(@ModelAttribute("account") Account account) { + public String save(@ModelAttribute("account") Account account) { // <1> // ... } ---- +<1> Bind an instance of `Account` using an explicit attribute name. + [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- @PutMapping("/accounts/{account}") - fun save(@ModelAttribute("account") account: Account): String { + fun save(@ModelAttribute("account") account: Account): String { // <1> // ... } ---- +<1> Bind an instance of `Account` using an explicit attribute name. After the model attribute instance is obtained, data binding is applied. The `WebDataBinder` class matches Servlet request parameter names (query parameters and form @@ -2831,6 +2812,7 @@ You can automatically apply validation after data binding by adding the // ... } ---- +<1> Validate the `Pet` instance. Note that using `@ModelAttribute` is optional (for example, to set its attributes). By default, any argument that is not a simple value type (as determined by @@ -2841,7 +2823,7 @@ with `@ModelAttribute`. [[mvc-ann-sessionattributes]] ==== `@SessionAttributes` -[.small]#<># +[.small]#<># `@SessionAttributes` is used to store model attributes in the HTTP Servlet session between requests. It is a type-level annotation that declares the session attributes used by a @@ -2925,7 +2907,7 @@ class EditPetForm { [[mvc-ann-sessionattribute]] ==== `@SessionAttribute` -[.small]#<># +[.small]#<># If you need access to pre-existing session attributes that are managed globally (that is, outside the controller -- for example, by a filter) and may or may not be present, @@ -2950,6 +2932,7 @@ as the following example shows: // ... } ---- +<1> Using a `@SessionAttribute` annotation. For use cases that require adding or removing session attributes, consider injecting `org.springframework.web.context.request.WebRequest` or @@ -2962,7 +2945,7 @@ workflow, consider using `@SessionAttributes` as described in [[mvc-ann-requestattrib]] ==== `@RequestAttribute` -[.small]#<># +[.small]#<># Similar to `@SessionAttribute`, you can use the `@RequestAttribute` annotations to access pre-existing request attributes created earlier (for example, by a Servlet `Filter` @@ -3089,7 +3072,7 @@ Therefore, we recommend that you use flash attributes mainly for redirect scenar [[mvc-multipart-forms]] ==== Multipart -[.small]#<># +[.small]#<># After a `MultipartResolver` has been <>, the content of POST requests with `multipart/form-data` is parsed and accessible as regular request @@ -3274,7 +3257,7 @@ as the following example shows: [[mvc-ann-requestbody]] ==== `@RequestBody` -[.small]#<># +[.small]#<># You can use the `@RequestBody` annotation to have the request body read and deserialized into an `Object` through an <>. @@ -3328,7 +3311,7 @@ as the following example shows: [[mvc-ann-httpentity]] ==== HttpEntity -[.small]#<># +[.small]#<># `HttpEntity` is more or less identical to using <> but is based on a container object that exposes request headers and body. The following listing shows an example: @@ -3354,7 +3337,7 @@ container object that exposes request headers and body. The following listing sh [[mvc-ann-responsebody]] ==== `@ResponseBody` -[.small]#<># +[.small]#<># You can use the `@ResponseBody` annotation on a method to have the return serialized to the response body through an @@ -3396,7 +3379,7 @@ See <> for details. [[mvc-ann-responseentity]] ==== ResponseEntity -[.small]#<># +[.small]#<># `ResponseEntity` is like <> but with status and headers. For example: @@ -3440,7 +3423,7 @@ Spring offers support for the Jackson JSON library. [[mvc-ann-jsonview]] ===== JSON Views -[.small]#<># +[.small]#<># Spring MVC provides built-in support for https://www.baeldung.com/jackson-json-view-annotation[Jackson's Serialization Views], @@ -3564,8 +3547,6 @@ to the model, as the following example shows: [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - import org.springframework.ui.set - @Controller class UserController : AbstractController() { @@ -3582,7 +3563,7 @@ to the model, as the following example shows: [[mvc-ann-modelattrib-methods]] === Model -[.small]#<># +[.small]#<># You can use the `@ModelAttribute` annotation: @@ -3680,7 +3661,7 @@ unless the return value is a `String` that would otherwise be interpreted as a v [[mvc-ann-initbinder]] === `DataBinder` -[.small]#<># +[.small]#<># `@Controller` or `@ControllerAdvice` classes can have `@InitBinder` methods that initialize instances of `WebDataBinder`, and those, in turn, can: @@ -3774,14 +3755,14 @@ controller-specific `Formatter` implementations, as the following example shows: [[mvc-ann-initbinder-model-design]] ==== Model Design -[.small]#<># +[.small]#<># include::web-data-binding-model-design.adoc[] [[mvc-ann-exceptionhandler]] === Exceptions -[.small]#<># +[.small]#<># `@Controller` and <> classes can have `@ExceptionHandler` methods to handle exceptions from controller methods, as the following example shows: @@ -3907,7 +3888,7 @@ level, <> mechanism. [[mvc-ann-exceptionhandler-args]] ==== Method Arguments -[.small]#<># +[.small]#<># `@ExceptionHandler` methods support the following arguments: @@ -3972,7 +3953,7 @@ level, <> mechanism. [[mvc-ann-exceptionhandler-return-values]] ==== Return Values -[.small]#<># +[.small]#<># `@ExceptionHandler` methods support the following return values: @@ -4042,7 +4023,7 @@ see <> [[mvc-ann-controller-advice]] === Controller Advice -[.small]#<># +[.small]#<># `@ExceptionHandler`, `@InitBinder`, and `@ModelAttribute` methods apply only to the `@Controller` class, or class hierarchy, in which they are declared. If, instead, they @@ -4106,7 +4087,7 @@ include::webmvc-functional.adoc[leveloffset=+1] [[mvc-uri-building]] == URI Links -[.small]#<># +[.small]#<># This section describes various options available in the Spring Framework to work with URI's. @@ -4385,7 +4366,6 @@ capital letters of the class and the method name (for example, the `getThing` me [[mvc-ann-async]] == Asynchronous Requests -[.small]#<># Spring MVC has an extensive integration with Servlet asynchronous request <>: @@ -4398,11 +4378,10 @@ return value. * Controllers can use reactive clients and return <> for response handling. - +For an overview of how this differs from Spring WebFlux, see the <> section below. [[mvc-ann-async-deferredresult]] === `DeferredResult` -[.small]#<># Once the asynchronous request processing feature is <> in the Servlet container, controller methods can wrap any supported controller method @@ -4444,7 +4423,6 @@ example, in response to an external event (JMS message), a scheduled task, or ot [[mvc-ann-async-callable]] === `Callable` -[.small]#<># A controller can wrap any supported return value with `java.util.concurrent.Callable`, as the following example shows: @@ -4454,13 +4432,7 @@ as the following example shows: ---- @PostMapping public Callable processUpload(final MultipartFile file) { - - return new Callable() { - public String call() throws Exception { - // ... - return "someView"; - } - }; + return () -> "someView"; } ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -4480,7 +4452,6 @@ The return value can then be obtained by running the given task through the [[mvc-ann-async-processing]] === Processing -[.small]#<># Here is a very concise overview of Servlet asynchronous request processing: @@ -4558,7 +4529,7 @@ methods for timeout and completion callbacks. [[mvc-ann-async-vs-webflux]] -==== Compared to WebFlux +==== Async Spring MVC compared to WebFlux The Servlet API was originally built for making a single pass through the Filter-Servlet chain. Asynchronous request processing lets applications exit the Filter-Servlet chain @@ -4585,11 +4556,13 @@ types in controller method arguments (for example, `@RequestBody`, `@RequestPart nor does it have any explicit support for asynchronous and reactive types as model attributes. Spring WebFlux does support all that. +Finally, from a configuration perspective the asynchronous request processing feature must be +<>. [[mvc-ann-async-http-streaming]] === HTTP Streaming -[.small]#<># +[.small]#<># You can use `DeferredResult` and `Callable` for a single asynchronous return value. What if you want to produce multiple asynchronous values and have those written to the @@ -4742,7 +4715,7 @@ customize the status and headers of the response. [[mvc-ann-async-reactive-types]] === Reactive Types -[.small]#<># +[.small]#<># Spring MVC supports use of reactive client libraries in a controller (also read <> in the WebFlux section). @@ -4797,7 +4770,7 @@ are written to the Reactor `Context` as key-value pairs, using the key assigned For other asynchronous handling scenarios, you can use the Context Propagation library directly. For example: -[source,java,indent=0,subs="verbatim,quotes",role="primary"] +[source,java,indent=0,subs="verbatim,quotes"] .Java ---- // Capture ThreadLocal values from the main thread ... @@ -4817,7 +4790,7 @@ Propagation library. [[mvc-ann-async-disconnects]] === Disconnects -[.small]#<># +[.small]#<># The Servlet API does not provide any notification when a remote client goes away. Therefore, while streaming to the response, whether through <> @@ -4834,7 +4807,6 @@ that have a built-in heartbeat mechanism. [[mvc-ann-async-configuration]] === Configuration -[.small]#<># The asynchronous request processing feature must be enabled at the Servlet container level. The MVC configuration also exposes several options for asynchronous requests. @@ -4884,7 +4856,7 @@ include::webmvc-cors.adoc[leveloffset=+1] [[mvc-ann-rest-exceptions]] == Error Responses -[.small]#<># +[.small]#<># A common requirement for REST services is to include details in the body of error responses. The Spring Framework supports the "Problem Details for HTTP APIs" @@ -4908,7 +4880,7 @@ and any `ErrorResponseException`, and renders an error response with a body. [[mvc-ann-rest-exceptions-render]] === Render -[.small]#<># +[.small]#<># You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows: @@ -4931,7 +4903,7 @@ use a protected method to map any exception to a `ProblemDetail`. [[mvc-ann-rest-exceptions-non-standard]] === Non-Standard Fields -[.small]#<># +[.small]#<># You can extend an RFC 7807 response with non-standard fields in one of two ways. @@ -4951,7 +4923,7 @@ from an existing `ProblemDetail`. This could be done centrally, e.g. from an [[mvc-ann-rest-exceptions-i18n]] === Internationalization -[.small]#<># +[.small]#<># It is a common requirement to internationalize error response details, and good practice to customize the problem details for Spring MVC exceptions. This is supported as follows: @@ -5061,7 +5033,7 @@ qualified exception class name. [[mvc-ann-rest-exceptions-client]] === Client Handling -[.small]#<># +[.small]#<># A client application can catch `WebClientResponseException`, when using the `WebClient`, or `RestClientResponseException` when using the `RestTemplate`, and use their @@ -5072,16 +5044,16 @@ or `RestClientResponseException` when using the `RestTemplate`, and use their [[mvc-web-security]] == Web Security -[.small]#<># +[.small]#<># The https://spring.io/projects/spring-security[Spring Security] project provides support for protecting web applications from malicious exploits. See the Spring Security reference documentation, including: -* {doc-spring-security}/servlet/integrations/mvc.html[Spring MVC Security] -* {doc-spring-security}/servlet/test/mockmvc/setup.html[Spring MVC Test Support] -* {doc-spring-security}/features/exploits/csrf.html#csrf-protection[CSRF protection] -* {doc-spring-security}/features/exploits/headers.html[Security Response Headers] +* {docs-spring-security}/servlet/integrations/mvc.html[Spring MVC Security] +* {docs-spring-security}/servlet/test/mockmvc/setup.html[Spring MVC Test Support] +* {docs-spring-security}/features/exploits/csrf.html#csrf-protection[CSRF protection] +* {docs-spring-security}/features/exploits/headers.html[Security Response Headers] https://hdiv.org/[HDIV] is another web security framework that integrates with Spring MVC. @@ -5090,7 +5062,7 @@ https://hdiv.org/[HDIV] is another web security framework that integrates with S [[mvc-caching]] == HTTP Caching -[.small]#<># +[.small]#<># HTTP caching can significantly improve the performance of a web application. HTTP caching revolves around the `Cache-Control` response header and, subsequently, conditional request @@ -5106,7 +5078,7 @@ This section describes the HTTP caching-related options that are available in Sp [[mvc-caching-cachecontrol]] === `CacheControl` -[.small]#<># +[.small]#<># {api-spring-framework}/http/CacheControl.html[`CacheControl`] provides support for configuring settings related to the `Cache-Control` header and is accepted as an argument @@ -5162,13 +5134,14 @@ works as follows: [[mvc-caching-etag-lastmodified]] === Controllers -[.small]#<># +[.small]#<># Controllers can add explicit support for HTTP caching. We recommended doing so, since the `lastModified` or `ETag` value for a resource needs to be calculated before it can be compared against conditional request headers. A controller can add an `ETag` header and `Cache-Control` settings to a `ResponseEntity`, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5201,6 +5174,7 @@ settings to a `ResponseEntity`, as the following example shows: .body(book) } ---- +-- The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison to the conditional request headers indicates that the content has not changed. Otherwise, the @@ -5209,6 +5183,7 @@ to the conditional request headers indicates that the content has not changed. O You can also make the check against conditional request headers in the controller, as the following example shows: +-- [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- @@ -5248,6 +5223,7 @@ as the following example shows: <1> Application-specific calculation. <2> The response has been set to 304 (NOT_MODIFIED) -- no further processing. <3> Continue with the request processing. +-- There are three variants for checking conditional requests against `eTag` values, `lastModified` @@ -5259,7 +5235,7 @@ to 412 (PRECONDITION_FAILED), to prevent concurrent modification. [[mvc-caching-static-resources]] === Static Resources -[.small]#<># +[.small]#<># You should serve static resources with a `Cache-Control` and conditional response headers for optimal performance. See the section on configuring <>. @@ -5279,7 +5255,7 @@ include::webmvc-view.adoc[leveloffset=+1] [[mvc-config]] == MVC Config -[.small]#<># +[.small]#<># The MVC Java configuration and the MVC XML namespace provide default configuration suitable for most applications and a configuration API to customize it. @@ -5295,7 +5271,7 @@ and <>. [[mvc-config-enable]] === Enable MVC Configuration -[.small]#<># +[.small]#<># In Java configuration, you can use the `@EnableWebMvc` annotation to enable MVC configuration, as the following example shows: @@ -5344,7 +5320,7 @@ available on the classpath (for example, payload converters for JSON, XML, and o [[mvc-config-customize]] === MVC Config API -[.small]#<># +[.small]#<># In Java configuration, you can implement the `WebMvcConfigurer` interface, as the following example shows: @@ -5380,7 +5356,7 @@ sub-elements are available. [[mvc-config-conversion]] === Type Conversion -[.small]#<># +[.small]#<># By default, formatters for various number and date types are installed, along with support for customization via `@NumberFormat` and `@DateTimeFormat` on fields. @@ -5495,7 +5471,7 @@ FormatterRegistrar implementations. [[mvc-config-validation]] === Validation -[.small]#<># +[.small]#<># By default, if <> is present on the classpath (for example, Hibernate Validator), the `LocalValidatorFactoryBean` is @@ -5645,7 +5621,7 @@ unwanted characters in URL paths. [[mvc-config-content-negotiation]] === Content Types -[.small]#<># +[.small]#<># You can configure how Spring MVC determines the requested media types from the request (for example, `Accept` header, URL path extension, query parameter, and others). @@ -5709,7 +5685,7 @@ The following example shows how to achieve the same configuration in XML: [[mvc-config-message-converters]] === Message Converters -[.small]#<># +[.small]#<># You can customize `HttpMessageConverter` in Java configuration by overriding {api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#configureMessageConverters-java.util.List-[`configureMessageConverters()`] @@ -5862,7 +5838,7 @@ splitting URL handling across an annotated controller and a view controller. [[mvc-config-view-resolvers]] === View Resolvers -[.small]#<># +[.small]#<># The MVC configuration simplifies the registration of view resolvers. @@ -5919,7 +5895,6 @@ The MVC namespace provides dedicated elements. The following example works with [source,xml,indent=0,subs="verbatim,quotes"] ---- - @@ -5932,7 +5907,6 @@ The MVC namespace provides dedicated elements. The following example works with - ---- In Java configuration, you can add the respective `Configurer` bean, @@ -5982,7 +5956,7 @@ as the following example shows: [[mvc-config-static-resources]] === Static Resources -[.small]#<># +[.small]#<># This option provides a convenient way to serve static resources from a list of {api-spring-framework}/core/io/Resource.html[`Resource`]-based locations. @@ -6222,7 +6196,7 @@ The following example shows how to achieve the same configuration in XML: [[mvc-config-path-matching]] === Path Matching -[.small]#<># +[.small]#<># You can customize options related to path matching and treatment of the URL. For details on the individual options, see the @@ -6282,7 +6256,7 @@ The following example shows how to customize path matching in XML configuration: [[mvc-config-advanced-java]] === Advanced Java Config -[.small]#<># +[.small]#<># `@EnableWebMvc` imports `DelegatingWebMvcConfiguration`, which: @@ -6357,7 +6331,7 @@ by letting it be detected through a `` declaration. [[mvc-http2]] == HTTP/2 -[.small]#<># +[.small]#<># Servlet 4 containers are required to support HTTP/2, and Spring Framework 5 is compatible with Servlet API 4. From a programming model perspective, there is nothing specific that diff --git a/framework-docs/src/docs/asciidoc/web/websocket-intro.adoc b/framework-docs/src/docs/asciidoc/web/websocket-intro.adoc index 913ac8b0bef5..0c2b09685859 100644 --- a/framework-docs/src/docs/asciidoc/web/websocket-intro.adoc +++ b/framework-docs/src/docs/asciidoc/web/websocket-intro.adoc @@ -1,4 +1,4 @@ -[[{chapter}.websocket-intro]] +[id={chapter}.websocket-intro] = Introduction to WebSocket The WebSocket protocol, https://tools.ietf.org/html/rfc6455[RFC 6455], provides a standardized @@ -54,7 +54,7 @@ instructions of the cloud provider related to WebSocket support. -[[{chapter}.websocket-intro-architecture]] +[id={chapter}.websocket-intro-architecture] == HTTP Versus WebSocket Even though WebSocket is designed to be HTTP-compatible and starts with an HTTP request, @@ -80,11 +80,11 @@ In the absence of that, they need to come up with their own conventions. -[[{chapter}.websocket-intro-when-to-use]] +[id={chapter}.websocket-intro-when-to-use] == When to Use WebSockets WebSockets can make a web page be dynamic and interactive. However, in many cases, -a combination of Ajax and HTTP streaming or long polling can provide a simple and +a combination of AJAX and HTTP streaming or long polling can provide a simple and effective solution. For example, news, mail, and social feeds need to update dynamically, but it may be diff --git a/framework-docs/src/docs/asciidoc/web/websocket.adoc b/framework-docs/src/docs/asciidoc/web/websocket.adoc index af5f28d34f6e..48832aa3f388 100644 --- a/framework-docs/src/docs/asciidoc/web/websocket.adoc +++ b/framework-docs/src/docs/asciidoc/web/websocket.adoc @@ -1,7 +1,6 @@ [[websocket]] = WebSockets -:doc-spring-security: {doc-root}/spring-security/reference -[.small]#<># +[.small]#<># This part of the reference documentation covers support for Servlet stack, WebSocket messaging that includes raw WebSocket interactions, WebSocket emulation through SockJS, and @@ -14,7 +13,7 @@ include::websocket-intro.adoc[leveloffset=+1] [[websocket-server]] == WebSocket API -[.small]#<># +[.small]#<># The Spring Framework provides a WebSocket API that you can use to write client- and server-side applications that handle WebSocket messages. @@ -23,7 +22,7 @@ server-side applications that handle WebSocket messages. [[websocket-server-handler]] === `WebSocketHandler` -[.small]#<># +[.small]#<># Creating a WebSocket server is as simple as implementing `WebSocketHandler` or, more likely, extending either `TextWebSocketHandler` or `BinaryWebSocketHandler`. The following @@ -109,7 +108,7 @@ sending. One option is to wrap the `WebSocketSession` with [[websocket-server-handshake]] === WebSocket Handshake -[.small]#<># +[.small]#<># The easiest way to customize the initial HTTP WebSocket handshake request is through a `HandshakeInterceptor`, which exposes methods for "`before`" and "`after`" the handshake. @@ -185,7 +184,7 @@ HTTP requests. It is also easy to integrate into other HTTP processing scenarios by invoking `WebSocketHttpRequestHandler`. This is convenient and easy to understand. However, special considerations apply with regards to JSR-356 runtimes. -The Java WebSocket API (JSR-356) provides two deployment mechanisms. The first +The Jakarta WebSocket API (JSR-356) provides two deployment mechanisms. The first involves a Servlet container classpath scan (a Servlet 3 feature) at startup. The other is a registration API to use at Servlet container initialization. Neither of these mechanism makes it possible to use a single "`front controller`" @@ -194,17 +193,9 @@ requests -- such as Spring MVC's `DispatcherServlet`. This is a significant limitation of JSR-356 that Spring's WebSocket support addresses with server-specific `RequestUpgradeStrategy` implementations even when running in a JSR-356 runtime. -Such strategies currently exist for Tomcat, Jetty, GlassFish, WebLogic, WebSphere, and -Undertow (and WildFly). - - - -NOTE: A request to overcome the preceding limitation in the Java WebSocket API has been -created and can be followed at -https://github.com/eclipse-ee4j/websocket-api/issues/211[eclipse-ee4j/websocket-api#211]. -Tomcat, Undertow, and WebSphere provide their own API alternatives that -make it possible to do this, and it is also possible with Jetty. We are hopeful -that more servers will do the same. +Such strategies currently exist for Tomcat, Jetty, GlassFish, WebLogic, WebSphere, and Undertow +(and WildFly). As of Jakarta WebSocket 2.1, a standard request upgrade strategy is available +which Spring chooses on Jakarta EE 10 based web containers such as Tomcat 10.1 and Jetty 12. A secondary consideration is that Servlet containers with JSR-356 support are expected to perform a `ServletContainerInitializer` (SCI) scan that can slow down application @@ -251,7 +242,7 @@ Java initialization API. The following example shows how to do so: [[websocket-server-runtime-configuration]] === Server Configuration -[.small]#<># +[.small]#<># Each underlying WebSocket engine exposes configuration properties that control runtime characteristics, such as the size of message buffer sizes, idle timeout, @@ -374,7 +365,7 @@ The following example shows the XML configuration equivalent of the preceding ex [[websocket-server-allowed-origins]] === Allowed Origins -[.small]#<># +[.small]#<># As of Spring Framework 4.1.5, the default behavior for WebSocket and SockJS is to accept only same-origin requests. It is also possible to allow all or a specified list of origins. @@ -627,7 +618,7 @@ response. By default, the Spring Security Java configuration sets it to `DENY`. In 3.2, the Spring Security XML namespace does not set that header by default but can be configured to do so. In the future, it may set it by default. -See {doc-spring-security}/features/exploits/headers.html#headers-default[Default Security Headers] +See {docs-spring-security}/features/exploits/headers.html#headers-default[Default Security Headers] of the Spring Security documentation for details on how to configure the setting of the `X-Frame-Options` header. You can also see https://github.com/spring-projects/spring-security/issues/2718[gh-2718] @@ -971,7 +962,7 @@ endpoints, over WebSocket with <>, as the following example @Override public void registerStompEndpoints(StompEndpointRegistry registry) { - registry.addEndpoint("/portfolio").withSockJS(); // <1> + registry.addEndpoint("/portfolio").withSockJS(); // <1> } @Override @@ -1811,7 +1802,7 @@ its own implementation of `WebSocketMessageBrokerConfigurer` that is marked with === Authorization Spring Security provides -{doc-spring-security}/servlet/integrations/websocket.html#websocket-authorization[WebSocket sub-protocol authorization] +{docs-spring-security}/servlet/integrations/websocket.html#websocket-authorization[WebSocket sub-protocol authorization] that uses a `ChannelInterceptor` to authorize messages based on the user header in them. Also, Spring Session provides https://docs.spring.io/spring-session/reference/web-socket.html[WebSocket integration] diff --git a/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/importruntimehints/SpellCheckService.java b/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/importruntimehints/SpellCheckService.java new file mode 100644 index 000000000000..88fd79becf0a --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/importruntimehints/SpellCheckService.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2022 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.docs.core.aot.hints.importruntimehints; + +import java.util.Locale; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; + +@Component +@ImportRuntimeHints(SpellCheckService.SpellCheckServiceRuntimeHints.class) +public class SpellCheckService { + + public void loadDictionary(Locale locale) { + ClassPathResource resource = new ClassPathResource("dicts/" + locale.getLanguage() + ".txt"); + //... + } + + static class SpellCheckServiceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.resources().registerPattern("dicts/*"); + } + } + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflection.java b/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflection.java new file mode 100644 index 000000000000..93abd52e85a2 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflection.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2022 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.docs.core.aot.hints.testing; + +import java.lang.reflect.Method; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.ClassUtils; + +public class SampleReflection { + + private final Log logger = LogFactory.getLog(SampleReflection.class); + + public void performReflection() { + try { + Class springVersion = ClassUtils.forName("org.springframework.core.SpringVersion", null); + Method getVersion = ClassUtils.getMethod(springVersion, "getVersion"); + String version = (String) getVersion.invoke(null); + logger.info("Spring version:" + version); + } + catch (Exception exc) { + logger.error("reflection failed", exc); + } + } + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflectionRuntimeHintsTests.java b/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflectionRuntimeHintsTests.java new file mode 100644 index 000000000000..5d666cc9283b --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflectionRuntimeHintsTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2022 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.docs.core.aot.hints.testing; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.ExecutableMode; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent; +import org.springframework.aot.test.agent.RuntimeHintsInvocations; +import org.springframework.aot.test.agent.RuntimeHintsRecorder; +import org.springframework.core.SpringVersion; + +import static org.assertj.core.api.Assertions.assertThat; + +// @EnabledIfRuntimeHintsAgent signals that the annotated test class or test +// method is only enabled if the RuntimeHintsAgent is loaded on the current JVM. +// It also tags tests with the "RuntimeHints" JUnit tag. +@EnabledIfRuntimeHintsAgent +class SampleReflectionRuntimeHintsTests { + + @Test + void shouldRegisterReflectionHints() { + RuntimeHints runtimeHints = new RuntimeHints(); + // Call a RuntimeHintsRegistrar that contributes hints like: + runtimeHints.reflection().registerType(SpringVersion.class, typeHint -> + typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE)); + + // Invoke the relevant piece of code we want to test within a recording lambda + RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> { + SampleReflection sample = new SampleReflection(); + sample.performReflection(); + }); + // assert that the recorded invocations are covered by the contributed hints + assertThat(invocations).match(runtimeHints); + } + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SpellCheckServiceTests.java b/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SpellCheckServiceTests.java new file mode 100644 index 000000000000..6151f9cd3782 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SpellCheckServiceTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2022 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.docs.core.aot.hints.testing; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SpellCheckServiceTests { + + // tag::hintspredicates[] + @Test + void shouldRegisterResourceHints() { + RuntimeHints hints = new RuntimeHints(); + new SpellCheckServiceRuntimeHints().registerHints(hints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.resource().forResource("dicts/en.txt")) + .accepts(hints); + } + // end::hintspredicates[] + + // Copied here because it is package private in SpellCheckService + static class SpellCheckServiceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.resources().registerPattern("dicts/*"); + } + } + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/core/aot/refresh/AotProcessingSample.java b/framework-docs/src/main/java/org/springframework/docs/core/aot/refresh/AotProcessingSample.java new file mode 100644 index 000000000000..d5a0e3e86ddc --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/core/aot/refresh/AotProcessingSample.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2022 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.docs.core.aot.refresh; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +public class AotProcessingSample { + + public void createAotContext() { + // tag::aotcontext[] + RuntimeHints hints = new RuntimeHints(); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(MyApplication.class); + context.refreshForAotProcessing(hints); + // end::aotcontext[] + } + + // tag::myapplication[] + @Configuration(proxyBeanMethods=false) + @ComponentScan + @Import({DataSourceConfiguration.class, ContainerConfiguration.class}) + public class MyApplication { + } + // end::myapplication[] + + class DataSourceConfiguration { + } + + class ContainerConfiguration { + } + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/CustomServerRequestObservationConvention.java b/framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/CustomServerRequestObservationConvention.java new file mode 100644 index 000000000000..dc76cbc8395f --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/CustomServerRequestObservationConvention.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2022 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.docs.integration.observability.config.conventions; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; + +import org.springframework.http.server.observation.ServerHttpObservationDocumentation; +import org.springframework.http.server.observation.ServerRequestObservationContext; +import org.springframework.http.server.observation.ServerRequestObservationConvention; + +public class CustomServerRequestObservationConvention implements ServerRequestObservationConvention { + + @Override + public String getName() { + // will be used as the metric name + return "http.server.requests"; + } + + @Override + public String getContextualName(ServerRequestObservationContext context) { + // will be used for the trace name + return "http " + context.getCarrier().getMethod().toLowerCase(); + } + + @Override + public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) { + return KeyValues.of(method(context), status(context), exception(context)); + } + + + @Override + public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) { + return KeyValues.of(httpUrl(context)); + } + + private KeyValue method(ServerRequestObservationContext context) { + // You should reuse as much as possible the corresponding ObservationDocumentation for key names + return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod()); + } + + // @fold:on // status(), exception(), httpUrl()... + private KeyValue status(ServerRequestObservationContext context) { + return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, String.valueOf(context.getResponse().getStatus())); + } + + private KeyValue exception(ServerRequestObservationContext context) { + String exception = (context.getError() != null) ? context.getError().getClass().getSimpleName() : KeyValue.NONE_VALUE; + return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, exception); + } + + private KeyValue httpUrl(ServerRequestObservationContext context) { + return KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().getRequestURI()); + } + // @fold:off + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/ExtendedServerRequestObservationConvention.java b/framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/ExtendedServerRequestObservationConvention.java new file mode 100644 index 000000000000..48049a2c7552 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/ExtendedServerRequestObservationConvention.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2022 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.docs.integration.observability.config.conventions; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; + +import org.springframework.http.server.observation.DefaultServerRequestObservationConvention; +import org.springframework.http.server.observation.ServerRequestObservationContext; + +public class ExtendedServerRequestObservationConvention extends DefaultServerRequestObservationConvention { + + @Override + public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) { + // here, we just want to have an additional KeyValue to the observation, keeping the default values + return super.getLowCardinalityKeyValues(context).and(custom(context)); + } + + private KeyValue custom(ServerRequestObservationContext context) { + return KeyValue.of("custom.method", context.getCarrier().getMethod()); + } + +} diff --git a/framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/ServerRequestObservationFilter.java b/framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/ServerRequestObservationFilter.java new file mode 100644 index 000000000000..fabad6d79f90 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/integration/observability/config/conventions/ServerRequestObservationFilter.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2022 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.docs.integration.observability.config.conventions; + + +import io.micrometer.common.KeyValue; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationFilter; + +import org.springframework.http.server.observation.ServerRequestObservationContext; + +public class ServerRequestObservationFilter implements ObservationFilter { + + @Override + public Observation.Context map(Observation.Context context) { + if (context instanceof ServerRequestObservationContext serverContext) { + context.setName("custom.observation.name"); + context.addLowCardinalityKeyValue(KeyValue.of("project", "spring")); + String customAttribute = (String) serverContext.getCarrier().getAttribute("customAttribute"); + context.addLowCardinalityKeyValue(KeyValue.of("custom.attribute", customAttribute)); + } + return context; + } +} diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 250c7cee142a..02404e481d5e 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -7,29 +7,29 @@ javaPlatform { } dependencies { - api(platform("com.fasterxml.jackson:jackson-bom:2.14.0")) - api(platform("io.micrometer:micrometer-bom:1.10.0-RC1")) - api(platform("io.netty:netty-bom:4.1.84.Final")) + api(platform("com.fasterxml.jackson:jackson-bom:2.14.1")) + api(platform("io.micrometer:micrometer-bom:1.10.2")) + api(platform("io.netty:netty-bom:4.1.85.Final")) api(platform("io.netty:netty5-bom:5.0.0.Alpha5")) - api(platform("io.projectreactor:reactor-bom:2022.0.0-RC1")) + api(platform("io.projectreactor:reactor-bom:2022.0.1")) api(platform("io.rsocket:rsocket-bom:1.1.3")) - api(platform("org.apache.groovy:groovy-bom:4.0.5")) + api(platform("org.apache.groovy:groovy-bom:4.0.6")) api(platform("org.apache.logging.log4j:log4j-bom:2.19.0")) api(platform("org.eclipse.jetty:jetty-bom:11.0.12")) api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4")) api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.4.0")) api(platform("org.junit:junit-bom:5.9.1")) - api(platform("org.mockito:mockito-bom:4.8.0")) + api(platform("org.mockito:mockito-bom:4.9.0")) constraints { - api("com.fasterxml.woodstox:woodstox-core:6.3.1") api("com.fasterxml:aalto-xml:1.3.1") - api("com.github.ben-manes.caffeine:caffeine:3.1.1") - api("com.github.librepdf:openpdf:1.3.29") + api("com.fasterxml.woodstox:woodstox-core:6.4.0") + api("com.github.ben-manes.caffeine:caffeine:3.1.2") + api("com.github.librepdf:openpdf:1.3.30") api("com.google.code.findbugs:findbugs:3.0.1") api("com.google.code.findbugs:jsr305:3.0.2") - api("com.google.code.gson:gson:2.9.1") - api("com.google.protobuf:protobuf-java-util:3.21.5") + api("com.google.code.gson:gson:2.10") + api("com.google.protobuf:protobuf-java-util:3.21.11") api("com.googlecode.protobuf-java-format:protobuf-java-format:1.4") api("com.h2database:h2:2.1.214") api("com.jayway.jsonpath:json-path:2.7.0") @@ -45,15 +45,16 @@ dependencies { api("com.thoughtworks.xstream:xstream:1.4.19") api("commons-io:commons-io:2.11.0") api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1") - api("info.picocli:picocli:4.6.3") - api("io.micrometer:context-propagation:1.0.0-RC1") + api("info.picocli:picocli:4.7.0") + api("io.micrometer:context-propagation:1.0.0") api("io.mockk:mockk:1.12.1") + api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3") api("io.projectreactor.tools:blockhound:1.0.6.RELEASE") - api("io.r2dbc:r2dbc-h2:1.0.0.RC1") + api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE") api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE") api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE") api("io.reactivex.rxjava3:rxjava:3.1.5") - api("io.smallrye.reactive:mutiny:1.7.0") + api("io.smallrye.reactive:mutiny:1.8.0") api("io.undertow:undertow-core:2.3.0.Final") api("io.undertow:undertow-servlet:2.3.0.Final") api("io.undertow:undertow-websockets-jsr:2.3.0.Final") @@ -86,7 +87,7 @@ dependencies { api("jaxen:jaxen:1.2.0") api("junit:junit:4.13.2") api("net.sf.jopt-simple:jopt-simple:5.0.4") - api("net.sourceforge.htmlunit:htmlunit:2.65.1") + api("net.sourceforge.htmlunit:htmlunit:2.67.0") api("org.apache-extras.beanshell:bsh:2.0b6") api("org.apache.activemq:activemq-broker:5.16.2") api("org.apache.activemq:activemq-kahadb-store:5.16.2") @@ -95,9 +96,9 @@ dependencies { api("org.apache.derby:derby:10.16.1.1") api("org.apache.derby:derbyclient:10.16.1.1") api("org.apache.derby:derbytools:10.16.1.1") - api("org.apache.httpcomponents.client5:httpclient5:5.1.3") - api("org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3") - api("org.apache.poi:poi-ooxml:5.2.2") + api("org.apache.httpcomponents.client5:httpclient5:5.2") + api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2") + api("org.apache.poi:poi-ooxml:5.2.3") api("org.apache.tomcat.embed:tomcat-embed-core:10.1.1") api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.1") api("org.apache.tomcat:tomcat-util:10.1.1") @@ -110,7 +111,7 @@ dependencies { api("org.bouncycastle:bcpkix-jdk18on:1.71") api("org.codehaus.jettison:jettison:1.3.8") api("org.dom4j:dom4j:2.1.3") - api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.6") + api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.7") api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.3") api("org.eclipse:yasson:2.0.4") api("org.ehcache:ehcache:3.4.0") @@ -123,25 +124,25 @@ dependencies { api("org.glassfish:jakarta.el:4.0.2") api("org.graalvm.sdk:graal-sdk:22.3.0") api("org.hamcrest:hamcrest:2.2") - api("org.hibernate:hibernate-core-jakarta:5.6.12.Final") + api("org.hibernate:hibernate-core-jakarta:5.6.14.Final") api("org.hibernate:hibernate-validator:7.0.5.Final") - api("org.hsqldb:hsqldb:2.7.0") + api("org.hsqldb:hsqldb:2.7.1") api("org.javamoney:moneta:1.4.2") - api("org.jruby:jruby:9.3.8.0") + api("org.jruby:jruby:9.4.0.0") api("org.junit.support:testng-engine:1.0.4") api("org.mozilla:rhino:1.7.11") api("org.ogce:xpp3:1.1.6") api("org.python:jython-standalone:2.7.1") api("org.quartz-scheduler:quartz:2.3.2") - api("org.seleniumhq.selenium:htmlunit-driver:2.65.0") + api("org.seleniumhq.selenium:htmlunit-driver:2.66.0") api("org.seleniumhq.selenium:selenium-java:3.141.59") api("org.skyscreamer:jsonassert:1.5.0") - api("org.slf4j:slf4j-api:2.0.2") + api("org.slf4j:slf4j-api:2.0.5") api("org.testng:testng:7.6.1") api("org.webjars:underscorejs:1.8.3") - api("org.webjars:webjars-locator-core:0.48") + api("org.webjars:webjars-locator-core:0.52") api("org.xmlunit:xmlunit-assertj:2.9.0") api("org.xmlunit:xmlunit-matchers:2.9.0") - api("org.yaml:snakeyaml:1.30") + api("org.yaml:snakeyaml:1.33") } } diff --git a/gradle.properties b/gradle.properties index 28d0adf1073d..2635730a26db 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -version=6.0.0-SNAPSHOT +version=6.0.4-SNAPSHOT org.gradle.caching=true org.gradle.jvmargs=-Xmx2048m org.gradle.parallel=true -kotlinVersion=1.7.20 +kotlinVersion=1.7.21 kotlin.stdlib.default.dependency=false diff --git a/gradle/spring-module.gradle b/gradle/spring-module.gradle index 99ae6762e206..64efe91661e7 100644 --- a/gradle/spring-module.gradle +++ b/gradle/spring-module.gradle @@ -8,8 +8,8 @@ apply plugin: 'me.champeau.jmh' apply from: "$rootDir/gradle/publications.gradle" dependencies { - jmh 'org.openjdk.jmh:jmh-core:1.32' - jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.32' + jmh 'org.openjdk.jmh:jmh-core:1.36' + jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.36' jmh 'net.sf.jopt-simple:jopt-simple' } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a4d4fb3f96a785543079b8df6723c946b..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36987 zcmaI7V{oQH*DaihZQHh;iEZ1qlL_wFwrx9iY}=lAVmp~6XP)<~uj)LfPMv>O^|iZy ztzLWgT6@0L%Mke=l%(I{A1%VOoaE&Om>6y_=vN2gUFd_< z`I49N?Bm%~A$xw!r1{R)ZEe!vOQUafT$v|Di? z@6~Mff!Wcm&giJ>4E38a-ShQMLFjksfkL-#Xul77x8}fyTFt*bZ&h9SH`}sN~U_x_}#Pldr> zv8PI_b7zggb-?EDtAWaYG&Te)NF^l1gw$7Xfa2Q-YdBa8OPHKtm_`rt1=~xTUSIjj z+go^${hAi!SRJv)2O8b=zR63PD~Tk*_Yvpua(%(S=~K{G?%DT~*d^Cr$1(C^Vm}Q~ zVLy^I#0UPTJ$oXhmg-9M7r#Aph|D-2@5k0J(p&-_!6)sMYQ$%^=aYgdxB?0>3_jC| zj2_tn`fWF<{xt_gWgU6)H1_9mv@wKgLm@)0lB7QcghC~{EFE*8e$P_$6b+0fIztRY zX@clnI-~S{Zp#fiojF&=p6!b96xJyKrUAo1@qMyVO1?#R+l;^G0&x(_^e1#~vIUzX z5t$4=rq03TE5&IOqI?!5vLi$C@RLRfot(xi zT;}ESD9NN7S~G}$ahl^rg7GMO!*7<4kBhQMUSS`ekSr#$rASIXZmOZ^c8<3KnC!<6 z7?zx@%cm}gQ?EGDTAE265Rqif)4jz>4)BxeDB;fdP2tPzlV5GSZ;`M}Cd5jF6o$i= z(ir7Yt+E1Z1c*{wzDQi@ak!pH0#gml1PC@))5D>OL4J3a&DwmI=`zji_dOfq#D!aerL|9DXaM+a9 z3J=wmi&H@KNW+@__HM|Cst)tVUv@%Yv*nIv!;L$H&t=xdv3V8r|M`st@ccn}rN@gP zD!i<6pLa@){asX!DBU zKSQ6TFzX<|F-UClir`U2H74RDBWDOHgOqA`=E{7#xe1C1pd_gSY=<>XrQ zo)%o|1RP5LU=XUb%9ri1?%a@R`&N#i4#_BwWR=i)73-j+730ZX;*dkNjs2-E7^xJJ z?^dLOQbk!6QWo)+Re{M7Rk0$L3r$^QfCe`#Lb(QiEY>bZC1uD9upUE|xK_G1EQuUZ zf!l?lt&gN2rEaL!SEQ8ZV>g>02S3EYO%dmo0fZ`KXi#4yBbUpahL}@|1mj1HJ*A-7 z=w;h%t0koLjMcM2+RM{pOqBqSqqGVmQx8DJL)aT(*P5@U^{%qC7$z|m3L-g77?xCP zRK-!J*rFA@<3}wvc|z_ z)}Ccor@8(juC*77A>*i+(@IWT?p)@iXS=H7R}BSuD$0}1q%cjJm>h`XSwEw?RWHO# ze%5l;23sUNkFQHDRt`QHNnlcsG4y4oX!Pviphr`2r4EuLbAu3c-vsk< z;C#bU$lgd8pOG-yfeZ*V%bPu8RhDIH#rjRP8vdP*7pnPjFOph2+3M;Z1kk+7SXe=GNJ6X$r^i{PG@!RjmyWWCh++^w!GUYDO-Tsk_}N z7#EvAR@ZKhSpYIJv1>%VZVkG^v{B8Cb|fy+aV#m7e|MEFS!EXoM{XK-Iu@;{PL^Y< z&{^c$(~NGga46)V4!Ots4s>8~34X}{74nmIlga_Srd*WeQrC6aT`*l>6ivlW{bK8C z_DeYI;u-e_-Q>I4pJZt~luT`Lo@TE_!DL|%2`mbwPuv78%tX7njeJ>kl%QM6B9?n? zK3?AuP_ddvn7`&_GPF1*zJpmD;U4Stu7ut785kOLi|nmnpSp`yg~@RS$}? zG?oU;l^b%ymH#O!A9Wj3V0x{2Am`#)n?XocB&5yzBn#1exuW%omymlf`<0?uce^4V z-T-^gBo%-pd@0EUj_AaNq`qyK+P((7nc7-&BAVG+8=P|#qyQ3v3TH00Uj4<+ z5z&n>JHUh=z=*ufAk%eNu=G9nw*3vO5&8AV>_)hDBQ6Ka*Xuz-{-~Zf&HS5Rh>Bya z3R*<_OV`)}`jO!U54MC90^^duSyBMXzsVt4#A>RY$S87**y9EUnI*7kz+i@*2+${E z?#p~)NP2Myd@(7;uP`SS2hB_Zr$-K`Uj6Otmg~yBMjUVjjFDalRrn=)-WF#JHdPxIifOd4 z(tMQ0raUN@I+cO1|ESG{CUX9J`gSGZ8pn&$^Qol!$6V3#PRltYB{&pT@`8XL;`iFX zTDj2&T7{aEX@z8=lDc4NGb9rC21tz^;=k1II07nZ+Hp3q2V40JUYDZiKtBcd4m~p3 zkm6gm)3G?AplO9OtP-`)CqQSRt0DJ9PI_b@s(iSviBG^5ukW6gYqT#_gY_3nNfr$J zUlj=r4FUop46-%K=*;x*i!HgtO8|d4kaa2=6%JM<+AW$5HCja#7$x%{!|JMP-vN?< z+YIGBhXQ{3YTcK-8KuOj%iX}BR7Lz7g-(PiB?wwe>Bq4SHFVNmU#b3u$OgrhxGzNh zpk}{Vu#Cyy^1I9!=UIoqRh4ApXf(i2qBL@LQVm7X`Vh)t^5KOOaiMExc&BZwED{*} zA$%lm339JHrJxW={CJ*GY?~QP8^QId`NZW|J9^vk%p6qNljZf0-c}0R%#tda=%z%? z7;x?QiYyyJvy5{W&hM>3RLiJK)SYVhJQ#suW_Fl?!P(VLlbZ1ho+R+3Upj!<+Q~55 zXNW?{d2=B5^P*ae^vZbl6yF7e6y$D98O^Ae!t4n~6Rz74Ha|@G!DCrGgCa2NUJ4u6 z&3+>VfvwfPs&kZOVBW6YUbBQ9=0aT4Mbw{R%%v$UmLWT=${g)D$-(lE`TFnx1D>|C zv$@yfvD;Lh6h>$o?YP3na~mKQI-$FS>*Uz}Le+`ic%46;-YJg5!940hz8?F)e z!!=G=XVo*Ng|#y3(VC(848`+U6a>rnwm9>!5-B<3AmiB>vKjtLL34=tQtGIqt@5mE z6XtDRL;83~T@P*e4^1Kg!L)jSV{J)RCs*VCZBL2G+!}xpx?rDv7FYSlL`}VDPzGFWR(r(k zl>QpK@(F>$o-mIA)0tjnmlo#gO1kF{{$wNYOij1jRsE^QX2G9(*HQW_4^q#{>HETj z)KXZS?{hx;bZzdh{{o=S>Nrf+jcHyn(POE_bLkQ;RA>+bR`Pk@U(p9k$I1?!mopld z6N*W;DAlaCgv>{85Tjp5d6xud$o<};xVIQ9B>d09JQPrH0PQUX7pu3>gXEnc5bU;< z+4@|>j_An;Dq$6IPajUw>LQwu7WbLHDM;dHK%+Q&Get{-B{ZN3BU)zM!$r&-y?tI7 zefXTSRuA0?TzH!#M|LARtH-EDEGkKVP9gYfhX-S@4G~{Ul(w@wh+k;N%C9MnVgtV*SUz%z`{Ak zM=zt8=PdCHL=`w#l*wQ}IX!_YZy63NM!msFk&a8q471j~*-VwRfxCV60q-gqBc6x5^BTZ1kHmcm zB@Pg6?8W}uuVy+y@39Jej%MiI!fz%m{w+&3t(c;IaECQLZc)^95pc|o-PFG3rz_}t z$d{*do`l?{=jL5(oNRLyiyw(YP7+@9L381o+h^FU>C5<8mRRW6@|e|koHivsqjOhE zX7gZL4G+U;OWV;V9!97rh791f!2Xr(!bZ#Rt~O)?^0YP+3J*-3P9j%e1+p}nB1>v&2#ANy$m^R`*%_4_i^#f-V$rbPn&lc{8@a}u4 zm}*>dCGpZ#FOowv6s{2aMTASa8UCH+psV-p>)raxb1J=idPm+TAFCh+R3P2@m*^Ra zl7P4h7W;~&*%`@|pf&CcPV&`HwrInIbxQRi6x?`XVZQw0=$?Q915(MhuQI-SZbXXOjwFPu%Xfp)hYS} zT>NO5ceDTDN}?ofDYYmi82v!w zTyjJ)bA+JbN&rVN)-1!uSp^$DPF@;|1>KAt|FT<*3nIf!k(WKT=g2+jkE-<3jpYIU z3efXbEz@>d)KcN{(HAtdVN zBJVQzEd-c!|9S{GbO$vA7* zsLOTYr3tz3oT)s4u3i7l=1rmRw=*mdS1b+HSW6T z8Q8HZr7jXtz$ow742XmCcA7I3(Ij?1q@;obb~e6uoDclx^O}SJ?+|lZwf3>vhKeWc zFPUoW%2u7$sw_U9q2-%O4gL0}k{+{+u%2lr+eO_^cLd4qrK0rQO_PLG8$RA49FlcA zHQ7#gLk4vz)Y%pG)}~UOuywA`q<|^rmMWnt?RWVhK-E^LM5T4IaEEDDXRC(tg?sMu zVjgj^K7w+I@Rd?498Yc|GyL*&P_2%~SET*2TwFX3(lTj=8XYxWKyyhh)B#3)b}y`v?0iwfZ~Ha-YX9v)^aG><)l3 z@OT31B?d&PH8xoW^^!|$k3hz!+q`l;Lxio0k_zmI!FkGpDvee9u;^Om9XW6Jc6GN1 zfRQpW_6@`UC)6E|o$1S#Lrr(!;*w5-&oTQWFDmUxN|t)6mG))O!~UHdLCSR@qi1NJ zP`9-0H=I}c$9Ht+uyhTnNY4^-s~$Z%>PWVR|Em}S)X-K-m%NYAj12u3nQx<)3DVb% z_013;dmg5x9igAy58<@YE^@pww#6}Oz(!bek&X;&7?M+?^%IlR<3i1~DD5bk9g<&m zBhj8u;McIM6Oq3tFY2h9=8o8p~)M$v_?1ltv|ko@arfhcLlUO_o4uKoGr# zYRf%|lu#u$s+lV~SHdtmM=1@J)b8%MixhrfGYN8F^Ni9%3Ejdp!SyG`w{%XGU6PxY9WYN zemCR-gryT!QU2^6*+lr9^_NHz!8gQzv&60aEvhUi2*?dM2#Cc0u*Byf1)x+_UlC0h zU7-0>t3tODqN)g*RHo0YkZH8VdYO_^{#;UJ@S}y`e6MM1+947!@;#4b$b2{Odg(}d zn!6*9fLR-fl*{LOvh8}qll$p^cT5+6YlD-qK5Hb*M8m&4MTW-5tIw{?sm!8mF2z+s z7fdNyq{V9{)z%$oq;)Q(3Fs!we+=Q>69{L0i(5OHCDByLKQv?YqVfxi#e5OpdJ4Um z`k5EyP*B2W=S@Xc=e0)zS$)+h(u#lm5d>@C#?R3b9)*N&{6b)j_8ig$w)4cG*{ihW zN__!uA;iCc%{Ma3B6Qp~v{Ohxa?zZrl5NwiOf2AOc#-)-uHLr94nQ0qhmE~r*7f72 z4=^Ixcq+T|`!P;jsAA4S#vUzR^j5F(!~LrJ&N$xq!*CuxTA#JfQ+$;F83wTELA&)RV zrWJ?Reb_P4irbwC1gsHu=Am{94V_~+O7ta+&}13A5(;z}FJeikKh97XTjigcEliY+ zQfSL zL3;$Ue+0$|+l8Str4>(RsNZNPL-QRwCwoB780}*^pv~#9n=J6qr}-#+-VA@{&+7-7 zwCTNtsipc`N-2JklH#>a1>$SPOXsPun?S9vAfl7@yRD*M8wX#bt;65FG-8ZG0a0ch z6Lu)ho5H$q^K@Tf{u^?-#XeX|$=(^}fQlCJT1+}d_=yC>5;k{>#h{N~rizGF1SN1~ zH6`5|U~VxX7ylPV-r?@ve#OhI+#*F_i|_rEkK=XM$9t0D_uD-l$jqyn1cO7mayTFP zHcc@$o-9n!T~lN_HxrD3o5T)1365|+xacUUU7~VWt*?yuydfkSCKvjZ`x3|>bknbn7p^#44*lj?_Smq-P zjG~N}%+E$hy&={v{VnEX)I5^$P8j5OJ1+Sh2U+X5Vm?rLg0x&anN1ziQmzqI3DxYC z-TKT(#G&Q-H9N_6EX9&OJ>pAQ0J4@FtV(`Z!_>iHKR~b&c z4m`3Iea!{9uZFvlZ0W}2eH_DP!D@;}teR^0KG02b)1F*@Mt*D9>n`OY^~+O+Em=Nr zhhf^G)EL(xy1#c5=T~h*IV_)r#pv1-bjW56xV9%`v0Lc}*V(iDW*NFLfR?ugn0CHk z7u*MCG=9Z4uAXWuZ#(|jnsxLk3rClbpTbY2Yf+sm_i|B2=j3i*=W}6!yBU#oteH5a zV1!9B+U{Wk7UZakizWB5q`T6=OcDaDM%-uxc*>wq0w?aTnoBon4lqG96R9 zGPEnUuR)X+!F%mb`~E2bC@QoB*CgELgq%=x6W>033!T84GCkZkS#7Bpq}?q}Pq`Rq zI1wlWgYk54$!s})>I8%7W(F^fpB3!6)Et?I_ix{wJG9!{^QChe_EhYd=oJx1NkGVJ zRT<%AVbG6>!`2Py1g=l4Opp&$**gnFoZs(tl8C=l?NY2{Q7FU$vKrhZIT$qETWdS3 zGHocm@hUlDsct&ubsxE{pHU4go;+y1AiBUc+On#C3+*|~B~^-M6(g>%79`H2om)(4 z98#g|Q17cl)EjFFLv3Po$F;)#?$?2Fgw<1<-^vX;RAPL46QP8vH8L>ZzW9sjeAT2N zsSM$0+8!bR`+PtEfVeS95AyR;9Pp15leOeM##J-bUX9}|*?MouBYm)x-&xh0Dho6O7C_jPEo}as6-G#3Wgh7?EdKJb&XaBe6q?!yFE~xG5&t>P7MbQR z&6aMTOI}eB0NhUn^y`qagz}PwSqMYKMy#q$;!Y~S;8rH>*BrbHnCrZGz}jVaXwZhb{^6jw3*O6?X_jjrgZ1!*r+Ll&6`H&q)jCMtDt*tYbJ44sqiu%6P#nZv?)W2 zsJy<_msgJgy&%<1jg#!@Ff7s78~AlOVmTA`Cd5zHh<#L2C1>`QtEnGqlN-XXIPR1pBXg55b@l+>bEHm z9=LA56`E(atPz9GBWJ~d@WwjUzNkmAL6-$YLKH0kP00~ubn*B?;0v_~8Fl2S1ajPJ z{Ld)P7-H01#r{Py!gx#_ED_LQU1}7^0=@27ZxgPnVZt1$XOl=TC{5H^*nGCS!Ic0{ z6Zue26aDCJG+W)vT&-Q?o%a2#pIrjvp^cqI#R-OEL8jCfwMrs}rW%gUkFFtIef^ik z+=p9$b?QmBHCLDVGd)y1QE`-2wBnBNNYh43aSU%T6CrZv0Cu4Wo4X%6!z3-y@%(VK zerMWnoei*SNenL`Pq;sQ^cmYxmITd~Xcg>2lV;Md`6c=W+mN z@-gzRN!=?V%bkGu6Vx`1|8T-94ByBcHfG;E-5HlJMcg6O9iKlc!0Rh2Nzp^)w}(nj z^c{wGT{LUz!-Ln}5GH@TJ5X>u2m*Rc;Wqgq42?R~>2SA#_s0ldjxHi?OLmZxJ!M&n zT|#l=d)QlHF|uSCxLtbr&*=D6c^(5CE+}!bVk&A}oQvS1MCWKtcHi@nTJmCOJpSJH z!U0!NY!>c{@(+v_L+pb-TKtwpPp)RBc%>vhso-w}=UX?aFQorYZPfxl4od!2Q;(4U z|C?(*p8%k*xMYMr_HBu`vxWCU+sgiZw#K1rI2;HncR-1lN zSFvH?z0@{2rBF;_R%;{8_J}70s(nE1$zc8V0`u@@020a}VzN=`EC@E~RJyUwyt8I9 z^e1^q-BNYkcCa;tkbv^9CuTX{%2g8T%Mjx8%Z0N+^U{X_n7ki z$_wBin0iZOb*j2|%0V{NT|^J)p1PZu9pW!Z__N0Ir(3}D>Sqj@CVmGIt*cQl65sJ} zf$0GdZOOJw{xps{0YfcWleF8m@<`8@OvE~G7cmT;}cN3=Tv4O2Tr&iLl?aKZaRRW!?2J8t$d?KEU5SlPdP;fc_l*ut$Q)>wc@ zM~1x77vU{?{MwNPCqgVxL`Ugi@7X&ZutzKaac^|*;t^xZO}JA&s2(G`-TpgSPLf-i z3BBQ{6?iWeMTUmaEQ>exdk4dq8(fydamLUJGzZZsN|dYbL!V!#OF5I&!WxKWNEitt zT;5+c((GAdFbS0BRv_v*ruABlkMmsivszb#GAP0#UKU-J-Uv?-^*#y`PR*y< zy7}OdsDkzf?vu?S%~vXwn_$k?tvKk)yhiB|?%~mMX&qBK8cMDJd>EOGqURHBmORgs zh*-Tk6NiK&PwrcsBR0WZb<)7le)^@J%v1ej`L8yUB#Lf7_@Q~RI}E^#D}uwCD}|z# zhoAL5k7!18ryP(@ioy93VN8%Xf=K$=pQ&>%CcbP#G5dVgwD(F=ijIdtnPZLKx};NK zPD-2rhTJ`8G$#(=pR?$UHbnc!eS0t{N}NDe%OV4A+Uz*gGKxbMXi%wsHv}Ktv#oN) zIrMnP{c<6Wu*@evA>7Ob7|dgp`;@g;-!{ia%6oXU^NA}?^O-+REEp)SyVJQEz*D?s zb!?gLlhf$Pu9D5govl`1a$j=w?i|T9-InEP)crpGB5Vh6Ug+CUo!}yj(vUrNET4(u z4i@A%5@)8MDdsVw;}-p3&LOFmieRplChLN;XsCzAQSE{T+|LEgs^pj#G_sJdbBB$m z7h&fXKJm~0mX1YsHt27d>y~O06OXyXq9#IoBSnXr^0*a4^d<#H$f8>UV^H!fq5SOC z23}*Bm7f3$lf5MOh?N2r*^5aill z5##=!ckX|J@c*DBe^fAoA^YJpGgb!uK;WULx~%+nZX3jZyq5onX8#F0slo(Yr5;+@ zq9BWl(=QS-NTL9OtZGX_o*%t&$piK8A5o z5FjAoBqi>4uHHuMKXtrc&(zaf7W-ym6wwdki(d14!+&<`v<@+A=H-_@%6tVaoo)hq z|J;D9f0UA?F>ePllc~V#iH!cl3>M+%Oppl6NSA@cY#3*D!F+j(J6yf&??GxH;nS{gpEzMkk-+N$(RK`A_NiAYU7!WoXTZ~M`SL2 zD9s!QuII@SBw5q;t5wj)38wvwvc{(T_M$@|1Hwwlrx>fCg`xu%t?{l{3tIxkAE1`) z{(?k0Vt+u`A0kT|KPTodID>rhNyIb0E9zgW_{+J-K+~7W5=y|e&m8jlaZo4UaJ-wE z9O$>eXt_o81HC~^Uw~bhD(~Pb-JvNcxw|%0^(y-6#Mw(DqSQW?izG`k8sm3A+2vZG ziuT*^Bj#N)#OS$_hY94|nTr+XSchmV&`@=R4JJV)j{VVfo&@v)75EAjDc}B&VkG2S z**P`2u~rpOI)zCqqTUjuRaiQ%@)MedB;lWkQhTH` zLo3$&rZn|!)>Wq0IV^nepXR#pySbS5e|!ES3lOh4l`@tHXT(B)KxpPwo1Qo>4D;@g zUtMk}DEwzcwCnS28!5q#5J0w`UunY+xo@@RwIKmK8NNH#-Kp7BUa|%^PA8=x_E_D1?P=t+89BQxM7@Cix1;$vj)#D9Ze|**g09KJ({eBh ze{NjyA)|aJHXD-$GaY9&^FNtsc+bZ=1*kM?(T6QmFPmhXe=E*YIMcUdTuaV{Ic%Es zv1t`}mIoUr7*xVChL&1IkS5cUWoHOL0VEN}{*iR%k+j)3mkCInaSDC%y&DoBOvKx$ z+6_|N4@}+p1Lir zn;9B6c&)JMvd`{Zb61CGj+a@=<`>K?+`xn7_E{yx(U_U>Z!k1TqxoS^_F~L)Vi zcbuZcBbQ2k_I>1;^PctI+6DN3fjR}G#j;m%vQ}8!4ND*>GF)m^ps_LuoQc;%SN=K- zG4cp1l-0WWwJ6Yy{i6RQ{OC6eNa-B-`AQ|?&6`I)b2<$N(_vaDqWMIM;>`MOAfxH- zixS4zXXg&a;UXae@3)5YnzsZqYDyB`DXOBGP3wpTYkF6D<5E&o9G{3KHK^0$!zc(d zhUIefNP0Y>+~q7Y{%fCtoMKt3I%fby1C(dPqEMKc@{41q+%;?3y2~pEfa9>50C!|e z%rw%Q$u+m=1AByiREw{(PI0-6^}z3VQOqeQM7I0|CEwsP5Q+=D;rBbgV9Q9$qeOz! z4pIjYa6aqG!_DwNE44HzuIpNG5?<|k#J!(f6O-c8_j!o8-#M*iQAiH3#fYw}4tq9Fl{ zrgp}zuDROYMrtb^-+mL*+Y>VoBE&xR@L=pt#^eqzXydX5-9g7L+2} z6+!NmBdfJR?liS!Z8i`b0m|pL7b>>ZZGyGE8irdhzOtIN_88jleE+mai=^ntPt$9j zmz*2l6J5XwpQnM~*P}5A+i@j+%OODV{Lb>}H9GE>Z^6DOfrD?sVg0Mr$?Y!tU;QB= zmpe+q)xtwG0v_(7eN}=XXLhVHCw{CCry!(2$|BQnGj9srF=}V)gH;v{euIVOE=>U! z^w7FuS(hG@ibUgc7QNV*TNy(0#6*LMHM5jB>(>CjDJywcH}nIr`WRz6(-nYej?TVn zyefLID#q^JIg9Xwb!~P=^bl(#68_q7eX)wdl37#S2CH~-WtQ9$i>AVwGQ|>xc_F1Z zFXkewN=>oOjG9a&WhrkOZJ6T(d40+PtxBB*Z8xjvl}nhWMb)#M{%n$Vm1gC{Mu!$n za}TRzGVMxkwMXtr>YL2tzqVuTir-k)Dz&Bz-cu&{mWpZfa5BxUtP07c2HIt6e3E14 zE_LVsf^p3Y9^5;Ard_Dexf^H;8=sq0NxdLXOO4JIKO@4>uZ|p8XjK?hSZ8e{{D6KV(E~ z4=2+ddOn)`$!;NWaTo}!oS@jg3re2mfR^Beug5@NhBReyu%FYA)UBmCSJ^@3Dt@+- zOLh-hSRLmXu%b8E-H__wgc_VNYgo676r1rs%&JkuDfneeY-4fRC7h7W;zYwG*Pdpy z9FuWV~HvLctO?RNyBpy;lT z=t~olEmqiq5tK|+BDIBq-OW;S=%w-S&G{oh4Ax?B26s%6Ev!bZS{3k^X|RU|VZiL9 zK@F8LTy8@g@vtJpinpyowr9@3xWc5EOKKnDd>u?zRMPSmtpc_djp*mGS*^w9x{bK8 z4T;AY=}p{#X<}LO6hfX=7u(xb5}Gt3!e94Ns>Ch4$Ou(0!v%D|G09IR@=5CK?O-pi zl>`PhLN6fCb(iylTWfe?k$8?cpL$dXpg2MOHrgoJaCq?`n&FlzY)+XdUgz7`=mXKx zFmgC5l2oCFc>o<=(@t!r*>RP|$YM!}W$@?3z2Go)oC`R5c+!`-1WNc4e3gULr>9Ka z!IC-X%eA4AHFQLJJ#r(XW{_f=0V4z27=^N3g@yY zB4VTgCM)~BA(=Yd0g0-w=a|J9(|u`$qYY@;iSnOpZ-C|{s>G|xih}+(Fs)(MALYMe zTn92U$sWQ$X>hL>$O}k=aYvZqAau?Y4Lc>P_;|7BJy1~?W27M6;^M@zXRKH)FO@0u zB$w?P^%C$WWYHYFnahr59Jsn7P}8AAa<`Z5!w!|7dZ!)WSV>%~IBGP+c@JqZ2`J14 z?*i8C_5p5`(XL5DB{+E`?4hpVR%mS-*W=J6} z{8j743h87@aG$j@se~U~^~|vgNmA5ioZ3J3(3cR2k15aT9LvepqekV;if(7KVoH4% z0Z8xU7G*LBil&yb(Jr&VA9xIH7Rw$C=K*v4fq)O}Svrk0?bDjXEc_yse7;iE%u1-N ztZ6N~^BNpB@FiF%$v{%V1??@1$J(4)jXa)|RIte?@@Sr@P*1}2jq(lyqO%yzMoyIo zehZLtmyxml+I90i%5A&7sj3(CZHbWct%L5LHL+V(Cb)~FwUF1NexTn*4SWGmOQQ*# zFaQ^*jS|AEph@9)ys>kIT14xnjf4g<__G9tFfnlw8Ndk+YPte$=fCciDf8+AyLo~o zIK@_!W2ozy%(&Z$YJiF&gf3L*fLRsb7KR_v%8N53c@*8{Cl;5n*eP|lykI|dT) zjwwYQG{Rn!?6{6F-)e;`r-h zaLB)_JB=bw74=?(uwLb!JExNvCU+&vP&Tk_J8)8g#%uG4{rO~K3A;=az^PJ`ECvKJ zhEBsrs`LdK9@vXsCuV~)A6>ZA7pzpxi?RT^XC5D*?<95p#R+R=mxG%L$WaXexVP9Wr3@WYro^6+<#g82O(GGcN|8-`*G=;DofCu34UQQT0 z^2y?_Lv@Tc+Ck>o40DVMIsEa90r}htE~HX{ef`MMrZ_x{9%_MNd&-7Wf$4jCxnW2y z*)Qx;Gbn~hukW_%i9k~$eEj9yz0zP~6k$X>jGshtu_9Q4A^Jl+7!~1{ay}b%bn?zd zc#`%k*RO%;IRFwa>~{WJVo5vcnqZNvWut4p*zqrzR+uZVUr6 zx8~p>x8%1PS4871mfLI#QXw(!Us&$f)@OLz_P>ED4F#}ec7l|mJtY99<&hc&{CNc z!$Y3k<+8sS#j`D9HJIqD+?Z2CYTV_O4XeVTfa9RcR|s=26E<_R3)#sSlI`^mznb}= zeGAv@&d#n1l~@(iPmwRGmp3m%2ukzumXbMl+3bxfWe(raic&a^QQ8s7c z{D%&+nHX)!+hRbtdo_K`Mq-MG(D>_PUQlg?yWh2GOGv3fk9s;+CJtv)`r2mnA6}s`+Iv8r(;g1=)E7dwU_S6gGVpJPfnj4MnM3GrZdwv0@R*2toBDus^@KG zGla!J=ms!ZV5n?N{}p%3*1K_69(Kf5P**%#RnG-k2dO*0Jj1I-e2N~@)UF5|Y-KCh zhx^<8S>NvF_{L#da$ubO!%~eU-A=D(-1;>1x6)toCPWfVCy>z}@YPo%w_yh=JOL=~ z6yXVDcp-qP6W)--pq=}u^JBQYp$b~h%( zKLKuYE(Ma(Ir#%sALic4!-q#BP?$Q>0kPx9` z#ls@k4y&ftQ}*c9V}*pI+PN#~1^LZ*8Xu*f=aqnx-@)4ka>aBC--7806_drw&)$f} zzc8-^B<}9XJz7eJ@L+zcXNgx*P}ehDh?C%89Amu{h@qrE7O1rzR(A_JB29Xb?ViY2 z$tpWF<1*H}YW_h#qE1%79I>+*;VMnMcElUo++ zpQ9wXuhVBECnCCyudI`DkiJy0xzxJ%TT#&ar|*$Rga$#?R;aGk>q2`xT} zqLsL{+DtDq(vMNMsDz}s5;&Kw1~$(mojiYpTlr%hn@==0QlKs ztX$>ej?^c`(|uz}XAa7K@dC$z-s606s0ci`9#-p~=*{dg_xT)tm&)i(p70#LHmAHY zk#R-?C=!QM+zc1c{Fi0s9SCY48-O7H#(gVHNpuyfk-G8({l8v9=$qpEj`E@;425A% z%l{f%jGXzjxA*%GbofIFvqOQEU88`;Cs;>BBMWl}Qk~X}_G(~bhw3-eb@cJXBdQe^lRax9 zkSo}p!q1b$)D*$5C#_fWK2Lmtid1NS2JVe7Aoxg_M^&pcFNm7{i4`qRf(gK(@IFuI z9Y$tzLgSQcME#4s#nww>$XGD+&nvcSeAR-VBy(PLuVN)bvYF7_74*=(2a^R?3VuKS zfdj^!mjl?o>+c`a^>ng7{%Iuz48Ix^+H}>9X`82&#cyS?k1$qbwT4ZbD>dvelVc$Y zL!v08DPS3- z|GFX_@L!9d*r0D=CD`8m24nd4MFjft2!0|nj%z%!`PTgn`g{CLS1g*#*(w8|sFV~B zqc{^=k(H{#0Ah@*tQgwCd0N@ON!I|)6^`Q?Xw~3P z0>F&P85;TXwk#VAWS+GnLle5wSz<>g3hqrf#qGfiyY=*_G1~|k*h-g(AA+NbC~N@A zVhf6A6qXmVY2Temx2|X$S0UFw%*D3^qpS5e`ZtH#e-p_hv3bYtz!vUA56&MBhN4*s znI=g8YNZ{TYX{~dPZ_gk$3 zZ?0ZR{D-aliB#|SEnR`T;N3$!}02ZQ(F`K#y94FLke@r z>i04JrfBacpWL!tC&p$j#%e~cG0Oa(wM#M(Mn!CQ&`w@usAmfZg29h)&o{r_NeX64w5N5WxG6 zq(-s6n3+LYQoRE}bt$YsBWg30rQ*(MSoLcIu2Zpl1bcHm-1-=no;nuG(Rr?&=9Dia z+wfu8KmGNY@a~FBD`eM%#b5ICn=aI`v<7i^08qgeb@EmZ1l73Fe^)VHH>vwnl#LfZ zYM}d!X*vZ=X-Kmm)|p~g8rR~7THpjqRDXxKte4N;M7#iYw%0~Ki2cgxoq;87kGDaW zGMa(5g9dgC3{EpOF1o}w3Ms0+270RrL{cUBU0=kwNClDNSwY!Lm!3n$dY&svjk#S0d>tPZn?&G%Bd ztl_HV)BD3T&C$JTZ)yChEr+){P!q~(%s;6J22$ep1;aq;vT%}A@4H_e%j*18G#k|8 zR4HfuOLp~*H8ydsM!zd^J6-{I0L19#cSH6ZtZzWy;Vf%NE{=DfqJAc(Hd_EwUk?-s zA$*+!uqnSkia#g=*o}g>+r%Me7rkks(=8I_1ku94GwiBA%18pKMzhP#Af0}S zeaw|!n{!*P9TQbotzCQLm5EQN>{zN@{lSM;n`U!Q*p-J1;p{Vto$r7*_uOOfBqxP8j9?Yom^}ld7Gy)Bh)og{sMVE=iz& zQ8tl{Xm~-Z3>H`75=x^d=n#jJ1K1%%tgPj|GD0Xzq9fV3Ma?HtM@!DivcDoBi|RXcCu&(8=pz_F%9yGJ4E2WNqNhi9LNi3%1JG?Rmen)( znidVu1H>g%W>~Nf(Wc-#-n>MaFPSE!=s9gJNWJ^lL>IYBfrCTlc~T6XDLkz-s$mN% zIcmW+gIppg>?!bII5df3{O}s)J@}LF^h1FuLYU-?Vze6uM;x907Tu2_LdU}6#WqSB zkug=xXpYs;RFi*m4cZ2p00*fzjt{@Wmy9zR#T`u%o(6TyxeX%8M$A)wCq!0MXnhE! zs@Iv}v%rr(7RGQM)UwkdzhO-}lT}7!tC()&KKc@Dj>7m_nc}0VC9Y|;4=Sm7dofgU z+K{Ti32BJ+5cs-Xy7B&*T#hw4cF}b803^9dTGqsxPPP=R8-^vbHS!I{bIm;SX<)F`Yyo-=KgvZ`cta>vzo9Our^+Bfz+X9 zV?O5|xpYjqy`sdQ#j!QoL4@>Z1VWi#YaYf}_?(VW)6Jb?I%0-9#+l|j!<_zMUmr28 zik23XZ+1$xq!fw=hEFm2nC5_iuZV4X9&o7i zLrgr7Ms~sCEB_sDy#`7cxztH9MxO%Vu$A2wR*M^gV1>YxG_=tHv&#iqu~^$wcGpy?v*h@t(H$ zH|bo)EDRwA1s%B4fQft7@6e$2;M@)U$T^O5!>z4AOYTn{6SGX8hvO!By2v73jw^`8 z=HZ`X|)E5WAI&98d=Qk&8#5X>qZ%dRAYO!+Y$z*tBa^ z&){4d!#2n2RL#)WWo)O2y|<3#!jz0SxnV@_sd+@2et6Qm__f*>Ztf*pa9^^XX$-2! z+e{3w^PgG{s$OocN`|_D^8+P}+Tw=R)lt|<;>l4~B4Y@ziF_jJ?^?260204x_$pCN2!RMELv&n7a0dHvv!~W*yB~qxQVSiJ7k{ROR50x*QuojGalJF_K$p&Ul?FMT z&DVHWb(8HD$KLuihvY@DN}=fG);!(efhBilm#&2~I0+NuobS=9Fxe zz#tO1zN?UV0{P6%Fu7I4?94bv_m+30R(ZD~*F9k2pnS9#`W3i=M@{Xe#Im1}$Au0o zHxX=o%Q~r(4Nt(_aGA;|qDjGcs5>nb5q?Z)GFD#iisNE^T(HXkzY7ftImPb!MlG_k zgpcSeWS082&ms4T`UWg^iI}i7!=&MC3K6rmfKU|M62D4GJSEtL%RFmFeIWo|379{H zrGTh}r&I^?;fwcO@-ljq7NFchF6Y2$%I$XOc`WQ3yUri>IJ3U+d$>nA2Omc?+Vu}4 zDKc`JU*$v+$ZnN{V*kM|~Oz5fC%_3L} zubS}2@T6qj53q?Hgk~U*`be^>m6Gl_bjnVurQfuZodxPFyx%$IQCF}2Rb&BGh<4$b z;mVdA990|@Ds|@~-FtqRNkQn%RcLefMO)&k1xdP=D(y+19}~feMzCYbVpfqMwXm62 zg6zvoLd2OSbfiVlxiN>(qh)DMBJ^VZT1Zz!;rFge+?LVH`D+>&L>W6%iqWX3VNaZ5 zAV`F`&Lhk(u}fBoxw052zhBEdZMq~|_C73Q#@UhFZP}lRlH%F$mMooQSxWbi&4ZT6 ziS$QR)Pm*Ni_YILnlA9wEob90F%A&GLv2 zkW^Uh(@WkC(rUJ%P`^p6zYt1}Z))akS+g6i<;^}f7 zZT8$~D`X0xfWFn8{ez$X^+zie9ca6ab&RE2gnC$Ypc)33`*xABXDL+g&R8F&9EJu} zfD_}@4m{4hk1EZGyRtP?hs3Yn;~Harq^tbP9EwBGjGu25XF>?agUOxds6U1fXSQj2 zYBT$(GTkJ*aG*6nOOUoDpL^h9<{5p!am_Tmfq;W(vEd1E!N0tz1_&qDO;F1@oZQ7moSvE9 z)H3IKYVyx6BCoY_T!k+>Qp!KU}%oSL4`(T-*zo_Q^-$zmMv~bCDPcyjQ z7n(KA8z`7cL&bS4h}T>ZUlF2&@<#;ku;y2=>Q^+6TP(THSlDlvq;aMG>eG=8Qw-8a zK#wRYS+-M&luF1FZe`io4|K~3liQ>1&o@|nFc-cx6O%L~$%v-8C7kVlzOQx^L4~$-2hOZGabOWL?#^*o(L*9ossJ(CfH`xxLNk&Aa z0#56|`2O#KcHfk<10^R34lz>%6RqqsG^rt|GAb&x>3|$4q*@O-=Xk#<<;bKmN-_Rjaaf!({{$@Y2@^TNyfN9*TQ=ZWtL z@5x4b^6S5we4oUKwENln$`JpP!uZn{AmP*~GgD+B#>_)PHUXh`R4&A&u?GnMcoeo; z=mVUTNql&a9(DREEY@zn8!UEGkSEPm{EPWj8~V|6!MUqaDm#9_WqJ>svqp^ z-5j65_>jw+DH6enmvIK;+@~?uh^U=!)nGYIPrqoiS7A8j9Vt@pQ1pm}kQPm@RlrS+AG}cf+sO%+n6s;atg|E7< z#$9)B@8lRi=!3C6R?-?aB+)`sGG;6hWA&|LA`~A!)tbn^rzCc>gB}YHl!(=;0bsKt z5VLrJ{Ofj*-^6DbG;dJkB>SasakjQL-&tz%aeQ1SWMcs}_s{*j`{`c-Az=+d#=N0t zLtbSA4QgDb_u6Jn_rY?4)TX!Ry*Qcw!y}hlq6*4RP zzy3aCM#r*nOGid!L1TF-u(Z?0r@+mIRmf~ut);TsMPJi}xS`jI|J4zij_)u-tFZv;xMU2?Xe^gx#=5eG6th8;&yqapc}8Xt@F?YZ8IZ%&@0 zi<2$U@z5Gb5f1vlTyq)wF%H!`Jdl2IuJI^@1%QMO7@0HWmxHE)U3VAzXirY89JQM19z?4 z`dFKpF{PMp`N(iqf$7J61XbZ^#J=DXY0l5F~vB6JR2) z654K)Kt>!3?}i^R4a8x7Qp!dlWD94pXL(O1-VRvGq^Fcm>>v)LhtUtHU(d8{FXReC zIWdIAXNky50&XLUy}RR-nlk7e z>rKDLIgd8sg6rRu6awe@u42O#-=JgTNgUK>9!|)b24u8Bd>P+wt)Q2*n_MnLN5U<0 zqyA@~A&QdWsQ_uPgbf|2Q`-vVJDu=XT5m*0qWOb}7brRn>TYh)q8%R=1ZrarsZkb2 zz8?iI*8WHzl-td++)1z;d4ES{fJ@8q z=TViP`Aj>fpwxWq>E$|t5!;^^5FO^NGDq!}*tK@0@>AIR!u>tAYV*j%Uo_9}ssul~ zwyCpPyJ{lZp<;`_@Cw2k@;P1?KNoZ^!Nrd+iG}ii2^gVGD>265s z2RM$uM9o?`pPyNo0L#kidYsnr8$04p#a;1dhQ!T+5AIi(Ku9da(DDK!`!_1l-0S2g zM(iKju(3Co*!;tCwr^Y_wO6ay{JnacPx_rKwoIw;+{yxzdy3G*9fb} zRp|3@bOlSkiEws-!CB_SK@(iTS;rWx5TN@BP^3!YP$4F3)RT$aq>Ee{N9ae0jpcIn zRa}5JEFC%Y8-#%8to|W;CHI@9@d4p*eow1&_bU6ZXeM*rU3c71r^W5#?qg#IrToi}LjJFB&;GTYOcO?#H?%!I6?zeUSN z%!E9T2g~$bAF+4z(pZVXq!UCX!<;pD5%~rN+ zEE;HumO;S2M5Hk>g`TvllDMpyN(&a~A4~Sdnt4jbcw&0Xd}(aO;Rw>AFWt$PtvUxT zB)|mfvML)?L7F$b#v)F$G}Gh-cyN*)zGHz+lIf?$1i>P3(asIYz~t9;RSz*$I|eOM zm@(804`s*#^g)L-b_-c`=hnd3`*`xbe z3}rP!Pim3Y?f7FYBM?*sWw@f65j`^UrELxV;QSoTyK}u3sP+Z^i7(8C0%WM+9&sO8 zs!Nh7QOSH`vMF%*i(D!-;Oj?juG1_}9sewcwSrlBy4gVzZ_Ab_{;9{ z$@BQ*F6Ve9;dxrP22LbhWnVo~Q-d%#mpPHt?>+g@92M@slJzAQniTT0whH(JKcIwx z9-+)%J2~V6Hrp*^PU%we|FZyY5~iTQ-^5)8ea%c1#@MNLYtRb9g|c6>9x+C_NK^ZV zvbEP((f&a*Nc11-h9aFe+REuyN8A%!*}FJHr!6FA))ywcpJ#Uk9DhVo$JY(Ldv}qv z_9Y(A$>Uron)tblzGL1;t9zJSMV)YS94Z>GMeC(i&J(M03i8+6hr+kVs*5|*^1W=4OKvz3%;-SS|rD#w+Kq) z<3_9DA}VY-4Oy5uqwFkC-Wn8TRZ8AE#gjm)p7ei?aWX0^Nj_RTpIp2l z5>RYCkYM1tjM@1mE@?p{k@yMvh_zLdfFyp`ftwOSjxljXS=%oJHWO7XWSp%`^R|yq zD693?BQyrDT*$u|)h)+*{7MBeG8n z>Q>!~-%tDBG2ML_AKpcEf7A2z;P%0q4UqIi@=*O0CNvMf+}WA-F{M>Ss+f}=CX+8!vANYVg zU31%sh@u&zY~^6KOg+sb)=X#Kg_MZ&*JUAxvB)XZ$ zTk}~!$;yUeq)V($K03#i$1C>g1!C~YJRl_t0yGj$_w=%4L1>E!$NR(^HqC#W&QiQw z;G{e+Dry%9owX<{W#(vLc-&+|mA0+UDw-Jtkm44i-&Rsi%ymDQ2pVf&@MHH5ACj#)PZ?FN^5PMC^v^Te%XllwQz?zCj5)idP zUv;;r*|XYb8knj(?n1=hLDtF1i+(fUfJ&Ftl=%niTv`p;bf0@o^uv1U$4+1CpqW$s zy!;npeaDP6iqk2d3dfkV7jMm&g^A))2-b&}3p!XCxTE%l|4M8wdk*mAtHfxs`Dez* zDlP&9+`PZ-a4g4&KxhZFD;8r3n!d3Cxt2Sgz# zN3x84z4x{J022`R2Y7T~`75}RJo=;f%0p=oO&5chCXrN$#A?d`c@tCJNxVgGUyRPf zO55h4uL`2~LX{0iEIBh>DMplSo(G#>NDvuIsm@qDFODAV-qBBQ%JU0YdgCV^+xy=k zXcwSd+5Mze1Cqb=gjbya`m>X#5(d(oceGuZvl3>ggsz-?;={|)5!etZ2d?Pc8W2Zt zXLu1AzK*D64#vko5W((K-2$y&bz!GwQ?Mjs9>{R@{bK?pI^Gy`;;-rpWX#R{sH~G@ z4;>(H2i=FikZkkaocR6X`;ZVY?o_;Uw*!DtOxy|(2gK?XN|7RVumqZ?@}b)*r*@&+ ziJ2}DYmrh!lGJjcBd8ZG3r5sgx;tU$d%27 zplmZ26=7b$yys_)pmK1#-gGt`!Mp$aflia-?$2g;`T?EMHOWKgFP0?h-QjlYx%{ zUz-b5;g?Nba7%6c!dR`EUQggxx6j-L1>fK}1nS#BkVZmRzMBgIT~Ju`k)5C`KV(8q)u9y%>mLdO*ZW`T-fcFOM9b%Q z43EKqrW~mKI|D(YbBz$)u*)YmXGBaFB1LZy=7W;<(r53Om70%xQlvjpKj4I+VRSSO z_=f}wu_!`+(3z15!(X^miGPu!OZtodY2$x`sR?1uHm!}B(1DR}nKYyCysY4ncu15~ zY~qJzukY+&5H@c;5{BAyxC^EsYRYO)Pppaq4&)mM%lM^=p-O)!sLJF~p6$SInmx`o zz2$_HKM7BGD7gt1K~`T39y=to)92GP`egBvS9d4Zw2dF-*$O|GfhSJ-jhp4F)-g)g z>O1>cSzkRHXw=9^4vfYK)%WM)oQ8Hocy9@47HHmeg7sRP6|}GEhYD9B;+IV#m1X?` z(q$QhyE+*9<3D?%DL-P$jBU7rpvrY=cMYxlWs~}5To`;v*!)qqF2RL3-6@gKSTuk4 zSf_6-#`r**((AC`{-QF!HctJH{@&oQ1@w`UmWo-0ZK8HC6;C_OJ5cQLy%TYNGt#1y zKydF3zJ|-n-a&T2G6*8=R0kFg*busbo&10_8<3B~CgXCS!vG*_4D|owVIdK}`4PInCK9TeUn)ND=X5X4`d&yE<59nsz+V%MQ zP#AkkQtW$DA(4@6PHw!6dtz+^it}rw_WAjGGzULKJb}HMeso8qlUcrOYw9YXO%1pWG$m_Ff;5}Lbk+2u$0ifZ6W&DA(Lgf*X8m^Eb)znCFq1j#A<=~*cq1ZMi;f>9a4 zGE;_qvHkgsc_1$-D+(r5;U?|P1qCnr*14Gv#HXD`PLV*pDrak*T+{DnkLs_S@GJ#| zNrUATuiTBt=5$b*aH}LwQTcLq9Rv1YD%tFDD?#ZZdUeUPR7%Zx{w81>2!MlpFS+ir zGB=tWz}TIT5;Cs9!X8QXJ7Va!>jHJojOte%A(kZ0c>CO@Qd zFx-*fkfwoTb5*LPichy(NiYvTNXGs9O1j*I?4NWCc}E+U>zK;h?Q;5@Jw4)>`F`!W z)6&`;BKuL3)N4wJDk_kW*oI18QI-qf=p~S0FX8cwWX-(7UoNSbQI*^%y_I$b4gsm; zHq6pio2k$e8}#>lVvX!Y3x~JNOL*d>EOH#0ZDT6Ks1!zqm(8L-O7^uS2#UGN5YJw% z0VNyV_IS^$LwEqwR(&qa9bzMqLOZkyJ;o@#e^4dDe)?2GuNjCDa}X00?wEG}&lG{? z6~4axpc$5MG$d&D?$&Gj1GKMVSN63jsD8H^wXbaVf~$NN@3kyM65SUrp7xc4lH6Bv zz~hcTP)Jp#l>lOA4C!wL-!CZ-e!9=X5F(maW|uE;!PHw;2*EK%^qet9j8E8jnpbxJ z;@$R|9}g*H^M62gQJ0L|TS=7mOB3=_r%!`HBJ@ubMe0|y@0wl4S2~n*5K5A&=?UyR z??vZx*5g|5syx_=?M6#fdC)?8d3jxPI_WPw-cOHD(ShU)j6ccfV z%R^$uyh<%;9~yJ;x*QZX&{cio$m8TZ8~vrW=*hsWnI)h^c(L+9)1_~UUNmfxnuk+q z$iIx*$~fI_P=Fb)-8vz6t>7E!CV4e#RGeJ@XfjG^~7lKxsv|S0aO4*gd z#>7AlwrJdu9gH3t&FZu4hev6i{Vdotd-}VElA@3M3>k0xV>y8Az_MG-A^@~_)L18r zp(@o?odRg?2Z7Pe96ghxx-n&~IaSh@k=#4}P-nb--$_5Kn>7h)`hqXZi>rSmFx>{n z7@>cdDf(??-PC`6q5V*%ZNm^Y{K>)tElp#96LJD^lpq3wINDjL#DbNoEa>)I+E??c z(XA_%Yy>I9tkj{nN4Gkkz2L}Y(~1I>K`XjHw;O0^4(jn*G)RpWmYTt0hmhUo^jzk4 z2-dVm>Ss@DSonH)vP^+O2Z=~UBG#(-)VEQTZYHgbDdKw7oUK2|_jQN7K!x|)uH=?) z2RTv#S7}lIpYpk#|6=YvWQ_?Ju7yee_x)A3p2y?6^qx<}t~4is!Nq!7Hp4)g$nbBO z$w?rcr4a<)_l-phT@?O5;ie^U46P%zt~$ccBwG5@iX;KY z)18@wV%KsGq#k7!iM)&5k^W@wr$F93#Z7|8Rw9f9%f2?FH)^q=C}lM^wz$DnhV~RUT&Dwk>bA^yQI$CZg7y?%u?OSTdsBxk_(i&fGHa0eKjfY>f+?c0 zBVLUdlL2TEw&gsY*ig3LiQ*Zj7vB7Z>@Ons`2joakt^R$^yfN!L4`Q-T6|U_)q=pw z*+|rb4i-rr7Yr0Ob0>BbGvylsf$)*=FN=oZ@P?gacX@~HeJ6T5H^qFqIb3L{nO&Vg z6x;p!3vhl$(b@r23KSJo#H8#zc5d;#U9PmJJWq2{D((bvQOrqgqOZlhs7>L}^0qs0 z#8yZdF-hqX3lg|`?K6O1rFN}LX;FH zmaTG7;!g(=vlF7z9W;OKtcegGqCQ`w@Es$3q=lgxxMAn30DLAJ11X>zW||7-$){rB zlN`wXyr7v-LO`7R0euj1t4AOw6MJ4L-2I56=0yAy~9I1jLlgt52Pv0>NM&0lrqo%Ie9hXTfZM-Q z>ka}%TUg-E34%@{j7CS#dV{sytQCi4Dq)>5({J`K4v(!Tej}oa7MdQn^pCzNxDbobluhE;bIXfb0$LVzx2%1)6GvT7hqtzBy;j@nmClpDd_5IJ z?(!G@V{J4>TGRR0jydOd|FexHY4QW4Ie^ zl~#^+B#t-bwUhyMs?Jj9%)*pEOnObEM3a6(;-DI1zu<{t87#GfRz@Ln1%$`#b*t(P z%H(icHO87l={E!oqfw3baqF@(hAGe}RVd-fciUoq+YgTJ*a8B}8? zd2KN@E$tzz9o3oP*AJ;h5@U(c6;MDqQPvHm){5w54$xEcsb}(q=+YFBzZQl}E5Nm2 zaCL=(0LDq$u$c&^8KVH9Y4V)POj`~SL2ux_Q6?7KgiqzZrsbbPoBRUt_%jjLejBrX z8(Q%Ha`^Cxhc0P({rpw9w>1e^WE+hKg?Y_jIoQ{-h>=8w$1xdG@PZBV`}pRP5ye<& zf|pmGzds2QABJft@-FP23o>%45TCj0jX|thKOVf!JI{!5cFF>>e}yy!Qw05WwzVv zGuY>bs)+luF5mrL%L=v>hicl>it?}+Mv7J0fals>*Y=Bo$zau!^@g(X^@ zn372Ze!FUSOeh|7&Wu%;3W^?h3jz+=aXDYDnAeOPYuPSJxK&SU(raS{wu#B`*tbjW z%!z=TWAZEwBZ`w=)ol5s{EUSko;uZBbTW5Xc=DLO$xtu zXxG3|-mfJRjjLTn#Nzfh)djtZyYesequJLt(rpSwi;44S_CB$L*>@TmJXGJx%Pu*# zzD>oO2u7X~ukiZ0SDDy)B$H&Yo4hzyK{DPN^4RH7Awk3P&#W(4TqW?$C)T# z*C@ipMViB=QhVE8j@vSx1~bM|zJ)C(Ety13Z_~U?h{=_@+>p(_2&1_j3n|Uwm?oi}D&K%Qm2ts-_UO0%=%;OQkBTI!QEDz9Jd9YLeirlncdc}s)6xVJ%vE3Sql zyI2f|WXL^@0^Z6|-9TBSxuz_6D!c=bQ!|Xr+)Xw*Q?8ELI4r4lAyVW@nKK~ALz)Y- zEsZ5t|C7YquY+<7v)dFcxtns^nkBXdX>2M?tz})#mWhdmFrpnhQC@RfU2bo666I->Fpc++oJ0r}&Sk^(e zXG_Di=-Gh=57Mu8X<1BwQY}Wvw6J>&eT11Y9R>FQKo&ztQ~;Vu5yg0bVzUk0V%0sl z0~@yQAPFC~Z_>q%D|6D#m0X*Fr#r3$w^8ESaN4VgbT)INqZa#*89Nu3KY@LGc9z*l46Ae z8>0nBXBVz86Zo#KDA_ilTF<5d(ev{D}F^?6PiT*X6NO}!A)^l));|A3%L<`f!&|&$o z?SDB=(n%uh^u$2Ce9?A}w5Y6g`WqG0u23!xy@c_sgK*d+g?g79X#fpx)+uV<@0C{` zp$a}OG(F4BF_KZSa%b}Kd7a#wMZX2*J8KXUF~`pqSo zfax56n&U|H87OxNSV@L;9y(FWK4cx|{SfDi2KZWtu`;0Blx=EZtCFR94s^r$4-+oE3Qa=9o(oYnIg9@yWO>9MSjudo59lbB+S5c?{kbcIe&wQ>Yv<_iMK8|Z z^$)9Wkg-6Al>e-IeVGpPZyJ3N?5E)cer?Fz@+TW_cuFLiqU4dI>dP3^Ij-N7K)6g& z4-TpbVUVtS!tb`3oxPj$PyX+y8IRkS#D<(n>{wvI1Jav9?#sPC&(8FVRI}mf!oo%fx}M&s@Ags zfl7Gpa-33{*2$Nz(1}l{;tA26zMKVtdIZ}Ixz=#-d^}~~ z%*)*uF458(h<}3BQzJX(Dh>=u)-wNT16&Gl3hB%hZ>#QM=o2j$X`p1YQF@}xF?wQu zz!R9gxMG+Ma?+NkhfWv84zd|%QzYThFtlb5nJv$X*%D(}j*c=wU{q~lt}N%LPhKQk zJ=8FlF@O`dgUA|`8_C6?vn6~w59qOt&?q6{VdX~(hAa(&4NF$yC0Plc)HRcxlM-ri zB?Rw6?|ytX)FmYh^{Wx1rO9iE?#wLGVgj}cAr|$)K}08sH_C}1$hgs}K0B_Y1I~C@ zOL{ z1Zfl%2LfHSj0bn{<4O;-p!s5H_boBjez{uo(eeQZ=DB1jR|nr7+`egy5!CYL-+&gM zH8X-({qZh!@R^{9;qCn84~(zrBBz=QpWXo~>l4Z+I}zfW#)^?mJLYK!HNV{a71HFt zZb_96PTal;{uDeIjprVOA7`|{$k^;xN>xYUr;JAo$mQZ+UNWWx+uey#Q@@>v#{%mg zh=!SU__$faqLdHPUBAix)ZFE}`U69MY94;S)@N)Rt)}z*nE)=nvHKHH)SBRwF6w@U z%{WAn?d<=tpyw-bUw9)*>i(&G`15L(`vbVn<6FbAfkF>Pb6#}1PI=uE+)rzF^G^S+ ze&GGoFSt7m|Fsx%P!q1?Z&5~3q3kfjeHZ^8bCWvRWMG!{NJ6yG={XLda{*G@ok|UR ztmP+?L29s9JcSRB{|Y}+YnL<0l~H-3AUX1J($9TVfOP577pB>?*8yuKQrBa7^)?$U z5a-6iG>Imtrw$rx;$7sXa?X8Byf%l0jI8aeZaRPZz4Y41;3MxcF3GS4sdLql>QYDE zEAcK{|L-naeh;*qzCQvl9h`lOiUr?id z+v?^Oxye_`ql+MG%>=)e@X#W*FCF8lyNI&Kz>sKDISoQVuaP%a?jMRWpQw|z8xr^3x5u04c%BP z3b>^9Z*$KFw0>B{858_?v1_O>nhWnrzn^oOhSO}%H z%Z5J+0G)Tn?&~;$zkv*YH2!Jo6oU+qScfFjv9L2-TD5>GmlJ+`qtHtTXW)`y#urM& zt}VpSxp#Of&nKYEMt5|^o&PagaK|=+dxAm)!^q~&^z~H;!u7=C9e7I;d5t~Gm)S`h zuTU&%GtiF&aFdWDb!sJ}cT&2*WvX`Xsi{U9dGer`Z9@^lJp(OMH~q|DDWBMV^a8Uw zo8a)Dx_piWgChXOgm3bd(WwGw%7UQGM)WeeeL?#DFJ)-dNnt@XjnH4JQH3EHL zR$B?5>3fOYqlw{+4~djG01ILH@I*_okPN96THH+(b#ip`0lox<0Sc^nZI3V@+(PA zyCHM18WF&4)O32~`xkjA&Wp!OXGK392=8J=J6)`5C7>VtAC;fdFR)LlBu|V|Ly=TH z&l|N<5Bm#MKN=;`T<}d=^iNAoxI~>WYgOSRA#Py!Jc&pDmM8>CysL?bK@1X-=ZB@O zs#QPUZ3-}5{ZYjTDb^=obcb7NMtshRnOakLg@P?op;*;2Gsz`&8bEiV^3I|U6>0jV zd0JhtAFlB`I8|>=SEl<6(vkzlds~XrXqkpB(|$BL-G0EH(|tRN|Fx|BX|J34cxcKE z0_|DVP@YKMmiD4l8lev2dcOEnvM-^0F4u!qz77cO>1}xr>QVSnM&^(T#aAan&22WD zm`>+yc}}<>YTyO!iIny-Cr#o(1d;81c9<~M+OKx$*$=9Dzw4r@t~0I=PL!-h=*Y)4 zJn2j2UEu2%3+LR~qo|To@P|rQ@^jF({u+=qzJ-kVV%f4>;()#DKl;B`v0sQoT_qj+ z`JJCo@m-yA!cOrS?sAXp5L8DKRzeHd5wxYZ*td%3+@g|GfH~7GQ(M8BA5kh>=LFu1 z>X|=nHyZ2FUrkPvD&yOfi(k`IWI}3lJ^8dm14Y`wnB&8jys7Z}(Pt^~&pM}HW|lx- z0tk>v6``i6KEzswg4Tfj&R=`%GQTN|R?O{S%WCov``f$ggsYHor2^He$(FebARqcZXjracd+=UxLrL{P1Ij`PnhTE4o^G(vL$nF;FvH>dV*r zPkW{z9@tAYv`v!nz~FGR`7mPT`>TKzIQNh9gJl8b>6iqY+2XmiXIBZQ*=+C?*l_W% zlx0KtF7u2<-B#RB)bi;;U!=rmW3+(?#i5VLdE{qHrmgjb;p)aIR4@yCPmgFAQy!H| z<3C^ndLBeYk_)(m!i!Ch*Xc&l zo0hGTbf^}v7Pk1y1YSLXwNfadAA<}W8u+-3Uz}56cUX(Ue_e?N&-Q9$Efy^y{1NC* z=8GS((F07i#WnvUbPOpt*&D2sKL7o~MhTt#>jqvaI~g)097{NcG$f`9v0Zlwjwx7} zbejC?7nRp$@(c2jcAjX^sL>Y4+4=H3|60}*6#u01glm6Vd?dg9QBgLo-T-RASP?qA z_nsQt>)Msut4ZQR_ONtSmg?8iRT)2Hne_5*ptC58Kw|pP+VI4)Hn$;a!4c{kZs{vT zr{y5|-+taT(b()njFDkH+~+yd>`O|%ecv@jqMJfWoHbHH*!_^XcS|}TwSUoW4Iz)N zVMJZ{%vqt!i%7OeNzJ5H@p--Yd4o|$=xCuI`iejNvk&OWQL@$@8}X|u>^y73>1@M) zp4v%9OS~`C@|*g`A13NA%(H85&m*P^{&=M?0+C0E-E;9@?=J!8vJ=I*0T0!6m?|)9v)j2cyL6 zel?wK52~P=ys3%>L)vAowVp;$jH0eob;4;SSFg%ZQ@{){U{%(ho3oxO{vu9RFQNsj z(RZ65xM`x=@R75@Cstzq0=kV;iLV!iszYeDO7+i#E9sTw>X<4>1L8IzC z{0gKt-CfGo^{Q}>B;OnM74e$;UfuCBjfM0#A?TY_m;ElVC)PND4pK}eKOW2<>s`NM z$=%Fl+4~T`=*Q^U?~pd9ObSyxM-pybd~!{`^|Da?vKVk#&aqNB?-*66Sa`FK(vmDW zU+%?rB?9DrukH}a^yYUo5Q}x3uxXeTNg=AQ=COu5|I4|Hi?B)RIJ<)}Q$K_IW&JMs zF4dj&UFrB=mT&*y_oG7xP4d%a?$3aNlRUc>GQnNx{Km~9X>3vd6AIHT0z_tu1)F!c z64_&q=-W>zpE|i|d_=6_3&R(upV(#ubD-6{C8tbh7|WWw^CIZWs+E{mDD5u8n#-YE zfD zg$*C2ZJxb@&~2ESsCzA!QajS%m@mmO7v}sKG@F>iXYHb4-N!eZy?=TeU&eyCzG^(j zV*>^_mc2Y;a{AoFkKqG?pPZdAhdE!GTH~#+lza+_Kb=_NJSggQwZOs2NaZ1q1ineUP6n)i2A@s0W z5vzZwryg(UDCqwR#DtYVqUSJcH5_&NaU3#IMp13iD5cFtcMd~m)|^J+fB}LNcebbn zTlN+`+!oCzJvRdDi;uHAzyE+3LOhEghf#s@A@nyB#|(!3$_800nml1MwOYg6g_!1L zyIe>>BW4r|4A5Gn-&m>w0(4njL5oXWj+#j?ssKc((b?dnxlj5dDlo&Fd0|DXN3bi8 zJR2_xjkD0?yzR6W6BGZuOP9%sedihUsJfheB=3f0hdx~^*wu^8(1^uBzX9^Am-K-H zuE!Yxj225u=nPg}T|3qq>JLhl9QecsYF7AkWfJ+l^7(#c+TbieilLfGH`PjZwYLQ| z1_m`%|C{5SLg3OlK(#R76>+c2`lP##ENP|z3$<;n%(AOHylE7N?!^yH(|yYWtKD;Y z+|_|`_Fu2i%Fq^pg*R^*ll>DQROxBYT7sndVW76-*kHsj!q_Z7lOztI9J|$3mKSLP4Mp1DkdeQ7lMvpqQ*Nie;g~@YedbCGHN6e0xc#kwQxN0 z^Vv#rKJAE9b#h*b;Bngxe;^6y|K&Ek{HxT%d2mvivAhS!cWgG?j}IwQ4|~8Spzf4! z*hlvTPC5d)v72oC%>g~bvs)9a;>x@bws#XZ35ZGF9n1Jdl!qen1I<(J=z;5J(Lmaj z=ZI&{j8>BOFq6!@_%GoRY}jEn%-_PLOq9+$n?Nh zu}?n{(tHF~oesPh27>LI2xE2-M<*NyzG@-Eu*>=hoz|QV;4nn=2hqC-lMDQJ*A$qy zB1sK`Y3~QcG!S3tC4BfMpkSJUv1j`UB@zAwV~`4f7p%to5krTG|HDC$<=R=uvZWNSAYY{6oc?3K+er?m_Z7MJyn4C5h<9k z=h$-P|NWZ(w|TZ*E5~aC=GU(pj91CI8+*1g2_w8KNIy{Kz+Dufr!B za*!iKcNwRcd5}aYBO@O{o3U#)!>}1Qpy-H&=LvO8d>XjX_45|w-p)jTfKyd0+nXsU&BOe65d-4RsUw6Mg zy}(p=um`g}eOYgMLMbL#o^_thr_j%s?e4m-uGxK`Q>@%MaiZm|K79NUk%Z)P#RZV*1GG2%eKhW{T;1i-e zBw=Tgl5H&Z=(#Kp#n6>jlqAXRynDu!frjKv(1l0rZuqJbTMlZhKHxetCCBsGf=%iH zmQAYDOZyWiTkd^DgHTKT$8)aUdHWiJ0;TCAJMcpjkk0$W$RK^6n!L>DY3eNppQoO5 z=1Phmn$E1}U(n8^->r08_Oma^Bih<-t_d=@(SQE%!KD$knimF+hmFigeq(BP|97K2 z&g%Ra|J)msl`W;4&wbHR;?qqbG*D>d<>r5O@@|!H(g|m0$ID|pGIx*FZqZfEr{9ET zm1M58Oz*WjdVG4=*|n<<(z;L^2{Q+@ymsigCqTm1ZuT?FGilmceIp-j?GtJB0cShR zqf1YC-8$($e|RRt>uUD352U(gdJV(J{)>Fbfyb@6yf=fZDcgJ1k=(u-#-`LpC!?cp zux5#jfhg^^I~SUI>eA>XcAKlm%x{gP62HpmI^J*LGFY^{l{WsO^5tl-)?z}%$?Ei) zI(;H(P-R3xufWBe5-~q|Z6LUc9k~*tml&Y4Pv9E#_gTMkB=u@(wm1o^)|KaG(ja}O zhcEfX@=EJhd3N~#;ffHfHRiIVwY2Jtm1{wH<3?X?KJPtK%kX`fwmM&aN2saV*d^t~-d8jcFKelOu1u#)L?b}9j@DrEk3zs zmLwva$*6SY?Bn{&qjA)!YTE~WAsuEI|FX?zvoA=Jza`T!;*!{3kLJGW4`?fVaF!sL zH0&`XOkP#DRH%LbZ0%Xsb<@WcUdHd6t?iYrmk?~54kM(+Aj`r-XH|n4_hZ~%2l==02UN39MR#|n1zvh%ZZ~lD`j?}|s}6D+ zc-6G$o4gs$l;^(RI;NNV4+?$SS)*_dGT@qwmk!E@E=k>eF15wTKiYQ%FJYnSn) zM*e7lbK2F^ro8Cew!02==YmDOWfDd-zS7xd?zriwCP9xr(*6`mErI`7X^LOh(~?aE zrYlBE^WqWex-pC1rusPD{C8~Dor91ceC@4%mw45*X9BflU6fP&d(7EQLVC3gFFi*+3$HoaE5`DIZNN+4f zrD=Nhe)?OUM5Uok40c=>yBu3y%9o)R=qaYvpPaa2KOb@ zT}!1cAs==0ivbCaURv3Z<^pHv_6^4afh{-NgJN8mGoA^ccHG+&_#osv=gx~7S4yy& z@m`^Ow_1^G)vlyrl|xp9cZXLx2i&Bd&8ME_3)`j<8=vz8Lz}}y-+V%EdQNXLTT(f_ zQa~H8^-A`bj=Nc7+~D3gleMeKeO2>lc0`Qt+N^k-S%*-vu zOh5O{bXGo1)vP@&qbMqjr?Y_qwkhquS(}u<9$PU+2i8^@_B+HQf1CZ z17Bj~{<)(?e#sQ>PFR$}%I@BfDKF)LePd1@n##t_d5eY(=@UfRmW0s)9g<7MRIak- zBoZLJZI85G$hm!YHdh2wwIHRB4Y*l?xbh+43zzu~LMe=@1V}uuE;jjwL{W^?Gyg*< z4>{)2s%ANV#@U99o%}oB4L+Q%RIDM3b#eOQEjL7zvo}<6INEHglA9E1xc|jzlHF5C z(2!89ClvM~Yd>*P)7u_tEKtg41~^4<)cfDub)?&(%vyqIVv5Sr=b~YH)LzRE-bHZ- zinz^>9k|yikaw$KyPu)cu%leq8O5Aggi3q7r>b0;pbt=nY#gFb2;mav>1M zL=XrZm^3605>!P%-cb}V(y={A6`BmS16t*vb$ux!CvbzA6Niv%+~C5*5u_mxs5hyD z4B-LEVLQOyDHPZ`DTe&U3x#NKW%3}hMgZ(f1weX~2*@>#0xwO8A&rFKqJZeF&<}9P z5@9%edY%U+7B8WAerH<(ph`I~cv@r=LLC1MrP?^pP z(k6IhzKitzSWt*%y%O(#Sxx;u(?Bw)q9-_*c=Db-*4eTRt~kb)bb%ZCH{asi=- z_*1{{XEx}}Z}s_4vfs_HsQG;#tf10{e_sN9{$P-@5Qw4+f}KMe$icv$;Q^%H8F)8I zo&yY~i?YG;V_-5}2q}N|S4A180=Pg&vB5$@;5VqUKKxfDo)-<84MU^vmoy0iA+z3X zXj?=sBmII^`8R{dMp0n-uo&{;-#?2b!Oz1uWIYInB#boZFoHwg&4NNz_)sKA#gI`s zaP6T{Dd5+49{dP|EK4G93Jm2!(4P}B*SR7xdnpXP8N~rC^W)YDxXVwM!bpQD7c(xNECxAehkA08+4t?U2wV$ep1F*Vf zutvHcEqjh?&ARxb?KtM!N7W{}(h%YICGL1boJPq_ON6wsZ3p7<}YII%U zEnH9v4LVpGJ3V4tTv-Zq@tQe`PJ}JS?v4%N?+C%ym5jc#lw~X^RfCZm^QzPPr#U*q-*SLQMUURq1W#wSCx-iHM>Yn$DXyeQ}`J}4> z`>s%vz~I3W=u@{()91P)5qk#I^TcoW6&SYBDR}d~POY6F87Syhnr@dxkyb4| za1__^WQtV$-X!i_6gnu9uD4D)Dm|yiCIlrKuwUEsipKN~6cyxm3a2U_x&bgQE@frY z2J;aXjxHv}e~z|nv3>2;_^P`0<1CXFYSwZeZC6G9hR;9S%+)q{k+|8O7927`?!zN6 zH(1<1e@&DZv5^0Z7-N3xc22!wd2biK#Ep-B;??c~5Q?4#a9dm3BJRL2Ru$S1csFio zo}t(erAF@1NIvDg3kzbTn1F2&OYZ_QQ6uBhiu;=i?$j^TO)utU z0fz&RGxOVBu~bYkhNK4L8JU;%sOh4DT%<+hVDmB>&2i(OpW%%Ej9@OgRA2Z=K7)UJ zM4Nn+{Vt1UD{^ST8ouc=#pTBGG>s#nzapcw zUa%SpgKYrFWKviqe=JDgo1i0fuyxAKa&cs*a7eMp9&k{r$>eT-Eqm)=P_{ELRfw~2 zq!hDLRR7pqpa9cEJ69^kE3UW8R-Zf*@2UN}d){|MvEYB1f`Gp%JdL$gmN;QQvt6-b zbzu$DQ@#+@8RJjDRL#X?AV~dF^wCIJ4h$R?1OyryWUI8RP+4TQ$R$1sB??Omjo(fB3tK_Aa`K)I|L%IbnVkzAv+-sZ&u|N# z!z0ab2k{ENYQ65G4R36uX=$QnV^f-(C&*-Y+7q?GRZF@?y3r3urJzRsh| z1!o=AN4R1c{f-(bJ`usimuYSmN~!i)TX*7Rq`ljv-3PzxspHY^a!<6sd?E(brObV! zN%WoNl8Y*=d0e}mPqLpdN3s>@qKoZd{ban;m+)duFhH+oeQ$baqk-&xMuI)o@LON_ zkLn}o2IE*;4OGg#^Rr_^D0DAC=e@y}ZFucOtauV#;Z%>9|DX~bFt1+4mKGe+a^QeeKn z{Cqg#SaZ2SW{qdMIe__8E&5S+dn>vc)_re;ah`-CBN>SVnwhiAlUH~*{73DDrirGo zOI}B3`Xfp)Bfmcxw&@1RgyXQ9=Z#m67x)Eq!+QAbE|Da=juXz@TVr(81z^>KB#q_8 z96XAolRrO!&jDxmm$0_@-~@TrFx8lMZja^Mk7~*q^VUWk6`-{3yy{Q6Ef4udNa-QD z5#+eDwWs5sG1lR#jK5px6e_*kTBT)wDy_qndvvVMG_fq&qy4<@>Kp=lz~s~clk8?) zg@iv1ju$(w#pyVkgM6*u{}H|!dg1$96{Pm6~G9=a)sw!0d zikmn~?Ah@%3rGvBde8xK*%3c*yP?7O$MD!6ggKo-ofh#m^LFm;m~2e4?Xq}>_6`=f z8l^9)#h5JnBA-E$BBL0c2C=J4_y%n%$)3p&?Oq`S)PUiBQ+p!q9t=)_2fQv+sd6IH zCVqa^aXP)TUZuf4UmVaGIL$voG{xYWpw%k!7?a_jc(0=1XC-pm}pYjo) zh7wG>lr_jjP6q_Z*c)+V63L{cYtkGF-%^DFkyRyD|6Yi^@kb7nLxW{lp=tV9#q^B- zJ#ux#TMKe)EHP_@LM%Zyi(t40TM#;l`XH&Cj7=z(IG-~S7Tvuw2Q+;{{NL?ObXJ<7 zK?MP+q)ZtEsDM!&7;nARG{JG*-Igc(E!jhH8EDXEZbKPpnBT@x1Wkdz0WbM}H=U>Xj|FZwzl^?T-I!1pX}?rxR)Iyp`%LJ(pa1N$-8!&p;oEyc zg2?;Kq;XaE$ebvOT%%fEKQDM}mpoV^ zeWtcoZr3Z5&vl14^*tK!q3jjiFWet?~V88;eduBWb47Kgsvc^OY4K)|s+{xV+dBGAKw z0}v_VR;gQ&Ti_a-KLoq)?{~HGn2yX$6grNH(BIzfuI#kb`K_-#?Mhj2Y}XfFN=wh!lYeUkB&7xSvQcRK4rV8C3@mEnYfWFC8NwGdy|Rs z zZ_Sdfsora*b1VWoWWGKpza6Tu`(EoW9@0X)?yl5>2p3WnwD1 z%*0fU2~`9k^J2681%u$N2+ySTZqQJYfUF=jkx&^|;z|w*vSQ|f^bT7OX(B=Ab7;?J zU8{9WM3^$~s_l}lXVB!Li$GY`O}%2BN#fS;Ax>nnftP5~E8&0&1ZIEE=opUTQo_|V z=cO52igb5SZPZhhpbTLF4KZMaR19i`rN!3I-wR28o5Ui=mq zpG)4f1s#k$+@y}6Kxim*1_d2c&(EqNYv1YBwsVi_V-QUr(R+8-1?2k#-Jd6p$UV3_ z5=P3eknuueT((aZzS1Rr=Q4UKa;lN}xWVi!_pM`G_p`8j#rGtA8b^tb4!A%F65A}J zY61HfInmmy+Ee~m0cP*~ofXGBet=;KnY(n!);Tim7Ce+L@PP6nS~*g%e1_PiR#I_h zV{9?Pj)nk}^xvd6%xJ%xqYp}|0-j*` zEb=K~?BxL~X|1KROv6%|EVm%|=ivjJ(^u-xZ;Hk3iu=!xAjy6upm34tOyE_KTA%5@ z{66%e5Jz`qR(!4SZDv@1;mb|UUOiDs_#_-<&VbS+G^XyQLiS$aUC0V zSb{%&X^~ORd9oBUB|AjUp2LQeY(E$RWsfS}w&cCD36@`R0!5E*p|=YDp}^qY^KxkF zE+mu(nW8nik5htkK^`_FxhAV6pOb0+4H}H=8U?@+fCEcKK~u2S6l}?*aZzW z*nZ&lBmVpQ;!xQye#G@U3)*+Jv>^ctrY5WmAhF|P$8%mKj0_=MdZ=-^m^rpMzMyKt zKr=I}>biiTa{bMFQ>HUrS2qSm@UsS;lGKSYxxlL2Nm0f*4?wJ;l8nLd+C2wv1nJoE zt`=G!6f{U&S`0M7`}USz9Z=4i71^&^Y9cHXuWXea5u~VSWcgQ(F=&u{A<%Qry6Q0s{3flpdXf>p*j;AY8ZPF}{9qTYgHzdIPq5-Q z9vNPrC-01_LTB5c`=}HD8xMW@czyvIFI?d=S908KyP=v}>@FT`6BSV;H}Md$(fQlz zn?&0_M#U7D$;7~DVRH~%=^s!v8A1*qnN3;ahu@m>p(bx}MOQpnxj(A?xB(9(RQOB6 zcG?tz0T<3dhwYysMTptHc0SZYUw@guPi4zA5+_dYLVo(um(^z4+9op{Zr6y%FOgg zl4R?bP;`gn@gcj24cm9*7(6_3Jej0H=%TkQf9A&jAt@^Fg*G$jzfi=rIMA1f2!O}m2mk1O=fkl));RbT>gF&7-3qPJg} z#|mcf0N5j?(|DoKdn>px!FzRyJu1VR2fyym(NML(dr!#ko#b!ArG@^CzNlxp!Yg7f zo_mIds*|pFGXC)_ zsFQXl!O+v@I%}i7e#_112zrb9?#oOwbH~F85?9yYl*D9j9;xf70lEGhfu}Cn21i~5 zRP{I&JYc^(%-CPJHG&=TofE|f-~FX2Y=dFy3st*N9d)Guw@M6Zo05~(eA_Am+``dK zveUM%+^q|8MOpLPDEFK*7${x~rNVKF*^-S9uR&_YfAo7p^J-76@swb8;PWxrR?z{E zO3D4;^);y_5U%doQ{y%V!U*|;v5E^kDNtDyTsMUhtx45YYP1dx9YdP$D(C0P5{AnC z3gh;Ve^bPOdisXxg+utf?m9_qT4oqyIcKD+R-8`;9&YrISDg0*gu~mV7HuDW$FWwZ zPkB$aaYAjCk0u8UFSnGMXYjm?qQ{)=Blp1pZU&rzF}ZPTo%X%FqW_#CTesgWuKJ)6 zLm_4lCW96Sw9eH>BI4rODReKZXpBi7YR;{~G-9vS*%Jr;8;3mhm;_iMZBCqkD&E}r9=aH@6PbZeKUQnL@4*v_kEP+zO zo?8*nBDG(C(`8$soNM1*PcV$&d`)BNx=>g1aUqF8K4cR53r{Z?yIcMetmfZ|`W#ST zHQfLa1cmH9lt#To#HlDLc4P zR%?c1F?F*gex>4omCT3^tRTm^Un|kSJ?UE@L&IY&2JtBD(E-3Zhg7=;0pBY5o>$_q^AwJ z`DM7MZxZVlQeO-1aU9<8<3A&gSPRn&QYg|>rpN+#p=UbS^apU~|DM2Hg`wKkyE^g# zLlN}OpU@e?hc&{b&~aW9e8IC*=+$bKVnyrdz6M2H=)P)2?dG7cXnZJ(XdvJ6`t+TQ zmDxcLvMPe9sW7=s#>rPSnrAVMqE1T0FdFD)jo-l#v*3ttCVs~fg|vZQHddKA##Tu+ z7voiCAQa>i)|`h{Xo2Di=R-;_*njbXOZ*B3u)f_*7TCPxA_8HYUHzX9j!-9vr3-vz zp%TwRL=}Nqf#h$O=P&!nbhjPR9uPHhBe(q5Gmq%G#H!|U2+wDi^+KV|=a&uPia49^ zAU3STZ6w4O{#sTgRv;{DYC@7*b-zV;GiQQgA&8Z+x2fj{ulB<=A3+j9guQrxi!<}T z4wDr1>H3GZMMB`hvW@Sk?_^nH;k(Rh4HIzlbCVlx9GSaiM0(H1)SMV;2hcNZWMb>? zt8bo`XS#cg<8bZ<5l=U~dyXuG?xy_vcj02hoIIB}kCG7)+4_MMd*L!>11gtza|_Ve z5r)MREVbKonv!r@?|J%h-IFxH4hJnO<%b68@?T8ueyk#2xW4pwTVoti>6*qb|I|E4 z^$@wF{PqFFUg#VY@HqzVi#qAeA;a|n(8t01@h7UE3qb?_)Shw+XQWVH-MpQ_Ioqt; zi+4XDJ}G>ArP+b-{DFz2+`2^R?!$;^xlN_BhN6;2XNEL+W538S7SJ=Gf)w&u9zAxAWROgL-@^A)wgMCw)=nT& z`U_v|XVgv6eynzvRZjx7)p@R&wER(7d{Ox2En@0YEV~(a-N)X5W~#lFMx zyG6@@a-fM|E2<$%+NW41d^@>={nqrT7)Zm*Sm>X8-c!jNiJy}pLi-9{;HmWI`tbmF z-;0B9$SaG~EeF8;f6|olaf#bM!azx-6f<@Jan%}mW&~&z@l=2 zRm@Fk1RYWI^1WR>?@c-6ezSUU?`kFvcoSrAfBZ8`_wDz%3!f->zhD7kH%baIuigYP zuUd4t;p&}$pI@`@Ln}+(2@cF_IcJ1mz21uo8Ir>=Y2KsutR%Vx_Q(%TYpBbN(e{Wk z_Nk86I2#VuXw0}wHmKa|_9({m>LI>N9Q>ud8O1~ISxn@5ySKyubyB(0#PIOWiP7yb z801r@PXoOf<-^!M9q(2TyK}_29sGQ_>~-}nz~8+chx+I!EJjC~cmtp`{GpMmUzt^D zC0WW3NeNY%>-WiMIS-O!_?$Nq>0F1UKE1UE$sQifT1aI4SV)7QP1KWi>AXpf+7*fi4- zR*>7R(gQtVlp3+$CXB|_XCP{lXWT>~BcTC}vB>t+3+tgE>q=vORmpI$0@#;++m4y~ z`U~@Rj~^5jE(Nj$#)KS4AiUvq%~RTwwNaJO$_ZbNa84=Dpzsa7`c%3hrf#Wbp!QlmeMWgYTz*%-%8|_Y|JS+ z4kLDccQ>Tig-|n0k$4uwZ@p*5nH|GeT5LPEa8y&FIsjw)o@_Wo*6s9AQCT}}yMSqi z;30W`NCR9gf&#%Vs9L+C-SS`4D$g95nmy-7f5kx&Jq8R`B7X7KtcoKQHKK(^q#fLR zP(3lw^oSB;N$?6@LfpP!C)DqcW5l#nNuze*5-LHf` zim>Pf@_;0~p7GAN%NM&pkYddgV}|t-C{5@>ZxUmN%BGAxzBibRH;7VToY=%{0FQeGiM6t;rk@ z$ozy5;{@JzG?Wqtw8~c$Mu*XH*3cN%lvyoDuB+syC(`2Gm|0U> zGjrwOZ1s!Snm@4k>U_F(y`Sf59TZ)RiwIe-t~^@)j7tFX430THk%^hkXRfnXz{PtTsGR zHICQeb&VqpLn`0YQ(HwA4|M8?BeY=d<23Pn+HKgI&OaJK1Ol-F2D4D5gJR}^%NNFi z5-0I>+0nY*X;b5^%(J=R>kNd7Twt7|mB}CjlxgaY>boPDq*|A337S zF~2fqPL{W1)oCfK6i+-1Dwa8cSk4-$oO40=3(7bp=2FcO!L7{sWqy^3hRK5=vI3SH zhG?cj(5jT0RIXcHyV}rH^f`Tcwspn!)W{q+Ils)48O}V^f^izAqQ~aJ3!Ju@&9ls3L?_NioMQe9^1Rup12y`vFM!JS+w7G~U%={+@# zf4L=HAu;6^4mUqA+RtFi^O%2XsN9E?XJ}gS%=j~K8~Uw)DhP=K+6)Wa(~T3%BG=1e zmSgV4e3p)TFNdQctY8o5X?MFKDNE`P=^sTX$-EC5UYc$iA%ScvDEY&>50XE#r}}h2 z+}WX%TcKi6D!>|1d>6y=>ghtgE0B=fr$VjJhie4;1;){LC`Wxw2b=2g@&>Bw1m=oti>8fkZ=;=zn zeP}-treWNp`qoPD>6o$TnxJbM32PREIl!MNO`8&K^AMPw+2)MVZp6`UhAeZf-!=MMv13&xhpiEW#^^u8zh( zQCK?Mpof(!YtpvhMXa5nxjw-QhT*s31jTki&Y#cFJK&Hf}YYHa+3r73A~6^4)%Ni<+{NVMZ z?n~*ys!ssYHW+>AidkDciGL7Mt`KV0WR9brr0cS+r4G~BqzlckgasgpsvKz6BuJ`J z(Jpij+k@t3#EwhPkP!_b|B|^!bvV58En{Hn?LK&?8^Yzez5Z5x)Py({gv2M7s1Fhh zDu&ByykRQvZJ(NhDQ_WD%bEP!$vn}fr{YsR`)SFWSfnWeY750uAd(-}vNkEM zWrOlst8ya7RcEQDtMJC{sp<=%5r5eBaVFj}l2$Pa!#{k`^_T+Wy}^(xXX$DD*8_-1 z3C`yPg4k0RAU4Y}w`N5!t&7N!!BJxtk*z_)N}=UFsd8j2t=2YlK>rqQ>L&WG)BP1} ziji&75nUYnCv4a2w5VApC2&dftS0gKY3Z4=Gn%EcM12Saz+q+W;hr*T55FVLH=5yNIyflf$3Hso z#F6Qdm*g^>8zy*krZOBf@|yIfFdQXsNWaJ3CK$5Idh#IY$+zeg8N<7$-1TvzxEa*T zysPW+*P}4?_M_HGD!3BUV77DOI`5_|m_N9msl zAPEjFCCI&2#(8uoQ6dRZ9vq=6O238)ubQlCn`pDFo~^5KT}Dtg7P@H*)Jl5LOBiXVP0{h5C}3)yPvRuiI=yTMua- zMPL&AT2+^ADe2wa)|8h0I8fyf5WC0*eqH*q$^_dgWff&dTO*-l!k9wyiuwAE(gvTL zw7O^7NO8cIZ~f;7Ei(Ia6ZT=FBGm0u|9mGEXIr}*8-n_0W7Jl#JTJ=E(qs^=pB+7d z9+h_{Z8mB)c^lkv0-sfd&zTiaohr{;C8ujF@6AVTT{o4IspT}1x|WVgOm+zvD**h1iO% zI+f+d6>gSiw6&#+!ZpJfj?pI7yiJo?V*cbujeK`ruHrm#xe&V;F)#KsBqIly>#;*; zd(z!EfdpK5B*o0xM>-6s#jG6~4XVn&K zO5p%Vb%OR-8^>~Vn+mdy0gm4>A)VPcJCpR=HaiPWuw@PoTkU^nGx86-jtfkI<{iiw zhW+p?EN8LCASGdb=qzlTaZLzkE8Xt|`bk>#vVC?>78`+Ac zL18T_Wd6>VPq1d6M4t8)AF1H9muWv1wl9o{?iIF=_P`FdFToA=ze7#-zw5sJE-J*y zOm)Z$-5+ZY4Vt}7eM{vo;3Ft*3M|Ndy?Ka8_BAPlh;3czP7Ov#?au8(bkOHRb+F;C zGGzSD4x-=WvtJ^FdAT0I$sWE66N>4uAfgaSJn}*fY}iM7EeWBzqk;g&jG{W=nPF5M zG{>UWv(wdbSe%yCeyI!pN{~pgMuqmUv)yFXlfE$8IOspPh0Px+9auU)^RSYaQnFPO z<=oJy98j>kEn5$rQ7it1|dcQLWVUW+r!!eb$+s-t^N?8D=ehH2Wo#6U5iM z7FceDV~sFA%#eJubLbfVMX2rcn9GhOX{wAv6jhDUf;{kX;VD%P33YL#PMxu^SW(*3 zLfzBwv(IxplD-Eks(*4w^|~Chg13Id_tK3HgIgnYK4kv> zBROO$J2?R`*jqvnzF*ijF(eSLiFIs4U~65wWM(;H{H46L6f-!d^eW4Yco~TQA=g$P zRv-&MmT$`=k$RPLCGjn{Oyr&Ki1(ueJoOXIQ`{iFUr0faXE)S1VMkUv;Y&$;PPx2Z z41c-2UJeJlfL{y`x`1CQiFFlF)7|mohK?XDz4`;VcGijHg=hvr|3ORr{d8t`dMUkeKF9QmM$D97AltnhQo)$Uu@p#NGAPM*PFXNqiEWThxvkfg%rq$ zxq}{{X4WSSmkVhn?*6ey~wh{-wm96S;S``{P8iQitVR6IW=0x%J36Ti+IS-g}F zstkPaBXvP3ic83sT7HIWH9VyEZM%9T4S@n}9QG(QJti^4_!CcjR z-th`YDIjl3+Pq*NTp0ongo|D%DCA{Ei81LijiO10Nlb z8If5gF%iRfF%ixcljn~UammkLO*E6{hcWm-8$JY0W-NyyNh!A=58xts1z%kutY#FN zbR=e|gHA5hTa;^qz+UCKk{U5PD^UwpaY0&Ls~ho?-;V!0PD4_Pw(S9dlAiKm?@9*a zmez+|^s|bjMy0*3jqv~Rk35XrT__%ac;rekNchR@{3DiPgnXnxa;q_{95DIrN1>p# z9{EEh5~`s`%?{IWh3qkntUjz$E2Puri;+ltI+%)$8>#P0Fnq%gIxHYJG=lhEUEXWy z4eYdTfxQ9$x3W~bzds%T3Ic)y`5#RBuY&00YQ<#bYGQ7m!dSryfUY@xh*m9OnomcP zgR6+G8mm5mE`#5ePb*`#F>E-j0>=ng+0yLU-sj;$Q{I-IHgZ)(3d?M6o~HqGex8;u z^Ls@7AoRu?!uUQomZ<2K7T(m$JOmItb9mCmBIBf?Dt})S=s0mX2AOp?Pj5R<*lRNq z=rqrV7`?XBsW`)d+eg|uX(&250DQ)Z*pPfD+y!~8}hbzLmO#gjfJ z|A=2#Iv({ach#E4L+|_d!(s`yF>ICpCoz6q!zR_^M0_3I!uW2Mn z_H3`2v;#+HK;tCRa5;QE@8k>?EPTsG@If-hoAwz9Cb_W%wD9dB_YVfyh0TS+Wh!c) zrSyxMJerg-&61N1(e!KlMjjXz7YHqdxWf<_G#WI>WJ<@w^aP5C^B)9R9TAtT{HEBq z-hOHuSe_|>$>BHlFBuE@CA_pkET)iFcj1=SRxz^>S63+BqErTv5**_XasQl?ev$85 zbu5~(6N0uFId-m4jgDIE2>WItlKFS!{CrYyN7ClOpN$GSsbeg(LdgX@5$Od2l23AY zDdnifmkZh`FwgiUSK*?HkgW3ikcF10b1U+kctu2jz+2-CZ~TKH?Kj4z)7d7K^&(jp z^7TX4;t2;vh|{uAg!BUr9?>8{HSS&QPb{*nrjq>pjBak0?KFJUz2OxcmaOvtUzkTCeP^4 zXYgcN>*Pjt?XdpCcWb&CvRJxpXC@eJ|ZpW8>LhB(mYtr1LVe^~PZ=S||taHUSz%9ka!E0!SxBgb6wIALB8 za_?Fggp!xoZTveGx4hfOK6#PuFqZVI)N%H)G$j+tW6-}Q2DPaz-OauzSZxN#I%%*K6ifhm$4fs z0%o4YU$2Oi=!KKDF6H0Vw^yyG(eaim>dhJ_hYQ|I5XPr^7%>r7tMX1vfndG6+9_(W z2F-bs%gC3_ndO|3#hP-gOD3c3*_r4_BfPVBo@|84dsQNdJ9r_dfBtN5+;fz!^Btwz z=-G+E068`miU^yzgoxLOf60%^31TEJW$`N_O*Kr>TqLZX`PC+ET0$fZehU&_NKf55 z+%qquM+U4k*R(mH0Qca`c?jf`r1I=tW5k4*8g6-b)8Oi#K!^jyqL3Ih{ zUhM-*zuXW~y4Nqpy`lTAHSxMgp_6?OL&H|H;v%nttK)^K3OhMh%qZ;dT)p7nVOhI5 zCH^IIdN$8QiR+Dn^f+x&suBL?LuH=LCtk&+i01BE<>X`9vVNw?wfVq_zg;|Q9D~ZV z1PmNU#dg3pFt<@-f9PPvFZ&iR@!Tg5orhd)gQLK<3^uOGx{rwkBqRzL6mhra{ka?W z@2KV}ohjt2T^}c9-mY!>!M2crn7!W-UNaWc+ogCkpHQ6zYebg%*%CEGcI{NK@Pvfo72pN>zcr_MiJdX+H*mjttOiF?hgpEIs5Yq)7+roB)SXo=o zkiKz3z8toLA-9RsQ(d<{dWf>zh7n8^cMK&MKp=grGq#PND5Gnb0v}GGxINBp29O5N zXqye0s8QNpvVLjNWf%Wu&A$p=Y>}bl6Sj?#Ahkrlw+f=hRFcRWD+rYli*8d!AIEn0 zz{B0P z04;LV;x?w!2Gygvl7M8MTL>Rl253x}##U(dSZ2)Ap_%TmGuPGB1|%4m3PsBK|=><47lPlNo4| z@Ovi9UqD4dh%NOuX5K_OF&5sdt*uq96ER&ot46{*Dc|WI<8?=T^PJJ%9kS;<6lc zIAk;yi|y|*f9^8X+qadk$6u!Oa^37~6`J%I^iH|3R&*Y(Np(*f$?34gYBsA>gSRpt z1|g0HwLjzR4H!c;hw|Rn*arT6^nueh^n%MRgRA}K?ip##ayvL=Bz!;QWt*xE{l_j$ zf9+P!m8yG6FKWPMiOeswKmwn6UnUF7PadIP(g5oj-;+V8pg0ld8Q7CZC}EKl!sawo z(>>1WM)O%L32#F-rA-gG3q;LMB7hsE1HM(FLSNNA)tvc@f5VXtl`0n013 z5C}#yhq$h-fss+L>g>KrAaW;-A17VZ;E%2cv5&^V@z;*Q#5<8tBH(2FC#f5jA}+AH z-*A5fDKw~r`++XRFuRWM1-WN+$8|1IZh%GW6S^^~;H;uzs?tbjNfEM`Ngy8YkW*y6 z5Qi8VW?qF%su_Z$e7ai!skU(s|!=2Y_ zHzbf)VD7sm6E1z72Mk~~86N{F1N*m+NbFxlgF=T^nVh-0W-fU+Smii7p*)(wk1Zc& zp@>UZ2wyPYEBmL$sy91~;kuP-d3b}`UiD>zj%ah&ycN{C=nhaFZcE}b!Nv_fofub* zwbl!qWC5ye;9ikeyHmxLUGpPkBHin)i`s({JWh16Ap!T;0Olr`$QcW<9}jY!v`8x} z2T~|xnI&8VsxlwrTIh7Qxy(YGSgSfAc-M+&)yd$EH$CXT-}mOC{0FItX~o*i-Q-Bc z#kB~S6fwo;VYsQj+U2bQ(J;Ma=nA7v%-)0^)04J+EN6;) zW<7bmN!T&$FEJL=@Lvq32>O9mceig4p+@Kf^HT<~tPrmFyK}jNz`hV{iIB|W5_TEu zuj)YnGuC?~AJqboNr|uHK?u~D_|xyQQFU*G@P+pc?Cb;5H}tugW~!9b4p9`t2DGlq znPia5X+Dikt?WpiD0uWQ?r(SFT0qQ#u_uPu0^79sJIv)0Yor4E#m9Z2H-yf?R&^8AkpBUOiNt@l^s= zR;P!+6%F@b8dO1td+28z6Omnqm6Z-Vew*)%b3hLihxy8!HikIuMwn_9CH1NyGApD; zJU+S`v5jlmK{h%4n`!P?8mp>Z2fQF+Pr*l~iLQG#239@#xs427Ws%+7M-gc}W0`Y$ zgxl`E3o%~OMz|ejMP2XxZ^u(xehd4Zuz%A)m^40EbZrd5sC#{})=t8)tm^g(c>rVR zi7$(2(~jAO*{=dkeb%o@>u2ubTB*#eQ69SQ{i>@u_sJ~&q)(+=g}0*-O|Q{7-;zWn z-ZNGurS;EwiUaMMyf)WPJB@;@uHLoNo=95#eS9cvm#SYixWu_Zb~6GqH1dV}@I{Kzp5pt{$&aZna3j9;)CoJ?=7#CkvZ>)9Fdio>?(~7E209Fs{6kb9+Z;ho zqMC{W8BhDqGdLX)7d1urH)prYCO?ce3#Fz!ezX}H$4u!0A9XsRx@nt;ZKv@iSL-5i zv(ovo_}%roIYJMqoB{h&akx`-AvZptGJR_>$2G>|ol8cE)E%PsGJ=XqoBrrj-=v=s zvjqjza!)eVzpf3ZeH2WstkIq`MJ1K!9)VKw&uL&2ZhUf_AV<~KCHKSf`lwYeSelaQ zHk|Ng`oqLg&o%c(RUa7&9F>FI;U{?_Br20;f6D#iv4~UNqdXS@a&Dm{brUCGATgY8 ziG}R3TeYX&G|CRWY%FP}qCRLMm8~<%Lztq@PCl|k>eyByX*AcmV>uxi9y%;MYN<)d zG$X5QW|V1l8VH&6X&I+jW(Z=j!lbfQoByVnhmuUik0~3oof)VzTaD;aT{XhYlNPnq zKTZfYXPwt`S|-QpVI){5tRMu4K`OozlP2;vRX&OH{PlFLh^m1pBG!Q81aZf3Yz*P5q@DK(ffJW- zf<`^}Jo@{kzpB^L)QsMdBZ7fuxN)_#a@DCKFrht=Qox!y;_>NEto|Q>{}xKph3x>P zj#lr4r6H6Gtg>FYz#2}*-rMVlyy*;oQ;b6VxD2E>(UH9T3d=zH+Xt^NGj*?d(zedT@%#7~$qMLGZ&O?v15|3wTC> zwg0b>vpF4Mtr+XqY4y(TyI^$5aM$5j(TVZnmU!0ShUU)S_W0x>Xx#vW;*Hwd^Ju!ndoVP_~+1s@&aZ53S*!>&C0AD^Y<0y^*?SytZHfGvAr-MahflC_-i@3djyv zJ0P6ZgGV`A5&lHcg*65fbV|Ytul9WKPjJDuH8g!OgN3esE5~Kl@EsOD9viIUpN;Zl zpa8-TR>vzp5&{S;Bu^s+qy^?zfhZ0+8N45Q1(JU3v@2&M@r0J~2`IgykLxz>+uytj z<4*A*{vnuv?aCdj0^!k=)Y+nXVO&s!)-h8!R4)UDlnshlQP$gNeq?3oj=`>Pm~5i* z2FXk+8NyCNRm{EGO^NzBsj&Kje^_-s$l{@kIIBv4^dxbgwzQA6Q^YP&@L0Ptayqrd z>zc_UoJpQS<2)nyf?!!gGP7BWBZQi!QQ}}5qw@wJVN}*IXo)rSkZ86hrewUUoSxQ6 z#X{AVBYT5}zH(O8qPPI6jTBo@-cWJ)WMfZ$V^is!dm%R+Sy{fOMiSg{sLd&Hb~rSk-Q7<#|sYkEn(bV z?20RZgzS~M1_JR_dV znYVWKif+r<@kN-Dx=k`QN!M&Ca8|ENG|n`C313oR91cZh@R4Zm_omDx-PO2=-YWc? zphmKmviTL8`}3dAn|B6c@Ha~48#Q`BkR=$J(xTgvILz4~?|LT>~_RbyZ6uk48O0Ma(N#Vn5<(WSYo8AI5*6V8DXr%yoV z3UTM$`1ZfVo<#4gr}<9fzjFd|m@Sjn$HafR#OPbbjO(jtq9xLi)x^uI@)s!x#G8O+(zB+LG`#N{53a<@S=)D6~Qgd-Su+*+JHE_1QB%$ zaT_PPT5wkR;t2NAbo_23)FVI@WC7JO_f7q~l4K|=f&BB^y;xZXd= zK+b$9=0?`UMxxg{l;^5wM)=%@VX_UP;@#*NYz2MXk?8(j=3RRxDPjMvfes!l28PE!&q{rR&@lPi77`9|Hiy%0y!G(Q^#)sbw32u=Z9|%JkWTc=j@~~zsUzy zxE61LS%aN;Q}G!{_Ig-0=IM+V2DO*wHLqIH?8}-4#LcWyf#?B#8QotfB}iLJgs8mm zeGyLhrs`Enjo~40NTr|bqRDL#7elcSjy|EkTRk!9ujhR$P->=5FmsAs^Jw@`g$z+A zwR$sH^Zh!$Tc3an9}FV@BOUb*14TbCQguTQ8b7SO(=B1UCm$pA*pOXH^Ln)Pb+!Cg z@WO{Jvg`Xe)imHon|?|><`yD&wt_2X$(RM*0A~w{sec#Yaz>^fSu00d)>e5L(?1Si z9Z(^tR+ZehuMY|}pLfGr*-?R@o3=N+>PRh3(JT*s>;xrcBz`$2oqr-EX!FK?ZBHt7 zMdzp!?q~_cxLK$!ETKBsBl&$2iBb-Qh5UM;hk+pNk3`!{*I@9GAezm7LHJnDJ_ z_nI^8suF3q1{Wgh^=7Q6*S8tr>f*u>@&!jJ$i?lRNx0&E&LSwpD5+kGH7IcJ6=0L! zpx}12eH@odwP8kjXoqmj0`U{dM=>Shn?d#wz%1({+KQtcC(!eAOJzUm>k4|9RoZUa z>wQ6jspPLftpAZF1R;~Jov@Q`Z~$@1_KK*&NTZeo4}TA2Wb?uNK+vrb_ljX)NT8+E z<fcjKXHhNXJx<`?LF+{3^V9ul`D#TA=P;t zBX2EgHPb0$swdWvK{H}~UM+X+XRYgIeGg*yJj}Dh_5BLMGoDV286B6KAH^6^VCtfR$Mb2#MY~0*5?_#NVNgh4)Um|y!ii^$<)?H5no=Gaj2F1nc&M)KJ9Q)i( zuO!tm`DDwOe?zTe#nKXl9aq2g`p3?m>@D~G)zPn0e{TJ^T+_3f?FguXFf+>N&k*U{ zdjocC!783%c}RVQFf(2Ym}o(w|~|;GE9?GiCQpJv3$af!55V5bAvAOGPNI8w%X) z9cf&AkN{^9!xSzk^c&!Wq!)pEiY?JLM1kQ`37;V-^z9))#{h=8ALM##kA=C9esNXg z;QEI40E@;yrSSV$fvW(^Jqj9azGrR(b2}wE<@c#%bH(sE2g#2O1N^H%gn&A!{IDI{p)t!^KN;#_~gCKTb#^A^(lPq zllT07al+qoca3QXQ4ju|bQ>8fDe5IBjJ{2CU=I__RVFmB@p|Zt4Y-Ed%VpUv*@fG# z`Hi7Sl@Z~}vR!0EJhO~);_K~2h8&@1_2b3G}_D&quQA-`5gcxWrg=m9o-aH~1q|DX#@p5y`Z85X0ZS8Qf zu+6b1VVx?v!NVcQLOtWHD%f0N)AMfUHPe&w_UaCND6nR?wN(2{O`^F$$C(4+zbEMv zgu0lR?Cc67I(uU?dpqrnrP*&tCW-c-q~V__#F9b2Lw3?21Nc*5xX3LtxwjhO`5BEX zihTMxST3XXxhwj954rytOZbAtQy8>6;dfptp1R0jW-u20WgXY{OIL@ds4c&-8E?y0 z@M0=AxXj49)!5=pWh=uvG3UT%V%t%REo&tuv}WGrW`o!xcSRMvQ^F`nfmJ?>{(`yD z$I#eCtqr0@8L+~>XPqHnUuY;Nv!X=UMnOnyk%&NZJz4!UPGNlGOCR&5Yj0X zlvPm0OF`wCNnvYJ*HJadPhPR#d7IF$SCNTh@V2xU7m{!^LP1Y|cX^D#I5x;cYTWQ{ zaCmk#EM2L=?Prtyt?SHuq9+Jf^TXXK7}0O^2K-KFiIeWpvO&?&&m|$h8N;GR)$Zfy z6G-B)&St-i49KdqBu$lvt^%a+rbb`y`_f(Fu;=qu6QI`({!_>MW2=rQ)&>*j&L3Pv zQ+x1EMs`WPj6h#k^#W|5oH#0tE@&ogjt4!Rr*unt{KAa-uV?5mRDw~tk*Q*z5@Ey| z9Uy4V@kmUXUQBfwADYkncw!C)`GP6;LXLXw*GcYOzes1cFd(~GNK)p9XOo#cP=LFx z+Na_iYyZg(`VG;ie#>j{Ng!FFTr!zu6^>3pm>=d(s=XO6U025G$>;w|{6lz{@RkxC za4d&4s*DX#zSaDYAK6j=0R2v>ubYjH1c?30VY!GJgZ7_@@)9U3i)9v7l#H#eqvmu8 zNiq)8EI3qKSyAx)P&rvlp7i75GR`c^d9E7TxE9t@)2>EYdFgdD-wO7za8DVH`t%KJ>*9&J-0?X7x+1*2q4m8^~YdSvTJp& z7AD)OZ??%$;vHc&0Q{1d|6RR#=X#xUv3Ih`FovnkRwtsvG$zxBYx-W=1JNg{>6b@t zc>($y6Qs2lg8E>C(<2>-D-^E7Az`|cz`HI^U4~W z$gIgLo=wD{%r5y9Zf!hyolx%ZM-?5N21Aj~n3fPL5@{?~*4%O8@_AMwTDJ76S<`jK0`TE^Ai;#Sz^)Bk2niWea7&kP;JYgONqJ zwAn&qh`5<`n3HPhoEoLXJP~itJWnR2XBNem}!h_>k79n<;;qqkZHctAjBxV zvxn#gorxx6(ibvcm?e@s{#=kan*)jM%G~$xw3f(sChjSsIoJkO=|+?b;;=vD)u@7; z2`0g?LuX$)($g;A)i5mDcc_zb;FGK~R799;+1Usak;a9TbTyY~B_LEbz3^NROhvvw zS($vXPa~_yZK94Z?x!YVs%LcBHaoC~$zVcWX>^v4pr>yj77JBw@)7 zZ72MIo|n5rjTaknd4$<#;0xqTGN_h^TBx#08^7Y5XAmkQ9rCX(9|zrsBLUmHDaE$y zMIgu6W{aVZIB|Ne2WUCu4!2HBos*-#DM`Ylu-LX#1!rr!6J){wPTN`XDM)l1T@W@R z#zCJSMszLfLjrgih0YE-KNO&^LFA=?nz=B8G{06A)#>e{2Dd<0M(1PM&Q zgFQP}+lY6c6O#vdll}Wg2V8q^G@=mT+)p*}4_jRz_O}odvHX}ZQVK!}3BOpi(Ctd#xfLv=@mu|rj=&6Ow_W%^SmR+wllV%TzFP2QTniJjot z;e*arY@u2e>iB;B)yhHf(V-!CW;G6CLX%VW^osOz4yWUaIr7R#IzuiLJiaxRRZdSU zWT3BQ=*}Vmdom^6ma_y-G=r%$6xSNbG@^ITrT1YK=Cht^%Mv5);ymSO+yh$uifc94 zOCRMYaG*I~I6NoWqYwVx`v&=MEwyAG155bhS;9{)g5`%hq@kIn9iNhKIRXJnz-hvO zn?(jAL$~lvKEqc6Q1jQ*D=8seN1_+dH-jVB2a2S|puq@B-|V#N{}Wj!-w^i$&mYkI zBNifK@=GdY#x*vrk!>KpL&b%*|kNP~=xbU{i~O$3$%#RTiY z1nQY9ocjuPx*RN?IUW-40Hr%4g=mj>lr)|i;@7JTJy;cF&cjs~GDh2uu}bT59R>r2 zGgk|PaCy9QGg3;1TDV`mIS%Wn6-jd{;_KEs+pWmJN$FF^h28f4F!7^s*(XWhGYuov za?U+b(Zm$t!r?;nCs@Vm{-a}5td&yv@hsWE&YA+vfW2VEfMyrITVseOJXe9K18ULq z#bu^gZWQlYdz-gf4Pru`stUjMXxrd567t5PkT9AX{AILHc8ZK#AvK*6U13{kIO^~{ zKxKoDNh80)9$MBcwnqnU8# z*_Q;M2+F|G{_W1Z*YvwECg7C*;h$wdpOt2FWr(aB$%yrQf z|E0cFS%dcJ8r}IiUbh_qKG{Da{PhErTA-C^z#GCiR&6pT?jwE=(I%TBnBbpB=&Kvc zrLG(JyppeLD_$T&#KcnKv8VZVPax}0pzTMcjMm;SQSrq*ag8}ER3kg&8awDqx-cN! zhH@}_N=Y>eC)h19;cAD)331>EX6sIB+|wUfLN~3SPe635W7%bwe1XG{V~rf8@FfWZ{m@52|3Pofc69r?F&*EVgkAC zjV4C8U5iF`zh#Lr{(Ct9GA-Tq0QuhwSb{B9lpIGa4`?nY63_$-qIC+4CK(aCAtpnY zPmu63Zfv&X?zzgmEOKzFF_?8(NCGv@x|o@o`LeL^ba=mCZ6gG- zaNY=vR|d6?bx&riEwUntk_J_+ca!^bi%OdJBUWLL}21mQ`D6cDUkjBN;Ro}aR1uPmoc06QDgnu9R9_?_TfVo2ApP?pW04Lmp7lP!sFtP;hPL&&W~uE zT!+0{@-^&72`-y`O&B$(+TY$5Cc5dFY6%SBkD6Kr3(R?xE(_^%`lZmrja0VL(>{qx zJ`M|I&!$u|#lSV%wC9O)!r^mb9h;un?zh}*M`+m}PLy z0M=Qj2>RGh@8bm3!{=LZ`7e55oCDwpdD!0oRvA`=U$&k0&S&>W%jY5ZhIcHUZI_}3 zfh;f&dZlOZC6YpqXwv#~YWayJ5zUgt)@+t?t&r1n0m3HfYVbX5Pb9*;`9(1`gGRVx ziT1zG;`tOnf{N>71Ip$IMNX_V40QHQ0Wqm0xN9*`qPEOzjX4NdAzYX&1d9mK@tdAx zt1-uxWNP91`3OL?!1Z%(_Q0;hO429N3sSv-vcx{j)$2-2`1{PkO)*tOy;axGlcaNwh*#k5!mEI7~}1|``ba-Ed9bRz>PK=AnGC>XLk;OGzUHvKae=2)8kcAn zqD5P zg+r#?z1fb7^``gBGXAR1`+UOyP^O3o76sE=FnC#(@cOaDWMX4&35@i?_TVZ!!suz3 z(MqK*3Q@p_dps`j=3zVe0f#^mbD(>CcPteL7Yv>!_FJxFtdC!~F`eUjs@$+fTa}MQ z6;395m4@o{Ww-IUr+QcI2KRyihumPtfRpQj;io&iX7^TMdiGzlgq3Rh+`SYeJ2o5h zt=_V+XVqM+jSIP4>o6^dxRJr*E z_@<ROYUn$j+Py7Vo7Z`2v)jQ8x}t9-Hu-MVU;3_qsr@tXHWxB zEv*1oN&kxgy?Bzr8QgK&ZzPMYO<2BMui0$E%DL)FxSrJpK!*e(qRk zHA7}HM9+E?O&sGRfooT1krdvZ4BI zRq^vj5AoW2;(pmF)+RhUhziagq|yd`%b05iW^pn_(^FR*5(m%8bVBN9L+>m2-y_Ey#Z zX*r|ud%ia)wW&--M3Y3STM1)$F_(sx%gbdg{VEQ9F2FzE^+=y`hyo0M|A}GDcro&O zBE-Hun7M)o5x3nJJ(=0$e%|y|uoMJ*-yr{?;DD`HrZ$?QuZFdZJD zA?_>jL$^m|nOxQvX+?G;H!dsz_`Ukg?Iin1UU=tFw*|-Fc!X&j=TJ1w)1spsk2Jbw1YCiQ@xb&JZw=;avUwkZ? zD_BZJPH_+3ic;hoS#bF$ga^Hf8ZaxL@VF<}GS1#_TnqvjJjDjd(RXYFG}ivzp+s6m z+ekd^4xm6#oj>X9S>sK}Ef>hC;IQBP)lXXDuoQQ{lI<>XWH=Xk+j{;=f`fId3&^rS zqMrRcT3e~#wJ)V<4hgy%E1QtY#^ppCOv?sdtZ|Q$^D*6j19AHuy9K=^)`P5%e?Gp8=J<@_J7J~ z%FL*+LwZ2><`EC-?Jpo?wxzF-)1G#~VRbHJslA)o$HvnebU)6tlc(T0+Fvle0+3#Q z2VS$_#k8a)hq#bt_5X+IdRw`{4nz~Hr>$6hF0tW zzFyQx+u9Y4XXdaxC}Z3w*nb(x??Po}?k9RdfeZx1|3AQ+IlxPvrNswSH;dO`&@#Y{ zCmi8^EQsZ{U!Y$AS#pS*B-ah><-riyZ zMSh=HhJpq`l=b=p$#QQgp=IQJq`$R9YmGB|YV9+mp=XiJF2SJuJ+ew2Jd4>z=kbHm z7$y~3$?WM97j*Zji%JG@@Rp{;(v8nKty_yWVkj33sga(@q38fS@D+-pYv{Ll^*~XS zB+NkxQWJQZuI|vYs4aCOjG$myG5>gg^VRWJRIFPY8*Q67s#}WnNtYMQxfr%mpK+vB zT^Bi+4fu|HO1^U!rp#oGJaLOpuv{SFT+|C9FvF3Z9C1V0EiD=2XiHsvcF~I}gzfbg> zFU$=L((LpnCRq%0ytQX0VQL4horofyb*y=74IQ0~s_vb#xbe~SqEfpr&@!^Ct~@qY zTAicrmt|oXhRtCLtu*F>OJLFp)calZ3U?){kt1_;a^+B}|KhBMlrtdKW~;c>z3Hpe z=YvMMZ++A?)i8N62=HmgUao6>J#s@i1xtJdi28@JRR`-weu z)8z2y@!ZE+!{b8plsL6paRkFoEuibD*Fu!R^-tC6F>TShp z!{iWj`n!XjR=f$FFB@jWl)>L`OGDexy5l_2x?=!aFbKQ+;McBXe>)ecSAXM7HhL@} zZ>^L~e-H^M-4N@3BMammy7%d!`3)d^qvcD#=n2tl@WCML)&D86TtfxdDbt=#jyy&S zWS=glhhm#4E(NzKTdgdIKdG!fSIaxIZ%&)0f_Rm2CP#fbULkR04#nR(W}V5`(G_?Q zGh5kh>|dEEjfg$k{>xm2>Mi)$P~Bhj3$`9WfmTfr4cP0@{^A028@<{+6!6WNUN^M$&ohl2(X zUr6QySMn)|%oE&bbpbUnKSG*NC@+Z=HA89iYWzzQw+5CN*o1Lnh(IlRm07Vn2&VxN zGxAf5WP4~iv}xcZ*4BxBH&;t_FIQ_7<$CCEnh)g)r`R)oJHy-+ebRsy|2*@UZ^JGY z&0AH<9B0O|AF1ZCIarnAK#5sxv&1ad6l(KTsY}drq&vpt4eVY~f9k z%=zTMl3kgiwMUhR=FG?~nqK!^)nY`IhB5PuhAq*c3@-OzQUf2{po?1o3o}xQ zLVqdO*Fv!sf?Z-sfJyri!SfBQ4mXhc73v)&l{tmgcM?x9+{j{J0Y};rDz=?|jo=S? zID_8X#-gV}Bj6{N!HxA!N+aX{P95+lyQDApc=V`l_%R-bvPPWd`vKo~>vRNVITL%i z+5J_Q#$iw`SG#?Hg$BNpC5=V^-aglHv?CJgj;b2q5C6VoY-E1D>_@EfEo@Ixz@YOj zZvYBlSJQdmUNgn7G3Kr{dtWd5;IM;WLTzRiH3)H|9@N%ZH31wJDL(g*a&?+gnPD%j zPuUeE+LNmfMa`-WV(+h*9Q8Vpq-mV|y{IHHf+F-9Z61idfCk5r*(QcAT3l@JZ!5EE z*l3B7ON05F3PR6>Sd{ALm+dHuS$N~2-QS58b0a|$~>Q@t!A(pN{tO#PaeD^pol7>>M!w{eaSi!UN5VMCY!K!Pc^o2^s8vJjI=S2-K50VfTY z`F(u>pc%tS@n9}INn@1KG*s?7k}#ah>#<4E&M5~O=5smpvo2zIZCK%kFL$yG`T*5h zpG{Wm*fNofMjO{vcKz^aGE;P@T$H0xE#4TwcOd81_ui&K3-V&+d3&%W3H3#^z%>&la0t4 zEpxmvwRvhXo#TQI8O73!?UKrkEdWoyY)pFwoMCmvqzx5^T;d6H(Id}8ioxHc2t+Gg zM4dwK{{kkEY2()rQ+b$nAh%qu@sEuHn@M@(2%>nF^QLQtnMZF*>!O~YsNZ#wUzJqS zSq2ZC^{^h)%g8yWN7Mc$&69GlR$@bhCupQ^K)B_wF#Tc%MW@4RkCcVDG6L|$i-*5{ zr9~RQk$swH@Oy(|Q~0!n7@uPeLgVDtZ+?O=d|ErMm%^w{Un(B9AuEgfoX_Znvz=N&OA0^*Vi~$8oU(mAfh0j*F~24<R%--nGX-W@_~_yu9Cizi-AT0v z;fC`m*O?_*LC`~1T;y2HkKnY%VCV1Z1LzBsXhgX$|EvF{1|};|`Ge@8A4LCu)!Pm` zIZ?6memhwThu#EvF}3`b>|Y`39w%yaar6QraB&((+|gt*4*DM#85hb}#T*lj=c<}d zY{i(VURXRdcrp!|94%FOq^X9ma=&|c=pI}4W^F3nhS^veo-*SScpdvx@O(XtVu#c8 z-*dt#6wxJL>yD00f>Q*&_umPp=98`6yl{y3{WnIhw=kk+%N5Fv(r0T{>{X>+BUSKs za4C2poHTk`3r~Pt?xzU^%wC=K1A^fe_YIC=n~<(r>GrTcbgn&_r~0b$ zU#&JtmpbF!h~A zBo{Br;u;<^9N*DZCKY6{j&)^aT7PJt?SN zkA_#*&bEN2?Gg`{T016&q=MNJ3Bm;=h$BDHij*9$yHMl;VjQ4B>_(YTsKu-WN{@cG zCv;+e7tB4yFYP()=q5Tfj$pD$<48a2=y`0NSz{rbxyGz;vPtIRUHmCcFT#{7^)?}yN94Lhu@wCXV z-}B7kkz6&bWG+SeF>C*;>6JmgJJt0A(#ju@a{edbZI6!p|GaZ|*ddb7xg^2AN6HQ3 znmD-QK;RE8L^H~Ofq;rYfq=OF=U@GQ55DmVgTnaZH3{$q1H%`NJd0QTe7hAK2#EDR z$Apd{l0W&807nzfDLP+f!us&;(~IyL(TDQ(IWIEOhpGcbns0) zY-nLru*~TL_Itc_d$r-daL@!RSdEh#AEa%3SF6=604}%BzaXNDbCFwr%BBndb={&2 zs`YNwZK-HkD$xor8)IpC_*-S8ZB zWH7qX0Ga{x$4?QO-Na{B?XwBdA;%5jkZ_E%YM!0}=Igjei`7Wx#K%}JmQkjs2d$T4 z%C&rgvU{l?++FhxPX6?3(Do-CwBbyf;Y_3=aC$-`LKq<;d~8gCaLkOUaCAnA#)p8- zGTvHEcM;HE^=7*~BUo76hW#1gqi`jzw-#d`fI-8pO^=J0l(RA0LSMwQ+7t2jN2+6E zydKvB`tSmTWcuzh+F4xq(K?hZ1qAz2jp?O@TL&>Fg;_>a6-MiHbox?uYWtAi zF*1K4tf6%vr$ou7&iuPhW!@2adz*Rkm#=lV<@PJdr7^)n1pl9dqz4YQ38 zTJ5)b>6P?BnBUb%5K>TFiN^$rS2@XLK{9##s%`*}8x*S|#&MVkkJLv<=9V&3X%DO5uuwa5kUS-|Kt<6JHkbgBIPyco zdBoQXR$whKq)AR2Vg11xRM@U9Y9WsJWf<7Cg>=?tON*==1aaM*c&h;r3uE z5y5~7@BIT?wW;x=#BlEp`IV*m-vvVndTsi=KmM1yFnx%Y{YzaqQTC0Nyw-TY6Ww>m znkyBn-aXF1I(jet0aryhq{p9xb#4Uv*_w#nOAgxY`Gyob{sbL+8E7sQgHc2gO0kW>^b-_ZC{8v#@S}5-mx(@Wg*h zCK>0SigV=7bZyuKVzc26f6eMxwSIe+g%}4KRKPHVl~^T^B-4>*XY>F8BoRiDYNRs{ zVHOT?@qrwSLg|w4h9U$-3;62Q`63E?gGzhDD($8|acaufRZok~u~E8n`IE1xQEc8p zmoP6tqDTso?P&_d;zrK^O~847h!jaC+2!s94m;ve2>+lciif80%A1NTbcJmokJE1w z|3d8`kKJQfo+63xJ*+qkQlN~Je?o`&SMB2yGlKnVRGYk8go9o81I#`?;N;ZyTRd4U zLPTdI**THDkq;w9pV6umwEZyukH6e}Z!jdXKtx1h?udM&Y09-EG4SA_yp&hxuTtvY zkqy`XMX8Z|fI3)mov0vS$8kXk?Ms0g4c0YqV~HCbnG*|lxd%Iz0!3bK%p!PwaGPPS zp5bU^(?jB$($H^y2=W`~o9dv0K6{HuIIH;4`y5VYCeOt~phl>XN! z$GMk@@u)~hg72O*qmT3$Th=I86K<^DfX!_3)!wVwZY840u4^e^0XBR<5MyHTmd#qC z%s`5&8QHd&`Ig*`=75%>7Z#J3=Wd zk`XkW6dm6`wp$2g1-FOwR+_6f!L*E!S}~q?8dYQ)mXQd%U_|E#F1{XR3@tm1q9Er* z(=C6>nx*V=-`N7tt}RNO5E0ak^zO6GCwonuKs|=b-B>TXW=(hOy)rLp?-w9b{*9Pr z^c>>K?zZFew!~?{WX~^;jBy86bxUJ+)uxM~BYsg}XPjT(Hf6{6x9<`UerD5Hj7uxv zGW75MUM-t!e6VFkq?K1?-}X5PMAJl{2EtpT(J6YMQwS*_g1%w;>MQUz4j+_5(3MA; z9Bqw}0;shra9llzJF8QD+%Mww5ocOQt|?InC2OrKj2#KJ_CP_-4>LQ5 zaYA%)_BdsA3aZ1Z4NrGTDG84I#OQd}OIUXtA2o)sAC;}|F!wfcPUA}z)W$X%NZMxBT zJ%m3Hp4bj(bTr^|J9~cW6g%oe$y3o&-Cf;%d39N_4&V0)DNq}5r$)eY+hFsh$=$^Rqp!63jbZUr}+8;FR)s00E_SaqB3ZVPkMXehu1&vcQMMmjJFz! zrWx}>E;))dzC2W9+U@nZuiD4H{DLl?7q4IVPHs4jRf%0-A@OF8=AH=B7rRodH0tIz zkZT@GVd|c;i`oZ3Ye;vl#IEC_D5v-hmQ#F~SDh6lqZo6Y;>X9BytezZ;LCkUs)3^H zJcCx1Ewgf*4gcP}GhT;VUU_D&SZvh9M_9&vsUj7Jda+n<)P?-UKJzdg#z&lHStix6 za+!fu-L;dU=@5=Rt7bgMuR0BUot zu8~X;q)Zr<(O|x3#OE*qexTt=E;28rN`evH%ouZZ3NsQMWhIyF9y~r7?A#=P!by6o zMB)~eDNhEVs_$5Qoxzmj5^Us0*}`tXTm>_AW?e~vqL7ncaUFG4tZx-&`fL6MBCIS* zMR2n6WTRibrBg`nZV%0BF+0L;jS<^oF#|KaA60r~zho=Pv>*d4JKbtEHI=n$W;y}v z&KttaYPusI{1F>9y~(tt8_Zm_T3uzh)!x!jYdrkphIRk9Qlqs&1yy|xXMj?Dr4Tx> z%-poKsoq18@mnMDHCS0$cfEvP8oOwr95-0Mc-$7IMKU<*3X5JZv7__P7dC>#-zt0H zdrfiE%jL@kBiprV2k3}O{0)sTlG2)s5AhUk9xMb&PmWNJ%$oQvu!%S36~7U0XY(mG zbbMyOztuY>zIJ-Mg{WTe0x7BnsRUh>3D*a@nAR*Az}3=Pk7+ixZW$DKSXCi?|IM2b zU2y{pNu)HbM@z=| z+&W2yZjyxh?7d1fq9gC1%GzzU(Nlti0xlZhBxRqKv?kV`K#o6zC@YrjND1?ioNUgn zaB&>h{b>fC2HJE>&KS+joc7veggNcBGJPVdX!73ci%3_HD2dTn#F>h8C4X2aBB}ia*;tjE&SX^5b18h%; zyf+YOA#}24RlOWb?mCjEKH)w*#qkd+i51*>q6TI-wYOtqnxJrqyx)nF^D!$?OzzXb z_*pzNfqObjLvQ{nG+JB}KGUh2$U_Ujl}o)}efYyC7F6;_-NtXB{#Wt44E;-Xf}Ij~ z&2%9OLL58OaGW2WM|rFdJ$nOXH%7f@2TvGMqy_#2+8+C^5+r!jjSv5OKDr24VA@l&_=XTRB7N?A$?q zX>-f%t^dLj?WKG+%TNJe#{6vEVPPesiH7eE0Wg`h=`jWp`yLvI_A)+Ox#S0N9zlk0 z<`3tA*oqP;?Hy+l=)YXkCvhEhXlSpEv=aA<^jwO%w3`fBqW~H%I!+yc2T|F!d z=cD;$eaD)Lpxj?-EJGJBF~C1eKQBds6|FR18*dbPJS0LtOpnjkfDx{hb6fG$*jd9` z*~?;dIk!-! z6peEi6hDZ$Ywd!m|E~Nu+9jCX(ddt5J~4;bR6v+rrrU*%uo7&pKy`HQjD1W#E5e`7 z6MQS-syXmh+wU`p+7J*+Pwh^t1M30i+gg$CM3?B7f}Yx*5`w$z`?Y^%#FZzWzz2=8 zgy=G7=+Fxo*=Q>f%{4XX_SM~tS}siTSw-;(g*jnE<><_~N$1nm@3W0{<4{#-?RA{O z8{y^ohZ_7tYFS-NA%8`a&zGH7y0S}kcTGzQj4C$f{kD4Ub2q30YJEK3f;GZXIQ7i) ze?t{v0Z6iZKGNDN1sb>H>hrLb4BKoA>MNWkFgbqI-Y5iGxwF6zt72vxX#%4l@k}<; zum#38ec?%KhlUf3wT|vgY`)6|E6>&Ngui7>{Qs8ILC`H(!xd~ONt%R6z5i+33OHzB zA^^Y}nfTps5`3(G%}5!(cZJ=py0C7ez>*6FxK~hq>0u^EoPq6lG>wWJCW6Mm3m?JN zMRNi=w;cWL>#aUE1A0E;2S$JPPCsde21U5OK_dc>of+a#bdp~Q(RptSY~98m4ksS* zZ3hm*$)BwAUE7{zVK>$8j-syAG%`6zs^~1|-3s%8>oWgS<|VSimhGV!D8sg9X7w{` z;#`xv*C*p7JBpJ@F8x{;9d3jpn~ zj(HF4cZd88vB&^LJlbbdAgud05qoo4? zAJ~yd2I5?f5Y+BWHbi_rzm9V9p%=|-sYxHoSqZpryWOZ-$Mk8CQ}!U2M%_f!hLL3Cu5 zkJL?|y#iBX1zjW5+5(o6vb^0#W3|h4GSda!m5qetXlgoID={wOdfPED%Zk;2N63$! z^3Aw`KtXE;^?rGq&dPb+WX+T^J&jMI;{}>86%X{)^Gp(V$>KZ|?PV|fybIp1eF3^s zhNRlsre4Iwhy_{Xvz5eYyG>6!3v)(=x;01f$mlXk<^Cr=IU^J~an$pA1X^c-jYlXG z(AEQxhu_$mCZvl-sHXO;1S1##zBt(1)wO8D1CnmpiRXIw{37ttc^Xoq39sgHYO8b; zHz{hKBRRWticoV@4=KvRg)oK=5X?#W$Z>naA6qCMf2{IKj>I&aZ)zuC%?-yhI~ej7 z$t}v1IY2C)&!%Ns=h1bC&r&T~llqL<#qady&3e6TZIYYt{80PiV63W#i!C;j5 z_`LnyGs@m&qr~g1H*C zuU0m+1w7VJ+FBB`gh*bheFaQpO=ga~2Jy)BR@YHOA3-%OF4T8$s!xgWXZt*z#vrY{ z0acLCJ<4PG{fcQb!@+j0Unku`C%uW5r4PCC)072s+|&0jtFmwpRQ#%1o!F4{bdRj4 zZ#Q)G3V8+mi?i77;i`MwGfEHEr%N|E;V0`bTesk)_{vT1h^8c&2z%fgKm&5mX+rPGa`!clEUXVjd-XoZfLPP8thI4+@e zopo7SAvvV;^lE%w`{()no_&A6_t`%0`~LB}JdfcqC7UmM@p^MCCdu|j*v03E9 zkB;Y$?<~6=b7W7p^O5qV4xbWoX3Kq-^464fI-#}IYrC>#tf_X7{m4|w6_ed(1YWNr zj&i$80eP=;TxVicvuRaRG?_7)rc_Vl%Vc@3_qVzx2~sl{L_bD0Qf?s`D7LP!D%KJE z(i0cZ2hP4qdZwV3n~K|4v-4RAg*ng77jRtrsI{@OmB$}Fw4ai&_NJhNlqT1=)3x2} zl*!3IeA@bNCt*_^s*W|OT;`L0bMx`j+>|3{wV&0J1gBLwLGmC@x2dm_=#4yjZWP4-dC8@Oxo;XHvvDhQZSDb6tEXv(= zt5D??N~nx{o1`62iVpnamG`F%Y2|QP%!JB|1}+VWp7M5nbWmf0maSB=a3tWaBq1e2 zS~)|0&2PZAWOT%Uzy1p4^U6o;oKUXt;Im~{bJG4yw!Xi~_x1;C`l%CdPrakp6mSK% z28SoJGPac@g}WjT_9FS$hWvV%E%u|nzqF)swmswoJ7w=?OOw3a)uvVL=5k9D z$(m7j_YYHd{~pkoMrJcD^m4|PL7!$?^4jaQ zrpn;5oCwk2LU!^_S?hJ14D+J!L;fDzG!f$t$G|(-(ZQn7Xn3H1SYH`_Ib5vgU*__m z@vrL@c_s9wx+rD+jxPb9Pfv^UAFBK(|D$wE!YQWee)T88zcV9KN|DLhbk5a$5--AJ zNm&4M7jt;-{w0Jsb%^ND%E22{8`%c4yw440AGHFf?Or)OfGOG_i`YyO#E>MJORowZ zdenPV+|F&cVM@V>&5-+?{PO%8X;PU)nm1yol6uvcH!*6y4j7saz03ycUOObz0<>j0fP_{e2vboIe=6Xq7|42SY+3#jVctq*f#MF>!g(Uw zj)NpnD<=ZeJ_@4aicPKUGeN(d;Q&KwW;nZ{uh19K6d>xGh5oqlvW6A%QkP-g#z4=) znLy_a5ykC-t0CbiqtDS#nVOL(_A;J~PianKCKS_+`I_-U}H z1qU%sVvxGsihz|DgSj1sNNEn7^a0!sB8n^h#94-)!7S@LtO;1n3>bx$Ck5D~Yo`^8 zD?7kho+tnnE(?ID(-QqL?W^I0yqxn8a>dqwOY>?7I0|{^i$EjY4M;nQ1T4u51PVi> z;3DRJEVrvr7!j~MS=MVqgnwy#-V(*K67WyE0iR`ioX7Ml*bjKQeEOJI6c?bzfo;VY zXt!bx2I2zLIMDbS1kFIK(hiBQ20bD3L7kF@9BzQ386bBpMlL)CfnDLqfhPcVSQ)MH VBfu2+*qg&~f-ihLS3eum{s(|${^$Sz diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661ee733..f398c33c4b08 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787337ff..65dcd68d65c8 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -143,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -205,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93825..6689b85beecd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java b/integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java index b4c1131ea1cd..5b76d47f4c6f 100644 --- a/integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java +++ b/integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -34,7 +34,7 @@ /** * Integration tests for scoped proxy use in conjunction with aop: namespace. - * Deemed an integration test because .web mocks and application contexts are required. + * Deemed an integration test because web mocks and application contexts are required. * * @author Rob Harrop * @author Juergen Hoeller diff --git a/integration-tests/src/test/java/org/springframework/cache/annotation/EnableCachingIntegrationTests.java b/integration-tests/src/test/java/org/springframework/cache/annotation/EnableCachingIntegrationTests.java index 63534df9635c..41acb6ed5d62 100644 --- a/integration-tests/src/test/java/org/springframework/cache/annotation/EnableCachingIntegrationTests.java +++ b/integration-tests/src/test/java/org/springframework/cache/annotation/EnableCachingIntegrationTests.java @@ -61,7 +61,7 @@ void repositoryUsesAspectJAdviceMode() { ctx.register(Config.class, AspectJCacheConfig.class); // this test is a bit fragile, but gets the job done, proving that an // attempt was made to look up the AJ aspect. It's due to classpath issues - // in .integration-tests that it's not found. + // in integration-tests that it's not found. assertThatException().isThrownBy(ctx::refresh) .withMessageContaining("AspectJCachingConfiguration"); } diff --git a/integration-tests/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java b/integration-tests/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java index bf52ef5fde18..4ab8cab1569e 100644 --- a/integration-tests/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java +++ b/integration-tests/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java @@ -97,7 +97,7 @@ void repositoryUsesAspectJAdviceMode() { ctx.register(Config.class, AspectJTxConfig.class); // this test is a bit fragile, but gets the job done, proving that an // attempt was made to look up the AJ aspect. It's due to classpath issues - // in .integration-tests that it's not found. + // in integration-tests that it's not found. assertThatException() .isThrownBy(ctx::refresh) .withMessageContaining("AspectJJtaTransactionManagementConfiguration"); diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java index 1d7c74b811e6..22a46e9456c6 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java @@ -655,10 +655,10 @@ protected JoinPoint getJoinPoint() { @Nullable protected JoinPointMatch getJoinPointMatch() { MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation(); - if (!(mi instanceof ProxyMethodInvocation)) { + if (!(mi instanceof ProxyMethodInvocation pmi)) { throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); } - return getJoinPointMatch((ProxyMethodInvocation) mi); + return getJoinPointMatch(pmi); } // Note: We can't use JoinPointMatch.getClass().getName() as the key, since diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java index 13d21faab205..68918ee376b8 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -132,17 +132,17 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov private static final int STEP_REFERENCE_PCUT_BINDING = 7; private static final int STEP_FINISHED = 8; - private static final Set singleValuedAnnotationPcds = new HashSet<>(); + private static final Set singleValuedAnnotationPcds = Set.of( + "@this", + "@target", + "@within", + "@withincode", + "@annotation"); + private static final Set nonReferencePointcutTokens = new HashSet<>(); static { - singleValuedAnnotationPcds.add("@this"); - singleValuedAnnotationPcds.add("@target"); - singleValuedAnnotationPcds.add("@within"); - singleValuedAnnotationPcds.add("@withincode"); - singleValuedAnnotationPcds.add("@annotation"); - Set pointcutPrimitives = PointcutParser.getAllSupportedPointcutPrimitives(); for (PointcutPrimitive primitive : pointcutPrimitives) { nonReferencePointcutTokens.add(primitive.getName()); diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAopUtils.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAopUtils.java index 0d2318092714..4ea59280d1b1 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAopUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAopUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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. @@ -61,12 +61,12 @@ public static boolean isAfterAdvice(Advisor anAdvisor) { */ @Nullable public static AspectJPrecedenceInformation getAspectJPrecedenceInformationFor(Advisor anAdvisor) { - if (anAdvisor instanceof AspectJPrecedenceInformation) { - return (AspectJPrecedenceInformation) anAdvisor; + if (anAdvisor instanceof AspectJPrecedenceInformation ajpi) { + return ajpi; } Advice advice = anAdvisor.getAdvice(); - if (advice instanceof AspectJPrecedenceInformation) { - return (AspectJPrecedenceInformation) advice; + if (advice instanceof AspectJPrecedenceInformation ajpi) { + return ajpi; } return null; } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java index b5c8b87ed84a..ced1c2556123 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java @@ -21,7 +21,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; -import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -85,21 +84,17 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware { - private static final Set SUPPORTED_PRIMITIVES = new HashSet<>(); - - static { - SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION); - SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS); - SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE); - SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS); - SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET); - SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN); - SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION); - SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN); - SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS); - SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET); - } - + private static final Set SUPPORTED_PRIMITIVES = Set.of( + PointcutPrimitive.EXECUTION, + PointcutPrimitive.ARGS, + PointcutPrimitive.REFERENCE, + PointcutPrimitive.THIS, + PointcutPrimitive.TARGET, + PointcutPrimitive.WITHIN, + PointcutPrimitive.AT_ANNOTATION, + PointcutPrimitive.AT_WITHIN, + PointcutPrimitive.AT_ARGS, + PointcutPrimitive.AT_TARGET); private static final Log logger = LogFactory.getLog(AspectJExpressionPointcut.class); @@ -205,8 +200,8 @@ private PointcutExpression obtainPointcutExpression() { */ @Nullable private ClassLoader determinePointcutClassLoader() { - if (this.beanFactory instanceof ConfigurableBeanFactory) { - return ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader(); + if (this.beanFactory instanceof ConfigurableBeanFactory cbf) { + return cbf.getBeanClassLoader(); } if (this.pointcutDeclarationScope != null) { return this.pointcutDeclarationScope.getClassLoader(); @@ -340,10 +335,10 @@ public boolean matches(Method method, Class targetClass, Object... args) { try { MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation(); targetObject = mi.getThis(); - if (!(mi instanceof ProxyMethodInvocation)) { + if (!(mi instanceof ProxyMethodInvocation _pmi)) { throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); } - pmi = (ProxyMethodInvocation) mi; + pmi = _pmi; thisObject = pmi.getProxy(); } catch (IllegalStateException ex) { @@ -409,8 +404,8 @@ private PointcutExpression getFallbackPointcutExpression(Class targetClass) { } private RuntimeTestWalker getRuntimeTestWalker(ShadowMatch shadowMatch) { - if (shadowMatch instanceof DefensiveShadowMatch) { - return new RuntimeTestWalker(((DefensiveShadowMatch) shadowMatch).primary); + if (shadowMatch instanceof DefensiveShadowMatch defensiveShadowMatch) { + return new RuntimeTestWalker(defensiveShadowMatch.primary); } return new RuntimeTestWalker(shadowMatch); } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJProxyUtils.java index e161007abe98..be7c8569404b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJProxyUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJProxyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -71,8 +71,8 @@ public static boolean makeAdvisorChainAspectJCapableIfNecessary(List ad private static boolean isAspectJAdvice(Advisor advisor) { return (advisor instanceof InstantiationModelAwarePointcutAdvisor || advisor.getAdvice() instanceof AbstractAspectJAdvice || - (advisor instanceof PointcutAdvisor && - ((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut)); + (advisor instanceof PointcutAdvisor pointcutAdvisor && + pointcutAdvisor.getPointcut() instanceof AspectJExpressionPointcut)); } static boolean isVariableName(@Nullable String name) { diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/RuntimeTestWalker.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/RuntimeTestWalker.java index 82e13b2bd2bc..bf37296a6e8a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/RuntimeTestWalker.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/RuntimeTestWalker.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -202,8 +202,8 @@ public void visit(Instanceof i) { } Class typeClass = null; ResolvedType type = (ResolvedType) i.getType(); - if (type instanceof ReferenceType) { - ReferenceTypeDelegate delegate = ((ReferenceType) type).getDelegate(); + if (type instanceof ReferenceType referenceType) { + ReferenceTypeDelegate delegate = referenceType.getDelegate(); if (delegate instanceof ReflectionBasedReferenceTypeDelegate) { try { ReflectionUtils.makeAccessible(myClassField); diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java index 80447912c292..04edaa807663 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2022 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. @@ -69,8 +69,8 @@ public ClassLoader getAspectClassLoader() { */ @Override public int getOrder() { - if (this.aspectInstance instanceof Ordered) { - return ((Ordered) this.aspectInstance).getOrder(); + if (this.aspectInstance instanceof Ordered ordered) { + return ordered.getOrder(); } return getOrderForAspectClass(this.aspectInstance.getClass()); } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java index cf1dcd929b15..ea26ec97912b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -117,9 +117,9 @@ private String replaceBooleanOperators(String pcExpr) { } @Override - public boolean equals(Object other) { - return (this == other || (other instanceof TypePatternClassFilter && - ObjectUtils.nullSafeEquals(this.typePattern, ((TypePatternClassFilter) other).typePattern))); + public boolean equals(Object obj) { + return (this == obj || (obj instanceof TypePatternClassFilter that && + ObjectUtils.nullSafeEquals(this.typePattern, that.typePattern))); } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java index 54110bcd0d81..4e004ea536e5 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -21,7 +21,6 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; @@ -163,24 +162,22 @@ protected enum AspectJAnnotationType { /** - * Class modelling an AspectJ annotation, exposing its type enumeration and + * Class modeling an AspectJ annotation, exposing its type enumeration and * pointcut String. * @param the annotation type */ protected static class AspectJAnnotation { - private static final String[] EXPRESSION_ATTRIBUTES = new String[] {"pointcut", "value"}; + private static final String[] EXPRESSION_ATTRIBUTES = {"pointcut", "value"}; - private static Map, AspectJAnnotationType> annotationTypeMap = new HashMap<>(8); - - static { - annotationTypeMap.put(Pointcut.class, AspectJAnnotationType.AtPointcut); - annotationTypeMap.put(Around.class, AspectJAnnotationType.AtAround); - annotationTypeMap.put(Before.class, AspectJAnnotationType.AtBefore); - annotationTypeMap.put(After.class, AspectJAnnotationType.AtAfter); - annotationTypeMap.put(AfterReturning.class, AspectJAnnotationType.AtAfterReturning); - annotationTypeMap.put(AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing); - } + private static final Map, AspectJAnnotationType> annotationTypeMap = Map.of( + Pointcut.class, AspectJAnnotationType.AtPointcut, // + Around.class, AspectJAnnotationType.AtAround, // + Before.class, AspectJAnnotationType.AtBefore, // + After.class, AspectJAnnotationType.AtAfter, // + AfterReturning.class, AspectJAnnotationType.AtAfterReturning, // + AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing // + ); private final A annotation; @@ -196,7 +193,7 @@ public AspectJAnnotation(A annotation) { try { this.pointcutExpression = resolveExpression(annotation); Object argNames = AnnotationUtils.getValue(annotation, "argNames"); - this.argumentNames = (argNames instanceof String ? (String) argNames : ""); + this.argumentNames = (argNames instanceof String names ? names : ""); } catch (Exception ex) { throw new IllegalArgumentException(annotation + " is not a valid AspectJ annotation", ex); @@ -214,13 +211,11 @@ private AspectJAnnotationType determineAnnotationType(A annotation) { private String resolveExpression(A annotation) { for (String attributeName : EXPRESSION_ATTRIBUTES) { Object val = AnnotationUtils.getValue(annotation, attributeName); - if (val instanceof String str) { - if (!str.isEmpty()) { - return str; - } + if (val instanceof String str && !str.isEmpty()) { + return str; } } - throw new IllegalStateException("Failed to resolve expression: " + annotation); + throw new IllegalStateException("Failed to resolve expression in: " + annotation); } public AspectJAnnotationType getAnnotationType() { diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java index d5fefcec81d0..3bdbb9c16abd 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -93,9 +93,8 @@ public Object getAspectInstance() { @Override @Nullable public ClassLoader getAspectClassLoader() { - return (this.beanFactory instanceof ConfigurableBeanFactory ? - ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : - ClassUtils.getDefaultClassLoader()); + return (this.beanFactory instanceof ConfigurableBeanFactory cbf ? + cbf.getBeanClassLoader() : ClassUtils.getDefaultClassLoader()); } @Override @@ -110,11 +109,11 @@ public Object getAspectCreationMutex() { // Rely on singleton semantics provided by the factory -> no local lock. return null; } - else if (this.beanFactory instanceof ConfigurableBeanFactory) { + else if (this.beanFactory instanceof ConfigurableBeanFactory cbf) { // No singleton guarantees from the factory -> let's lock locally but // reuse the factory's singleton lock, just in case a lazy dependency // of our advice bean happens to trigger the singleton lock implicitly... - return ((ConfigurableBeanFactory) this.beanFactory).getSingletonMutex(); + return cbf.getSingletonMutex(); } else { return this; diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java index 33af3adfc309..f842e50ed9f1 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java @@ -274,8 +274,8 @@ public PerTargetInstantiationModelPointcut(AspectJExpressionPointcut declaredPoi this.declaredPointcut = declaredPointcut; this.preInstantiationPointcut = preInstantiationPointcut; - if (aspectInstanceFactory instanceof LazySingletonAspectInstanceFactoryDecorator) { - this.aspectInstanceFactory = (LazySingletonAspectInstanceFactoryDecorator) aspectInstanceFactory; + if (aspectInstanceFactory instanceof LazySingletonAspectInstanceFactoryDecorator lazyFactory) { + this.aspectInstanceFactory = lazyFactory; } } diff --git a/spring-aop/src/main/java/org/springframework/aop/config/AbstractInterceptorDrivenBeanDefinitionDecorator.java b/spring-aop/src/main/java/org/springframework/aop/config/AbstractInterceptorDrivenBeanDefinitionDecorator.java index 52a00db50c96..28fc6cdbb69b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/config/AbstractInterceptorDrivenBeanDefinitionDecorator.java +++ b/spring-aop/src/main/java/org/springframework/aop/config/AbstractInterceptorDrivenBeanDefinitionDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -93,8 +93,8 @@ public final BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder defin // copy autowire settings from original bean definition. proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate()); proxyDefinition.setPrimary(targetDefinition.isPrimary()); - if (targetDefinition instanceof AbstractBeanDefinition) { - proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition); + if (targetDefinition instanceof AbstractBeanDefinition abd) { + proxyDefinition.copyQualifiersFrom(abd); } // wrap it in a BeanDefinitionHolder with bean name result = new BeanDefinitionHolder(proxyDefinition, existingBeanName); diff --git a/spring-aop/src/main/java/org/springframework/aop/config/ConfigBeanDefinitionParser.java b/spring-aop/src/main/java/org/springframework/aop/config/ConfigBeanDefinitionParser.java index fff18c0a4e42..b23dcf3d901c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/config/ConfigBeanDefinitionParser.java +++ b/spring-aop/src/main/java/org/springframework/aop/config/ConfigBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -153,13 +153,13 @@ private void parseAdvisor(Element advisorElement, ParserContext parserContext) { } Object pointcut = parsePointcutProperty(advisorElement, parserContext); - if (pointcut instanceof BeanDefinition) { + if (pointcut instanceof BeanDefinition beanDefinition) { advisorDef.getPropertyValues().add(POINTCUT, pointcut); parserContext.registerComponent( - new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut)); + new AdvisorComponentDefinition(advisorBeanName, advisorDef, beanDefinition)); } - else if (pointcut instanceof String) { - advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut)); + else if (pointcut instanceof String beanName) { + advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference(beanName)); parserContext.registerComponent( new AdvisorComponentDefinition(advisorBeanName, advisorDef)); } @@ -389,12 +389,12 @@ private AbstractBeanDefinition createAdviceDefinition( cav.addIndexedArgumentValue(METHOD_INDEX, methodDef); Object pointcut = parsePointcutProperty(adviceElement, parserContext); - if (pointcut instanceof BeanDefinition) { + if (pointcut instanceof BeanDefinition beanDefinition) { cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut); - beanDefinitions.add((BeanDefinition) pointcut); + beanDefinitions.add(beanDefinition); } - else if (pointcut instanceof String) { - RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut); + else if (pointcut instanceof String beanName) { + RuntimeBeanReference pointcutRef = new RuntimeBeanReference(beanName); cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef); beanReferences.add(pointcutRef); } diff --git a/spring-aop/src/main/java/org/springframework/aop/config/SimpleBeanFactoryAwareAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/config/SimpleBeanFactoryAwareAspectInstanceFactory.java index 3d89993caddd..446d5f93e0a8 100644 --- a/spring-aop/src/main/java/org/springframework/aop/config/SimpleBeanFactoryAwareAspectInstanceFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/config/SimpleBeanFactoryAwareAspectInstanceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -58,7 +58,7 @@ public void setBeanFactory(BeanFactory beanFactory) { /** - * Look up the aspect bean from the {@link BeanFactory} and returns it. + * Look up the aspect bean from the {@link BeanFactory} and return it. * @see #setAspectBeanName */ @Override @@ -71,8 +71,8 @@ public Object getAspectInstance() { @Override @Nullable public ClassLoader getAspectClassLoader() { - if (this.beanFactory instanceof ConfigurableBeanFactory) { - return ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader(); + if (this.beanFactory instanceof ConfigurableBeanFactory cbf) { + return cbf.getBeanClassLoader(); } else { return ClassUtils.getDefaultClassLoader(); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java index 1c333af6944b..68c54557a118 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java @@ -74,8 +74,9 @@ public Class determineBeanType(Class beanClass, String beanName) { // Use original ClassLoader if bean class not locally loaded in overriding class loader ClassLoader classLoader = getProxyClassLoader(); - if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) { - classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader(); + if (classLoader instanceof SmartClassLoader smartClassLoader && + classLoader != beanClass.getClassLoader()) { + classLoader = smartClassLoader.getOriginalClassLoader(); } return proxyFactory.getProxyClass(classLoader); } @@ -113,8 +114,9 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { // Use original ClassLoader if bean class not locally loaded in overriding class loader ClassLoader classLoader = getProxyClassLoader(); - if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) { - classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader(); + if (classLoader instanceof SmartClassLoader smartClassLoader && + classLoader != bean.getClass().getClassLoader()) { + classLoader = smartClassLoader.getOriginalClassLoader(); } return proxyFactory.getProxy(classLoader); } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java index bb680a3477b5..cf40782c784a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,8 +194,8 @@ else if (!isProxyTargetClass()) { * @return a TargetSource for this object */ protected TargetSource createTargetSource(Object target) { - if (target instanceof TargetSource) { - return (TargetSource) target; + if (target instanceof TargetSource targetSource) { + return targetSource; } else { return new SingletonTargetSource(target); @@ -229,8 +229,8 @@ public Class getObjectType() { if (this.proxyInterfaces != null && this.proxyInterfaces.length == 1) { return this.proxyInterfaces[0]; } - if (this.target instanceof TargetSource) { - return ((TargetSource) this.target).getTargetClass(); + if (this.target instanceof TargetSource targetSource) { + return targetSource.getTargetClass(); } if (this.target != null) { return this.target.getClass(); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java index 3862d4640862..90d5afef82d9 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -258,8 +258,8 @@ public void addAdvisor(Advisor advisor) { @Override public void addAdvisor(int pos, Advisor advisor) throws AopConfigException { - if (advisor instanceof IntroductionAdvisor) { - validateIntroductionAdvisor((IntroductionAdvisor) advisor); + if (advisor instanceof IntroductionAdvisor introductionAdvisor) { + validateIntroductionAdvisor(introductionAdvisor); } addAdvisorInternal(pos, advisor); } @@ -287,9 +287,9 @@ public void removeAdvisor(int index) throws AopConfigException { } Advisor advisor = this.advisors.remove(index); - if (advisor instanceof IntroductionAdvisor ia) { + if (advisor instanceof IntroductionAdvisor introductionAdvisor) { // We need to remove introduction interfaces. - for (Class ifc : ia.getInterfaces()) { + for (Class ifc : introductionAdvisor.getInterfaces()) { removeInterface(ifc); } } @@ -334,8 +334,8 @@ public void addAdvisors(Collection advisors) { } if (!CollectionUtils.isEmpty(advisors)) { for (Advisor advisor : advisors) { - if (advisor instanceof IntroductionAdvisor) { - validateIntroductionAdvisor((IntroductionAdvisor) advisor); + if (advisor instanceof IntroductionAdvisor introductionAdvisor) { + validateIntroductionAdvisor(introductionAdvisor); } Assert.notNull(advisor, "Advisor must not be null"); this.advisors.add(advisor); @@ -387,10 +387,10 @@ public void addAdvice(Advice advice) throws AopConfigException { @Override public void addAdvice(int pos, Advice advice) throws AopConfigException { Assert.notNull(advice, "Advice must not be null"); - if (advice instanceof IntroductionInfo) { + if (advice instanceof IntroductionInfo introductionInfo) { // We don't need an IntroductionAdvisor for this kind of introduction: // It's fully self-describing. - addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice)); + addAdvisor(pos, new DefaultIntroductionAdvisor(advice, introductionInfo)); } else if (advice instanceof DynamicIntroductionAdvice) { // We need an IntroductionAdvisor for this kind of introduction. @@ -506,8 +506,8 @@ protected void copyConfigurationFrom(AdvisedSupport other, TargetSource targetSo this.advisorChainFactory = other.advisorChainFactory; this.interfaces = new ArrayList<>(other.interfaces); for (Advisor advisor : advisors) { - if (advisor instanceof IntroductionAdvisor) { - validateIntroductionAdvisor((IntroductionAdvisor) advisor); + if (advisor instanceof IntroductionAdvisor introductionAdvisor) { + validateIntroductionAdvisor(introductionAdvisor); } Assert.notNull(advisor, "Advisor must not be null"); this.advisors.add(advisor); @@ -580,8 +580,8 @@ public MethodCacheKey(Method method) { @Override public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof MethodCacheKey && - this.method == ((MethodCacheKey) other).method)); + return (this == other || (other instanceof MethodCacheKey methodCacheKey && + this.method == methodCacheKey.method)); } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java index 4beff09bc343..d8a190298c18 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java @@ -188,8 +188,8 @@ private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) Enhancer enhancer = createEnhancer(); if (classLoader != null) { enhancer.setClassLoader(classLoader); - if (classLoader instanceof SmartClassLoader && - ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) { + if (classLoader instanceof SmartClassLoader smartClassLoader && + smartClassLoader.isClassReloadable(proxySuperClass)) { enhancer.setUseCache(false); } } @@ -365,8 +365,8 @@ private Callback[] getCallbacks(Class rootClass) throws Exception { @Override public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof CglibAopProxy && - AopProxyUtils.equalsInProxy(this.advised, ((CglibAopProxy) other).advised))); + return (this == other || (other instanceof CglibAopProxy cglibAopProxy && + AopProxyUtils.equalsInProxy(this.advised, cglibAopProxy.advised))); } @Override @@ -590,12 +590,12 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy if (proxy == other) { return true; } - if (other instanceof Factory) { - Callback callback = ((Factory) other).getCallback(INVOKE_EQUALS); - if (!(callback instanceof EqualsInterceptor)) { + if (other instanceof Factory factory) { + Callback callback = factory.getCallback(INVOKE_EQUALS); + if (!(callback instanceof EqualsInterceptor equalsInterceptor)) { return false; } - AdvisedSupport otherAdvised = ((EqualsInterceptor) callback).advised; + AdvisedSupport otherAdvised = equalsInterceptor.advised; return AopProxyUtils.equalsInProxy(this.advised, otherAdvised); } else { @@ -719,8 +719,8 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy @Override public boolean equals(@Nullable Object other) { return (this == other || - (other instanceof DynamicAdvisedInterceptor && - this.advised.equals(((DynamicAdvisedInterceptor) other).advised))); + (other instanceof DynamicAdvisedInterceptor dynamicAdvisedInterceptor && + this.advised.equals(dynamicAdvisedInterceptor.advised))); } /** @@ -962,9 +962,9 @@ private static boolean equalsAdviceClasses(Advisor a, Advisor b) { private static boolean equalsPointcuts(Advisor a, Advisor b) { // If only one of the advisor (but not both) is PointcutAdvisor, then it is a mismatch. // Takes care of the situations where an IntroductionAdvisor is used (see SPR-3959). - return (!(a instanceof PointcutAdvisor) || - (b instanceof PointcutAdvisor && - ObjectUtils.nullSafeEquals(((PointcutAdvisor) a).getPointcut(), ((PointcutAdvisor) b).getPointcut()))); + return (!(a instanceof PointcutAdvisor pointcutAdvisor1) || + (b instanceof PointcutAdvisor pointcutAdvisor2 && + ObjectUtils.nullSafeEquals(pointcutAdvisor1.getPointcut(), pointcutAdvisor2.getPointcut()))); } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java index 67e435a88357..c313ad570e5a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -65,11 +65,11 @@ public List getInterceptorsAndDynamicInterceptionAdvice( if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); boolean match; - if (mm instanceof IntroductionAwareMethodMatcher) { + if (mm instanceof IntroductionAwareMethodMatcher iamm) { if (hasIntroductions == null) { hasIntroductions = hasMatchingIntroductions(advisors, actualClass); } - match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions); + match = iamm.matches(method, actualClass, hasIntroductions); } else { match = mm.matches(method, actualClass); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java index 81b48229ef39..d6a45a9fc92d 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java @@ -262,15 +262,15 @@ public boolean equals(@Nullable Object other) { } JdkDynamicAopProxy otherProxy; - if (other instanceof JdkDynamicAopProxy) { - otherProxy = (JdkDynamicAopProxy) other; + if (other instanceof JdkDynamicAopProxy jdkDynamicAopProxy) { + otherProxy = jdkDynamicAopProxy; } else if (Proxy.isProxyClass(other.getClass())) { InvocationHandler ih = Proxy.getInvocationHandler(other); - if (!(ih instanceof JdkDynamicAopProxy)) { + if (!(ih instanceof JdkDynamicAopProxy jdkDynamicAopProxy)) { return false; } - otherProxy = (JdkDynamicAopProxy) ih; + otherProxy = jdkDynamicAopProxy; } else { // Not a valid comparison... diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyCreatorSupport.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyCreatorSupport.java index 9fa04a33e92f..096294321f80 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyCreatorSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyCreatorSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -86,7 +86,7 @@ public void addListener(AdvisedSupportListener listener) { /** * Remove the given AdvisedSupportListener from this proxy configuration. - * @param listener the listener to deregister + * @param listener the listener to remove */ public void removeListener(AdvisedSupportListener listener) { Assert.notNull(listener, "AdvisedSupportListener must not be null"); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistrationManager.java b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistrationManager.java index c9a4af847871..6589fffd19b0 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistrationManager.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistrationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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. @@ -55,8 +55,8 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof AdvisorAdapter){ - this.advisorAdapterRegistry.registerAdvisorAdapter((AdvisorAdapter) bean); + if (bean instanceof AdvisorAdapter advisorAdapter) { + this.advisorAdapterRegistry.registerAdvisorAdapter(advisorAdapter); } return bean; } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/DefaultAdvisorAdapterRegistry.java b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/DefaultAdvisorAdapterRegistry.java index 9335a1f5a012..e1a92b241d3b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/DefaultAdvisorAdapterRegistry.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/DefaultAdvisorAdapterRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -55,8 +55,8 @@ public DefaultAdvisorAdapterRegistry() { @Override public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException { - if (adviceObject instanceof Advisor) { - return (Advisor) adviceObject; + if (adviceObject instanceof Advisor advisor) { + return advisor; } if (!(adviceObject instanceof Advice advice)) { throw new UnknownAdviceTypeException(adviceObject); @@ -78,8 +78,8 @@ public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException { public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { List interceptors = new ArrayList<>(3); Advice advice = advisor.getAdvice(); - if (advice instanceof MethodInterceptor) { - interceptors.add((MethodInterceptor) advice); + if (advice instanceof MethodInterceptor methodInterceptor) { + interceptors.add(methodInterceptor); } for (AdvisorAdapter adapter : this.adapters) { if (adapter.supportsAdvice(advice)) { diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java index 4900c3d4657c..ca048cb9f17c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -58,11 +58,11 @@ public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyC @Override public void setBeanFactory(BeanFactory beanFactory) { super.setBeanFactory(beanFactory); - if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { + if (!(beanFactory instanceof ConfigurableListableBeanFactory clbf)) { throw new IllegalArgumentException( "AdvisorAutoProxyCreator requires a ConfigurableListableBeanFactory: " + beanFactory); } - initBeanFactory((ConfigurableListableBeanFactory) beanFactory); + initBeanFactory(clbf); } protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) { diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java index f6363db6a430..502fd9e29259 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java @@ -473,8 +473,8 @@ private Class createProxyClass(Class beanClass, @Nullable String beanName, private Object buildProxy(Class beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) { - if (this.beanFactory instanceof ConfigurableListableBeanFactory) { - AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); + if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) { + AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass); } ProxyFactory proxyFactory = new ProxyFactory(); @@ -511,8 +511,8 @@ private Object buildProxy(Class beanClass, @Nullable String beanName, // Use original ClassLoader if bean class not locally loaded in overriding class loader ClassLoader classLoader = getProxyClassLoader(); - if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) { - classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader(); + if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) { + classLoader = smartClassLoader.getOriginalClassLoader(); } return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader)); } @@ -527,8 +527,8 @@ private Object buildProxy(Class beanClass, @Nullable String beanName, * @see AutoProxyUtils#shouldProxyTargetClass */ protected boolean shouldProxyTargetClass(Class beanClass, @Nullable String beanName) { - return (this.beanFactory instanceof ConfigurableListableBeanFactory && - AutoProxyUtils.shouldProxyTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName)); + return (this.beanFactory instanceof ConfigurableListableBeanFactory clbf && + AutoProxyUtils.shouldProxyTargetClass(clbf, beanName)); } /** @@ -592,7 +592,7 @@ protected Advisor[] buildAdvisors(@Nullable String beanName, @Nullable Object[] */ private Advisor[] resolveInterceptorNames() { BeanFactory bf = this.beanFactory; - ConfigurableBeanFactory cbf = (bf instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory) bf : null); + ConfigurableBeanFactory cbf = (bf instanceof ConfigurableBeanFactory _cbf ? _cbf : null); List advisors = new ArrayList<>(); for (String beanName : this.interceptorNames) { if (cbf == null || !cbf.isCurrentlyInCreation(beanName)) { diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractBeanFactoryAwareAdvisingPostProcessor.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractBeanFactoryAwareAdvisingPostProcessor.java index 280eb9b23a6b..0dbea8a467f5 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractBeanFactoryAwareAdvisingPostProcessor.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractBeanFactoryAwareAdvisingPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -46,8 +46,7 @@ public abstract class AbstractBeanFactoryAwareAdvisingPostProcessor extends Abst @Override public void setBeanFactory(BeanFactory beanFactory) { - this.beanFactory = (beanFactory instanceof ConfigurableListableBeanFactory ? - (ConfigurableListableBeanFactory) beanFactory : null); + this.beanFactory = (beanFactory instanceof ConfigurableListableBeanFactory clbf ? clbf : null); } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java index fcef74b56ef4..ff9a03f15cfb 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -67,11 +67,11 @@ public abstract class AbstractBeanFactoryBasedTargetSourceCreator @Override public final void setBeanFactory(BeanFactory beanFactory) { - if (!(beanFactory instanceof ConfigurableBeanFactory)) { + if (!(beanFactory instanceof ConfigurableBeanFactory clbf)) { throw new IllegalStateException("Cannot do auto-TargetSource creation with a BeanFactory " + "that doesn't implement ConfigurableBeanFactory: " + beanFactory.getClass()); } - this.beanFactory = (ConfigurableBeanFactory) beanFactory; + this.beanFactory = clbf; } /** diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java index 01aa62b7607c..68ca0524471a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -66,9 +66,8 @@ protected boolean isPrototypeBased() { protected AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource( Class beanClass, String beanName) { - if (getBeanFactory() instanceof ConfigurableListableBeanFactory) { - BeanDefinition definition = - ((ConfigurableListableBeanFactory) getBeanFactory()).getBeanDefinition(beanName); + if (getBeanFactory() instanceof ConfigurableListableBeanFactory clbf) { + BeanDefinition definition = clbf.getBeanDefinition(beanName); if (definition.isLazyInit()) { return new LazyInitTargetSource(); } diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java index 5cef18836f8e..5d35e87994b5 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java @@ -182,8 +182,8 @@ protected AsyncTaskExecutor determineAsyncExecutor(Method method) { if (targetExecutor == null) { return null; } - executor = (targetExecutor instanceof AsyncTaskExecutor ? - (AsyncTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor)); + executor = (targetExecutor instanceof AsyncTaskExecutor asyncTaskExecutor ? + asyncTaskExecutor : new TaskExecutorAdapter(targetExecutor)); this.executors.put(method, executor); } return executor; diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java index a466164bcc7b..d585df32a49a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -113,8 +113,8 @@ public Object invoke(final MethodInvocation invocation) throws Throwable { Callable task = () -> { try { Object result = invocation.proceed(); - if (result instanceof Future) { - return ((Future) result).get(); + if (result instanceof Future future) { + return future.get(); } } catch (ExecutionException ex) { diff --git a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyBeanRegistrationAotProcessor.java b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyBeanRegistrationAotProcessor.java index 7f71d3a3fc47..23e5cf64b88b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyBeanRegistrationAotProcessor.java +++ b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyBeanRegistrationAotProcessor.java @@ -75,7 +75,7 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe @Nullable private String getTargetBeanName(BeanDefinition beanDefinition) { Object value = beanDefinition.getPropertyValues().get("targetBeanName"); - return (value instanceof String ? (String) value : null); + return (value instanceof String targetBeanName ? targetBeanName : null); } @Nullable diff --git a/spring-aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java index ccb008ebd013..f9efdc469ada 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -82,8 +82,8 @@ public void setBeanFactory(BeanFactory beanFactory) { } private void resetAdviceMonitor() { - if (this.beanFactory instanceof ConfigurableBeanFactory) { - this.adviceMonitor = ((ConfigurableBeanFactory) this.beanFactory).getSingletonMutex(); + if (this.beanFactory instanceof ConfigurableBeanFactory cbf) { + this.adviceMonitor = cbf.getSingletonMutex(); } else { this.adviceMonitor = new Object(); diff --git a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java index 0b821ce76dc5..dcc8670f8706 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java @@ -107,8 +107,8 @@ public static boolean isCglibProxy(@Nullable Object object) { public static Class getTargetClass(Object candidate) { Assert.notNull(candidate, "Candidate object must not be null"); Class result = null; - if (candidate instanceof TargetClassAware) { - result = ((TargetClassAware) candidate).getTargetClass(); + if (candidate instanceof TargetClassAware targetClassAware) { + result = targetClassAware.getTargetClass(); } if (result == null) { result = (isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass()); @@ -234,8 +234,8 @@ public static boolean canApply(Pointcut pc, Class targetClass, boolean hasInt } IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; - if (methodMatcher instanceof IntroductionAwareMethodMatcher) { - introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; + if (methodMatcher instanceof IntroductionAwareMethodMatcher iamm) { + introductionAwareMethodMatcher = iamm; } Set> classes = new LinkedHashSet<>(); @@ -281,8 +281,8 @@ public static boolean canApply(Advisor advisor, Class targetClass) { * @return whether the pointcut can apply on any method */ public static boolean canApply(Advisor advisor, Class targetClass, boolean hasIntroductions) { - if (advisor instanceof IntroductionAdvisor) { - return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass); + if (advisor instanceof IntroductionAdvisor ia) { + return ia.getClassFilter().matches(targetClass); } else if (advisor instanceof PointcutAdvisor pca) { return canApply(pca.getPointcut(), targetClass, hasIntroductions); diff --git a/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java b/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java index 6f624ca14b7d..b0c2c65c6dba 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -109,9 +109,9 @@ public boolean matches(Class clazz) { } @Override - public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof UnionClassFilter && - ObjectUtils.nullSafeEquals(this.filters, ((UnionClassFilter) other).filters))); + public boolean equals(@Nullable Object obj) { + return (this == obj || (obj instanceof UnionClassFilter that && + ObjectUtils.nullSafeEquals(this.filters, that.filters))); } @Override @@ -150,9 +150,9 @@ public boolean matches(Class clazz) { } @Override - public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof IntersectionClassFilter && - ObjectUtils.nullSafeEquals(this.filters, ((IntersectionClassFilter) other).filters))); + public boolean equals(@Nullable Object obj) { + return (this == obj || (obj instanceof IntersectionClassFilter that && + ObjectUtils.nullSafeEquals(this.filters, that.filters))); } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/support/ComposablePointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/ComposablePointcut.java index 61b9e9fcbb91..1ac7fd211731 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/ComposablePointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/ComposablePointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java index 5fefc059f36a..8b70d019fe8f 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -56,7 +56,7 @@ public class DefaultIntroductionAdvisor implements IntroductionAdvisor, ClassFil * @see #addInterface */ public DefaultIntroductionAdvisor(Advice advice) { - this(advice, (advice instanceof IntroductionInfo ? (IntroductionInfo) advice : null)); + this(advice, (advice instanceof IntroductionInfo introductionInfo ? introductionInfo : null)); } /** @@ -112,8 +112,8 @@ public Class[] getInterfaces() { @Override public void validateInterfaces() throws IllegalArgumentException { for (Class ifc : this.interfaces) { - if (this.advice instanceof DynamicIntroductionAdvice && - !((DynamicIntroductionAdvice) this.advice).implementsInterface(ifc)) { + if (this.advice instanceof DynamicIntroductionAdvice dynamicIntroductionAdvice && + !dynamicIntroductionAdvice.implementsInterface(ifc)) { throw new IllegalArgumentException("DynamicIntroductionAdvice [" + this.advice + "] " + "does not implement interface [" + ifc.getName() + "] specified for introduction"); } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/DelegatePerTargetObjectIntroductionInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/support/DelegatePerTargetObjectIntroductionInterceptor.java index 299a474a64e0..0f3d511c0dea 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/DelegatePerTargetObjectIntroductionInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/DelegatePerTargetObjectIntroductionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -98,8 +98,8 @@ public Object invoke(MethodInvocation mi) throws Throwable { // Massage return value if possible: if the delegate returned itself, // we really want to return the proxy. - if (retVal == delegate && mi instanceof ProxyMethodInvocation) { - retVal = ((ProxyMethodInvocation) mi).getProxy(); + if (retVal == delegate && mi instanceof ProxyMethodInvocation pmi) { + retVal = pmi.getProxy(); } return retVal; } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/DelegatingIntroductionInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/support/DelegatingIntroductionInterceptor.java index c7c5981fd38f..bd9647a0f462 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/DelegatingIntroductionInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/DelegatingIntroductionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -112,8 +112,8 @@ public Object invoke(MethodInvocation mi) throws Throwable { // Massage return value if possible: if the delegate returned itself, // we really want to return the proxy. - if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) { - Object proxy = ((ProxyMethodInvocation) mi).getProxy(); + if (retVal == this.delegate && mi instanceof ProxyMethodInvocation pmi) { + Object proxy = pmi.getProxy(); if (mi.getMethod().getReturnType().isInstance(proxy)) { retVal = proxy; } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java b/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java index 416697ef2456..19bf5adbb076 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -94,8 +94,8 @@ public static MethodMatcher intersection(MethodMatcher mm1, MethodMatcher mm2) { */ public static boolean matches(MethodMatcher mm, Method method, Class targetClass, boolean hasIntroductions) { Assert.notNull(mm, "MethodMatcher must not be null"); - return (mm instanceof IntroductionAwareMethodMatcher ? - ((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions) : + return (mm instanceof IntroductionAwareMethodMatcher iamm ? + iamm.matches(method, targetClass, hasIntroductions) : mm.matches(method, targetClass)); } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java index d3d762465d51..5c81794a92b5 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -100,9 +100,9 @@ protected boolean isMatch(String methodName, String mappedName) { @Override - public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof NameMatchMethodPointcut && - this.mappedNames.equals(((NameMatchMethodPointcut) other).mappedNames))); + public boolean equals(@Nullable Object obj) { + return (this == obj || (obj instanceof NameMatchMethodPointcut that && + this.mappedNames.equals(that.mappedNames))); } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java b/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java index 3809a303797b..cd371a805625 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -45,9 +45,9 @@ public boolean matches(Class candidate) { } @Override - public boolean equals(Object other) { - return (this == other || (other instanceof RootClassFilter && - this.clazz.equals(((RootClassFilter) other).clazz))); + public boolean equals(Object obj) { + return (this == obj || (obj instanceof RootClassFilter that && + this.clazz.equals(that.clazz))); } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java index 743e4c7bc1b2..5957514cc7f4 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java index b3c564743b8d..0824efc4678b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -77,12 +77,12 @@ protected void destroyPrototypeInstance(Object target) { if (logger.isDebugEnabled()) { logger.debug("Destroying instance of bean '" + getTargetBeanName() + "'"); } - if (getBeanFactory() instanceof ConfigurableBeanFactory) { - ((ConfigurableBeanFactory) getBeanFactory()).destroyBean(getTargetBeanName(), target); + if (getBeanFactory() instanceof ConfigurableBeanFactory cbf) { + cbf.destroyBean(getTargetBeanName(), target); } - else if (target instanceof DisposableBean) { + else if (target instanceof DisposableBean disposableBean) { try { - ((DisposableBean) target).destroy(); + disposableBean.destroy(); } catch (Throwable ex) { logger.warn("Destroy method on bean with name '" + getTargetBeanName() + "' threw an exception", ex); diff --git a/spring-aop/src/main/java/org/springframework/aop/target/HotSwappableTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/HotSwappableTargetSource.java index 5bfda76d8ece..3bf01a6b652b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/HotSwappableTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/HotSwappableTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -100,9 +100,9 @@ public synchronized Object swap(Object newTarget) throws IllegalArgumentExceptio * objects are equal. */ @Override - public boolean equals(Object other) { - return (this == other || (other instanceof HotSwappableTargetSource && - this.target.equals(((HotSwappableTargetSource) other).target))); + public boolean equals(Object obj) { + return (this == obj || (obj instanceof HotSwappableTargetSource that && + this.target.equals(that.target))); } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/target/SingletonTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/SingletonTargetSource.java index 5e1cafc1b605..be36de34cbb9 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/SingletonTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/SingletonTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-aop/src/main/java/org/springframework/aop/target/dynamic/BeanFactoryRefreshableTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/dynamic/BeanFactoryRefreshableTargetSource.java index 66f84a1c9864..0d5988f6b52b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/dynamic/BeanFactoryRefreshableTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/dynamic/BeanFactoryRefreshableTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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. @@ -63,7 +63,7 @@ public BeanFactoryRefreshableTargetSource(BeanFactory beanFactory, String beanNa */ @Override protected final Object freshTarget() { - return this.obtainFreshBean(this.beanFactory, this.beanName); + return obtainFreshBean(this.beanFactory, this.beanName); } /** diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java index 847ef4f38044..ff167db17bad 100644 --- a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java @@ -195,7 +195,7 @@ void perThisAspect() throws Exception { Advised advised = (Advised) itb; // Will be ExposeInvocationInterceptor, synthetic instantiation advisor, 2 method advisors - assertThat(advised.getAdvisors().length).isEqualTo(4); + assertThat(advised.getAdvisors()).hasSize(4); ReflectiveAspectJAdvisorFactory.SyntheticInstantiationAdvisor sia = (ReflectiveAspectJAdvisorFactory.SyntheticInstantiationAdvisor) advised.getAdvisors()[1]; assertThat(sia.getPointcut().getMethodMatcher().matches(TestBean.class.getMethod("getSpouse"), null)).isTrue(); @@ -231,7 +231,7 @@ void perTypeWithinAspect() throws Exception { Advised advised = (Advised) itb; // Will be ExposeInvocationInterceptor, synthetic instantiation advisor, 2 method advisors - assertThat(advised.getAdvisors().length).isEqualTo(4); + assertThat(advised.getAdvisors()).hasSize(4); ReflectiveAspectJAdvisorFactory.SyntheticInstantiationAdvisor sia = (ReflectiveAspectJAdvisorFactory.SyntheticInstantiationAdvisor) advised.getAdvisors()[1]; assertThat(sia.getPointcut().getMethodMatcher().matches(TestBean.class.getMethod("getSpouse"), null)).isTrue(); @@ -366,7 +366,7 @@ void introductionAdvisorExcludedFromTargetImplementingInterface() { new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(), "someBean")), CannotBeUnlocked.class).isEmpty()).isTrue(); assertThat(AopUtils.findAdvisorsThatCanApply(getFixture().getAdvisors( - new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class).size()).isEqualTo(2); + new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2); } @Test diff --git a/spring-aop/src/test/java/org/springframework/aop/support/AbstractRegexpMethodPointcutTests.java b/spring-aop/src/test/java/org/springframework/aop/support/AbstractRegexpMethodPointcutTests.java deleted file mode 100644 index 9970b496e2db..000000000000 --- a/spring-aop/src/test/java/org/springframework/aop/support/AbstractRegexpMethodPointcutTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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. - * 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.aop.support; - -import java.io.IOException; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.testfixture.beans.TestBean; -import org.springframework.core.testfixture.io.SerializationTestUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rod Johnson - * @author Dmitriy Kopylenko - * @author Chris Beams - */ -public abstract class AbstractRegexpMethodPointcutTests { - - private AbstractRegexpMethodPointcut rpc; - - @BeforeEach - public void setUp() { - rpc = getRegexpMethodPointcut(); - } - - protected abstract AbstractRegexpMethodPointcut getRegexpMethodPointcut(); - - @Test - public void testNoPatternSupplied() throws Exception { - noPatternSuppliedTests(rpc); - } - - @Test - public void testSerializationWithNoPatternSupplied() throws Exception { - rpc = SerializationTestUtils.serializeAndDeserialize(rpc); - noPatternSuppliedTests(rpc); - } - - protected void noPatternSuppliedTests(AbstractRegexpMethodPointcut rpc) throws Exception { - assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isFalse(); - assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse(); - assertThat(rpc.getPatterns().length).isEqualTo(0); - } - - @Test - public void testExactMatch() throws Exception { - rpc.setPattern("java.lang.Object.hashCode"); - exactMatchTests(rpc); - rpc = SerializationTestUtils.serializeAndDeserialize(rpc); - exactMatchTests(rpc); - } - - protected void exactMatchTests(AbstractRegexpMethodPointcut rpc) throws Exception { - // assumes rpc.setPattern("java.lang.Object.hashCode"); - assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue(); - assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isTrue(); - assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse(); - } - - @Test - public void testSpecificMatch() throws Exception { - rpc.setPattern("java.lang.String.hashCode"); - assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue(); - assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isFalse(); - } - - @Test - public void testWildcard() throws Exception { - rpc.setPattern(".*Object.hashCode"); - assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isTrue(); - assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse(); - } - - @Test - public void testWildcardForOneClass() throws Exception { - rpc.setPattern("java.lang.Object.*"); - assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue(); - assertThat(rpc.matches(Object.class.getMethod("wait"), String.class)).isTrue(); - } - - @Test - public void testMatchesObjectClass() throws Exception { - rpc.setPattern("java.lang.Object.*"); - assertThat(rpc.matches(Exception.class.getMethod("hashCode"), IOException.class)).isTrue(); - // Doesn't match a method from Throwable - assertThat(rpc.matches(Exception.class.getMethod("getMessage"), Exception.class)).isFalse(); - } - - @Test - public void testWithExclusion() throws Exception { - this.rpc.setPattern(".*get.*"); - this.rpc.setExcludedPattern(".*Age.*"); - assertThat(this.rpc.matches(TestBean.class.getMethod("getName"), TestBean.class)).isTrue(); - assertThat(this.rpc.matches(TestBean.class.getMethod("getAge"), TestBean.class)).isFalse(); - } - -} diff --git a/spring-aop/src/test/java/org/springframework/aop/support/JdkRegexpMethodPointcutTests.java b/spring-aop/src/test/java/org/springframework/aop/support/JdkRegexpMethodPointcutTests.java index 1e4c139274c3..055dad7a8f6a 100644 --- a/spring-aop/src/test/java/org/springframework/aop/support/JdkRegexpMethodPointcutTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/support/JdkRegexpMethodPointcutTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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,14 +16,93 @@ package org.springframework.aop.support; +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.testfixture.beans.TestBean; +import org.springframework.core.testfixture.io.SerializationTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + /** + * @author Rod Johnson + * @author Dmitriy Kopylenko + * @author Chris Beams * @author Dmitriy Kopylenko */ -public class JdkRegexpMethodPointcutTests extends AbstractRegexpMethodPointcutTests { +class JdkRegexpMethodPointcutTests { + + private AbstractRegexpMethodPointcut rpc = new JdkRegexpMethodPointcut(); + + + @Test + void noPatternSupplied() throws Exception { + noPatternSuppliedTests(rpc); + } + + @Test + void serializationWithNoPatternSupplied() throws Exception { + rpc = SerializationTestUtils.serializeAndDeserialize(rpc); + noPatternSuppliedTests(rpc); + } + + private void noPatternSuppliedTests(AbstractRegexpMethodPointcut rpc) throws Exception { + assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isFalse(); + assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse(); + assertThat(rpc.getPatterns()).isEmpty(); + } + + @Test + void exactMatch() throws Exception { + rpc.setPattern("java.lang.Object.hashCode"); + exactMatchTests(rpc); + rpc = SerializationTestUtils.serializeAndDeserialize(rpc); + exactMatchTests(rpc); + } + + private void exactMatchTests(AbstractRegexpMethodPointcut rpc) throws Exception { + // assumes rpc.setPattern("java.lang.Object.hashCode"); + assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue(); + assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isTrue(); + assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse(); + } + + @Test + void specificMatch() throws Exception { + rpc.setPattern("java.lang.String.hashCode"); + assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue(); + assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isFalse(); + } + + @Test + void wildcard() throws Exception { + rpc.setPattern(".*Object.hashCode"); + assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isTrue(); + assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse(); + } + + @Test + void wildcardForOneClass() throws Exception { + rpc.setPattern("java.lang.Object.*"); + assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue(); + assertThat(rpc.matches(Object.class.getMethod("wait"), String.class)).isTrue(); + } + + @Test + void matchesObjectClass() throws Exception { + rpc.setPattern("java.lang.Object.*"); + assertThat(rpc.matches(Exception.class.getMethod("hashCode"), IOException.class)).isTrue(); + // Doesn't match a method from Throwable + assertThat(rpc.matches(Exception.class.getMethod("getMessage"), Exception.class)).isFalse(); + } - @Override - protected AbstractRegexpMethodPointcut getRegexpMethodPointcut() { - return new JdkRegexpMethodPointcut(); + @Test + void withExclusion() throws Exception { + this.rpc.setPattern(".*get.*"); + this.rpc.setExcludedPattern(".*Age.*"); + assertThat(this.rpc.matches(TestBean.class.getMethod("getName"), TestBean.class)).isTrue(); + assertThat(this.rpc.matches(TestBean.class.getMethod("getAge"), TestBean.class)).isFalse(); } } diff --git a/spring-aop/src/test/java/org/springframework/aop/target/PrototypeBasedTargetSourceTests.java b/spring-aop/src/test/java/org/springframework/aop/target/PrototypeBasedTargetSourceTests.java index d2dcf75ee9ca..6e857ffe6b18 100644 --- a/spring-aop/src/test/java/org/springframework/aop/target/PrototypeBasedTargetSourceTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/target/PrototypeBasedTargetSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-aspects/spring-aspects.gradle b/spring-aspects/spring-aspects.gradle index bd4e49ff638b..b9ebcf86e0a0 100644 --- a/spring-aspects/spring-aspects.gradle +++ b/spring-aspects/spring-aspects.gradle @@ -11,10 +11,16 @@ sourceSets.test.java.srcDirs = files() compileAspectj { sourceCompatibility "17" targetCompatibility "17" + ajcOptions { + compilerArgs += "-parameters" + } } compileTestAspectj { sourceCompatibility "17" targetCompatibility "17" + ajcOptions { + compilerArgs += "-parameters" + } } dependencies { diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java index c306b6b899b1..ea4d3b052b2c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java @@ -61,7 +61,7 @@ *

Mainly for internal use within the framework, but to some degree also * useful for application classes. Consider * Apache Commons BeanUtils, - * BULL - Bean Utils Light Library, + * BULL - Bean Utils Light Library, * or similar third-party frameworks for more comprehensive bean utilities. * * @author Rod Johnson @@ -882,7 +882,7 @@ public static T instantiateClass(Constructor ctor, Object... args) List parameters = kotlinConstructor.getParameters(); Assert.isTrue(args.length <= parameters.size(), - "Number of provided arguments should be less of equals than number of constructor parameters"); + "Number of provided arguments must be less than or equal to the number of constructor parameters"); if (parameters.isEmpty()) { return kotlinConstructor.call(); } diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java index 04df5ad2cfac..e7b8d426e9f1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -270,7 +270,7 @@ private CachedIntrospectionResults(Class beanClass) throws BeansException { // Only allow URL attribute introspection, not content resolution continue; } - if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType())) { + if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType(), beanClass)) { // Ignore read-only properties such as ClassLoader - no need to bind to those continue; } @@ -320,7 +320,8 @@ private void introspectInterfaces(Class beanClass, Class currClass, Set 0 || method.getReturnType() == void.class || - isInvalidReadOnlyPropertyType(method.getReturnType())) { + isInvalidReadOnlyPropertyType(method.getReturnType(), method.getDeclaringClass())) { return false; } try { @@ -366,10 +367,11 @@ private boolean isPlainAccessor(Method method) { } } - private boolean isInvalidReadOnlyPropertyType(@Nullable Class returnType) { - return (returnType != null && (AutoCloseable.class.isAssignableFrom(returnType) || - ClassLoader.class.isAssignableFrom(returnType) || - ProtectionDomain.class.isAssignableFrom(returnType))); + private boolean isInvalidReadOnlyPropertyType(@Nullable Class returnType, Class beanClass) { + return (returnType != null && (ClassLoader.class.isAssignableFrom(returnType) || + ProtectionDomain.class.isAssignableFrom(returnType) || + (AutoCloseable.class.isAssignableFrom(returnType) && + !AutoCloseable.class.isAssignableFrom(beanClass)))); } diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java index 2d39e2ef096c..53ed5a156bef 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-beans/src/main/java/org/springframework/beans/SimpleBeanInfoFactory.java b/spring-beans/src/main/java/org/springframework/beans/SimpleBeanInfoFactory.java index 6719b156a510..75c9a699bb69 100644 --- a/spring-beans/src/main/java/org/springframework/beans/SimpleBeanInfoFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/SimpleBeanInfoFactory.java @@ -16,6 +16,7 @@ package org.springframework.beans; +import java.beans.BeanDescriptor; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; @@ -52,6 +53,10 @@ public BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException { PropertyDescriptorUtils.determineBasicProperties(beanClass); return new SimpleBeanInfo() { + @Override + public BeanDescriptor getBeanDescriptor() { + return new BeanDescriptor(beanClass); + } @Override public PropertyDescriptor[] getPropertyDescriptors() { return pds.toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java index 13e765893dc4..eed896e1b4f0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -112,7 +112,7 @@ public Field getField() { * @since 5.0 */ protected final MethodParameter obtainMethodParameter() { - Assert.state(this.methodParameter != null, "Neither Field nor MethodParameter"); + Assert.state(this.methodParameter != null, "MethodParameter is not available"); return this.methodParameter; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java index bf88c509cd71..edb0381dbd00 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -18,6 +18,7 @@ import java.lang.annotation.Annotation; import java.util.Map; +import java.util.Set; import org.springframework.beans.BeansException; import org.springframework.core.ResolvableType; @@ -322,7 +323,8 @@ Map getBeansOfType(@Nullable Class type, boolean includeNonSin * (at class, interface or factory method level of the specified bean) * @return the names of all matching beans * @since 4.0 - * @see #findAnnotationOnBean + * @see #getBeansWithAnnotation(Class) + * @see #findAnnotationOnBean(String, Class) */ String[] getBeanNamesForAnnotation(Class annotationType); @@ -337,7 +339,9 @@ Map getBeansOfType(@Nullable Class type, boolean includeNonSin * keys and the corresponding bean instances as values * @throws BeansException if a bean could not be created * @since 3.0 - * @see #findAnnotationOnBean + * @see #findAnnotationOnBean(String, Class) + * @see #findAnnotationOnBean(String, Class, boolean) + * @see #findAllAnnotationsOnBean(String, Class, boolean) */ Map getBeansWithAnnotation(Class annotationType) throws BeansException; @@ -351,8 +355,10 @@ Map getBeansOfType(@Nullable Class type, boolean includeNonSin * @return the annotation of the given type if found, or {@code null} otherwise * @throws NoSuchBeanDefinitionException if there is no bean with the given name * @since 3.0 - * @see #getBeanNamesForAnnotation - * @see #getBeansWithAnnotation + * @see #findAnnotationOnBean(String, Class, boolean) + * @see #findAllAnnotationsOnBean(String, Class, boolean) + * @see #getBeanNamesForAnnotation(Class) + * @see #getBeansWithAnnotation(Class) * @see #getType(String) */ @Nullable @@ -371,8 +377,10 @@ A findAnnotationOnBean(String beanName, Class annotati * @return the annotation of the given type if found, or {@code null} otherwise * @throws NoSuchBeanDefinitionException if there is no bean with the given name * @since 5.3.14 - * @see #getBeanNamesForAnnotation - * @see #getBeansWithAnnotation + * @see #findAnnotationOnBean(String, Class) + * @see #findAllAnnotationsOnBean(String, Class, boolean) + * @see #getBeanNamesForAnnotation(Class) + * @see #getBeansWithAnnotation(Class) * @see #getType(String, boolean) */ @Nullable @@ -380,4 +388,24 @@ A findAnnotationOnBean( String beanName, Class annotationType, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException; + /** + * Find all {@link Annotation} instances of {@code annotationType} on the specified + * bean, traversing its interfaces and superclasses if no annotation can be found on + * the given class itself, as well as checking the bean's factory method (if any). + * @param beanName the name of the bean to look for annotations on + * @param annotationType the type of annotation to look for + * (at class, interface or factory method level of the specified bean) + * @param allowFactoryBeanInit whether a {@code FactoryBean} may get initialized + * just for the purpose of determining its object type + * @return the set of annotations of the given type found (potentially empty) + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @since 6.0 + * @see #getBeanNamesForAnnotation(Class) + * @see #findAnnotationOnBean(String, Class, boolean) + * @see #getType(String, boolean) + */ + Set findAllAnnotationsOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException; + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index eabf7380b57a..f59e60ea30c9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -25,7 +25,6 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -46,6 +45,7 @@ import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.support.ClassHintUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValues; @@ -53,7 +53,6 @@ import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.UnsatisfiedDependencyException; @@ -77,7 +76,6 @@ import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; -import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; @@ -613,39 +611,10 @@ private MergedAnnotation findAutowiredAnnotation(AccessibleObject ao) { * @return whether the annotation indicates that a dependency is required */ protected boolean determineRequiredStatus(MergedAnnotation ann) { - return determineRequiredStatus(ann. asMap( - mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType()))); - } - - /** - * Determine if the annotated field or method requires its dependency. - *

A 'required' dependency means that autowiring should fail when no beans - * are found. Otherwise, the autowiring process will simply bypass the field - * or method when no beans are found. - * @param ann the Autowired annotation - * @return whether the annotation indicates that a dependency is required - * @deprecated since 5.2, in favor of {@link #determineRequiredStatus(MergedAnnotation)} - */ - @Deprecated - protected boolean determineRequiredStatus(AnnotationAttributes ann) { - return (!ann.containsKey(this.requiredParameterName) || + return (ann.getValue(this.requiredParameterName).isEmpty() || this.requiredParameterValue == ann.getBoolean(this.requiredParameterName)); } - /** - * Obtain all beans of the given type as autowire candidates. - * @param type the type of the bean - * @return the target beans, or an empty Collection if no bean of this type is found - * @throws BeansException if bean retrieval failed - */ - protected Map findAutowireCandidates(Class type) throws BeansException { - if (this.beanFactory == null) { - throw new IllegalStateException("No BeanFactory configured - " + - "override the getBeanOfType method or specify the 'beanFactory' property"); - } - return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type); - } - /** * Register the specified bean as dependent on the autowired beans. */ @@ -685,11 +654,10 @@ private abstract static class AutowiredElement extends InjectionMetadata.Injecte protected final boolean required; - protected AutowiredElement(Member member, PropertyDescriptor pd, boolean required) { + protected AutowiredElement(Member member, @Nullable PropertyDescriptor pd, boolean required) { super(member, pd); this.required = required; } - } @@ -926,10 +894,8 @@ private static class AotContribution implements BeanRegistrationAotContribution this.candidateResolver = candidateResolver; } - @Override - public void applyTo(GenerationContext generationContext, - BeanRegistrationCode beanRegistrationCode) { + public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { GeneratedClass generatedClass = generationContext.getGeneratedClasses() .addForFeatureComponent("Autowiring", this.target, type -> { type.addJavadoc("Autowiring for {@link $T}.", this.target); @@ -1003,15 +969,13 @@ private CodeBlock generateMethodStatementForMethod(ClassName targetClassName, (!required) ? "forMethod" : "forRequiredMethod"); code.add("($S", method.getName()); if (method.getParameterCount() > 0) { - code.add(", $L", - generateParameterTypesCode(method.getParameterTypes())); + code.add(", $L", generateParameterTypesCode(method.getParameterTypes())); } code.add(")"); AccessControl accessControl = AccessControl.forMember(method); if (!accessControl.isAccessibleFrom(targetClassName)) { hints.reflection().registerMethod(method, ExecutableMode.INVOKE); - code.add(".resolveAndInvoke($L, $L)", REGISTERED_BEAN_PARAMETER, - INSTANCE_PARAMETER); + code.add(".resolveAndInvoke($L, $L)", REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER); } else { hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT); @@ -1038,16 +1002,14 @@ private void registerHints(RuntimeHints runtimeHints) { boolean required = autowiredElement.required; Member member = autowiredElement.getMember(); if (member instanceof Field field) { - DependencyDescriptor dependencyDescriptor = new DependencyDescriptor( - field, required); + DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(field, required); registerProxyIfNecessary(runtimeHints, dependencyDescriptor); } if (member instanceof Method method) { Class[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { MethodParameter methodParam = new MethodParameter(method, i); - DependencyDescriptor dependencyDescriptor = new DependencyDescriptor( - methodParam, required); + DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(methodParam, required); registerProxyIfNecessary(runtimeHints, dependencyDescriptor); } } @@ -1055,10 +1017,12 @@ private void registerHints(RuntimeHints runtimeHints) { } private void registerProxyIfNecessary(RuntimeHints runtimeHints, DependencyDescriptor dependencyDescriptor) { - Class proxyType = this.candidateResolver - .getLazyResolutionProxyClass(dependencyDescriptor, null); - if (proxyType != null && Proxy.isProxyClass(proxyType)) { - runtimeHints.proxies().registerJdkProxy(proxyType.getInterfaces()); + if (this.candidateResolver != null) { + Class proxyClass = + this.candidateResolver.getLazyResolutionProxyClass(dependencyDescriptor, null); + if (proxyClass != null) { + ClassHintUtils.registerProxyIfNecessary(proxyClass, runtimeHints); + } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredArgumentsCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredArgumentsCodeGenerator.java index ab6b6a85a2ec..8a8f152cd7ca 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredArgumentsCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredArgumentsCodeGenerator.java @@ -28,14 +28,11 @@ /** * Code generator to apply {@link AutowiredArguments}. - *

- * Generates code in the form:

{@code
- * args.get(0), args.get(1)
- * }
or
{@code
- * args.get(0, String.class), args.get(1, Integer.class)
- * }
- *

- * The simpler form is only used if the target method or constructor is + * + *

Generates code in the form: {@code args.get(0), args.get(1)} or + * {@code args.get(0, String.class), args.get(1, Integer.class)} + * + *

The simpler form is only used if the target method or constructor is * unambiguous. * * @author Phillip Webb diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredFieldValueResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredFieldValueResolver.java index cd329aa9f13e..8237ccab4ed0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredFieldValueResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredFieldValueResolver.java @@ -39,8 +39,8 @@ * AOT-processed applications as a targeted alternative to the * {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor * AutowiredAnnotationBeanPostProcessor}. - *

- * When resolving arguments in a native image, the {@link Field} being used must + * + *

When resolving arguments in a native image, the {@link Field} being used must * be marked with an {@link ExecutableMode#INTROSPECT introspection} hint so * that field annotations can be read. Full {@link ExecutableMode#INVOKE * invocation} hints are only required if the diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java index dea4d1922674..7b878e6fc8f4 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java @@ -42,8 +42,8 @@ * AOT-processed applications as a targeted alternative to the * {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor * AutowiredAnnotationBeanPostProcessor}. - *

- * When resolving arguments in a native image, the {@link Method} being used + * + *

When resolving arguments in a native image, the {@link Method} being used * must be marked with an {@link ExecutableMode#INTROSPECT introspection} hint * so that field annotations can be read. Full {@link ExecutableMode#INVOKE * invocation} hints are only required if the @@ -205,9 +205,8 @@ private AutowiredArguments resolveArguments(RegisteredBean registeredBean, private Method getMethod(RegisteredBean registeredBean) { Method method = ReflectionUtils.findMethod(registeredBean.getBeanClass(), this.methodName, this.parameterTypes); - Assert.notNull(method, - () -> String.format( - "Method '%s' with parameter types [%s] declared on %s", + Assert.notNull(method, () -> + "Method '%s' with parameter types [%s] declared on %s could not be found.".formatted( this.methodName, toCommaSeparatedNames(this.parameterTypes), registeredBean.getBeanClass().getName())); return method; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGenerator.java index 4ef2debf2d6e..94951852b943 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGenerator.java @@ -134,10 +134,12 @@ private static GeneratedClass lookupGeneratedClass(GenerationContext generationC type.addJavadoc("Bean definitions for {@link $T}", topLevelClassName); type.addModifiers(Modifier.PUBLIC); }); + List names = target.simpleNames(); if (names.size() == 1) { return generatedClass; } + List namesToProcess = names.subList(1, names.size()); ClassName currentTargetClassName = topLevelClassName; GeneratedClass tmp = generatedClass; @@ -148,8 +150,7 @@ private static GeneratedClass lookupGeneratedClass(GenerationContext generationC return tmp; } - private static GeneratedClass createInnerClass(GeneratedClass generatedClass, - String name, ClassName target) { + private static GeneratedClass createInnerClass(GeneratedClass generatedClass, String name, ClassName target) { return generatedClass.getOrAdd(name, type -> { type.addJavadoc("Bean definitions for {@link $T}", target); type.addModifiers(Modifier.PUBLIC, Modifier.STATIC); @@ -167,16 +168,16 @@ private BeanRegistrationCodeFragments getCodeFragments(GenerationContext generat return codeFragments; } - private GeneratedMethod generateBeanDefinitionMethod( - GenerationContext generationContext, ClassName className, - GeneratedMethods generatedMethods, BeanRegistrationCodeFragments codeFragments, - Modifier modifier) { + private GeneratedMethod generateBeanDefinitionMethod(GenerationContext generationContext, + ClassName className, GeneratedMethods generatedMethods, + BeanRegistrationCodeFragments codeFragments, Modifier modifier) { BeanRegistrationCodeGenerator codeGenerator = new BeanRegistrationCodeGenerator( className, generatedMethods, this.registeredBean, this.constructorOrFactoryMethod, codeFragments); - this.aotContributions.forEach(aotContribution -> aotContribution - .applyTo(generationContext, codeGenerator)); + + this.aotContributions.forEach(aotContribution -> aotContribution.applyTo(generationContext, codeGenerator)); + return generatedMethods.add("getBeanDefinition", method -> { method.addJavadoc("Get the $L definition for '$L'", (!this.registeredBean.isInnerBean()) ? "bean" : "inner-bean", diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactory.java index 73f34e71abe7..580a0533cedb 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorFactory.java @@ -87,8 +87,7 @@ class BeanDefinitionMethodGeneratorFactory { * {@link BeanRegistrationAotProcessor} provided contributions. * @param registeredBean the registered bean * @param currentPropertyName the property name that this bean belongs to - * @return a new {@link BeanDefinitionMethodGenerator} instance or - * {@code null} + * @return a new {@link BeanDefinitionMethodGenerator} instance or {@code null} */ @Nullable BeanDefinitionMethodGenerator getBeanDefinitionMethodGenerator( @@ -97,8 +96,7 @@ BeanDefinitionMethodGenerator getBeanDefinitionMethodGenerator( if (isExcluded(registeredBean)) { return null; } - List contributions = getAotContributions( - registeredBean); + List contributions = getAotContributions(registeredBean); return new BeanDefinitionMethodGenerator(this, registeredBean, currentPropertyName, contributions); } @@ -110,8 +108,7 @@ BeanDefinitionMethodGenerator getBeanDefinitionMethodGenerator( * {@link BeanDefinitionMethodGenerator} will include all * {@link BeanRegistrationAotProcessor} provided contributions. * @param registeredBean the registered bean - * @return a new {@link BeanDefinitionMethodGenerator} instance or - * {@code null} + * @return a new {@link BeanDefinitionMethodGenerator} instance or {@code null} */ @Nullable BeanDefinitionMethodGenerator getBeanDefinitionMethodGenerator(RegisteredBean registeredBean) { @@ -142,19 +139,16 @@ private boolean isImplicitlyExcluded(RegisteredBean registeredBean) { } if (BeanRegistrationAotProcessor.class.isAssignableFrom(beanClass)) { BeanRegistrationAotProcessor processor = this.aotProcessors.findByBeanName(registeredBean.getBeanName()); - return (processor == null) || processor.isBeanExcludedFromAotProcessing(); + return (processor == null || processor.isBeanExcludedFromAotProcessing()); } return false; } - private List getAotContributions( - RegisteredBean registeredBean) { - + private List getAotContributions(RegisteredBean registeredBean) { String beanName = registeredBean.getBeanName(); List contributions = new ArrayList<>(); for (BeanRegistrationAotProcessor aotProcessor : this.aotProcessors) { - BeanRegistrationAotContribution contribution = aotProcessor - .processAheadOfTime(registeredBean); + BeanRegistrationAotContribution contribution = aotProcessor.processAheadOfTime(registeredBean); if (contribution != null) { logger.trace(LogMessage.format( "Adding bean registration AOT contribution %S from %S to '%S'", 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 dd4d4867a3db..ae9441e0bcd9 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 @@ -61,8 +61,8 @@ * handles resolution of {@link AutowiredArguments} if necessary. Typically used * in AOT-processed applications as a targeted alternative to the reflection * based injection. - *

- * If no {@code generator} is provided, reflection is used to instantiate the + * + *

If no {@code generator} is provided, reflection is used to instantiate the * bean instance, and full {@link ExecutableMode#INVOKE invocation} hints are * contributed. Multiple generator callback styles are supported: *

    @@ -75,7 +75,7 @@ *
  • A supplier when a method reference can be used
  • *
* Generator callbacks handle checked exceptions so that the caller does not - * have to deal with it. + * have to deal with them. * * @author Phillip Webb * @author Stephane Nicoll @@ -113,10 +113,8 @@ public static BeanInstanceSupplier forConstructor( Class... parameterTypes) { Assert.notNull(parameterTypes, "'parameterTypes' must not be null"); - Assert.noNullElements(parameterTypes, - "'parameterTypes' must not contain null elements"); - return new BeanInstanceSupplier<>( - new ConstructorLookup(parameterTypes), null, null); + Assert.noNullElements(parameterTypes, "'parameterTypes' must not contain null elements"); + return new BeanInstanceSupplier<>(new ConstructorLookup(parameterTypes), null, null); } /** @@ -134,8 +132,7 @@ public static BeanInstanceSupplier forFactoryMethod( Assert.notNull(declaringClass, "'declaringClass' must not be null"); Assert.hasText(methodName, "'methodName' must not be empty"); Assert.notNull(parameterTypes, "'parameterTypes' must not be null"); - Assert.noNullElements(parameterTypes, - "'parameterTypes' must not contain null elements"); + Assert.noNullElements(parameterTypes, "'parameterTypes' must not contain null elements"); return new BeanInstanceSupplier<>( new FactoryMethodLookup(declaringClass, methodName, parameterTypes), null, null); @@ -158,7 +155,7 @@ ExecutableLookup getLookup() { public BeanInstanceSupplier withGenerator( ThrowingBiFunction generator) { Assert.notNull(generator, "'generator' must not be null"); - return new BeanInstanceSupplier(this.lookup, generator, this.shortcuts); + return new BeanInstanceSupplier<>(this.lookup, generator, this.shortcuts); } /** @@ -172,8 +169,8 @@ public BeanInstanceSupplier withGenerator( public BeanInstanceSupplier withGenerator( ThrowingFunction generator) { Assert.notNull(generator, "'generator' must not be null"); - return new BeanInstanceSupplier<>(this.lookup, (registeredBean, args) -> - generator.apply(registeredBean), this.shortcuts); + return new BeanInstanceSupplier<>(this.lookup, + (registeredBean, args) -> generator.apply(registeredBean), this.shortcuts); } /** @@ -186,8 +183,8 @@ public BeanInstanceSupplier withGenerator( */ public BeanInstanceSupplier withGenerator(ThrowingSupplier generator) { Assert.notNull(generator, "'generator' must not be null"); - return new BeanInstanceSupplier<>(this.lookup, (registeredBean, args) -> - generator.get(), this.shortcuts); + return new BeanInstanceSupplier<>(this.lookup, + (registeredBean, args) -> generator.get(), this.shortcuts); } /** @@ -199,7 +196,7 @@ public BeanInstanceSupplier withGenerator(ThrowingSupplier generator) { * that uses the shortcuts */ public BeanInstanceSupplier withShortcuts(String... beanNames) { - return new BeanInstanceSupplier(this.lookup, this.generator, beanNames); + return new BeanInstanceSupplier<>(this.lookup, this.generator, beanNames); } @Override @@ -208,11 +205,10 @@ public T get(RegisteredBean registeredBean) throws Exception { Executable executable = this.lookup.get(registeredBean); AutowiredArguments arguments = resolveArguments(registeredBean, executable); if (this.generator != null) { - return invokeBeanSupplier(executable, () -> - this.generator.apply(registeredBean, arguments)); + return invokeBeanSupplier(executable, () -> this.generator.apply(registeredBean, arguments)); } - return invokeBeanSupplier(executable, () -> - instantiate(registeredBean.getBeanFactory(), executable, arguments.toArray())); + return invokeBeanSupplier(executable, + () -> instantiate(registeredBean.getBeanFactory(), executable, arguments.toArray())); } private T invokeBeanSupplier(Executable executable, ThrowingSupplier beanSupplier) { @@ -247,19 +243,15 @@ AutowiredArguments resolveArguments(RegisteredBean registeredBean) { return resolveArguments(registeredBean, this.lookup.get(registeredBean)); } - private AutowiredArguments resolveArguments(RegisteredBean registeredBean, - Executable executable) { - - Assert.isInstanceOf(AbstractAutowireCapableBeanFactory.class, - registeredBean.getBeanFactory()); + private AutowiredArguments resolveArguments(RegisteredBean registeredBean,Executable executable) { + Assert.isInstanceOf(AbstractAutowireCapableBeanFactory.class, registeredBean.getBeanFactory()); String beanName = registeredBean.getBeanName(); Class beanClass = registeredBean.getBeanClass(); - AbstractAutowireCapableBeanFactory beanFactory = (AbstractAutowireCapableBeanFactory) registeredBean - .getBeanFactory(); - RootBeanDefinition mergedBeanDefinition = registeredBean - .getMergedBeanDefinition(); - int startIndex = (executable instanceof Constructor constructor - && ClassUtils.isInnerClass(constructor.getDeclaringClass())) ? 1 : 0; + AbstractAutowireCapableBeanFactory beanFactory = + (AbstractAutowireCapableBeanFactory) registeredBean.getBeanFactory(); + RootBeanDefinition mergedBeanDefinition = registeredBean.getMergedBeanDefinition(); + int startIndex = (executable instanceof Constructor constructor && + ClassUtils.isInnerClass(constructor.getDeclaringClass())) ? 1 : 0; int parameterCount = executable.getParameterCount(); Object[] resolved = new Object[parameterCount - startIndex]; Assert.isTrue(this.shortcuts == null || this.shortcuts.length == resolved.length, @@ -269,10 +261,8 @@ private AutowiredArguments resolveArguments(RegisteredBean registeredBean, beanName, mergedBeanDefinition); for (int i = startIndex; i < parameterCount; i++) { MethodParameter parameter = getMethodParameter(executable, i); - DependencyDescriptor dependencyDescriptor = new DependencyDescriptor( - parameter, true); - String shortcut = (this.shortcuts != null) ? this.shortcuts[i - startIndex] - : null; + DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(parameter, true); + String shortcut = (this.shortcuts != null) ? this.shortcuts[i - startIndex] : null; if (shortcut != null) { dependencyDescriptor = new ShortcutDependencyDescriptor( dependencyDescriptor, shortcut, beanClass); @@ -303,13 +293,10 @@ private ConstructorArgumentValues resolveArgumentValues( ConstructorArgumentValues resolved = new ConstructorArgumentValues(); if (mergedBeanDefinition.hasConstructorArgumentValues()) { BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver( - beanFactory, beanName, mergedBeanDefinition, - beanFactory.getTypeConverter()); - ConstructorArgumentValues values = mergedBeanDefinition - .getConstructorArgumentValues(); + beanFactory, beanName, mergedBeanDefinition, beanFactory.getTypeConverter()); + ConstructorArgumentValues values = mergedBeanDefinition.getConstructorArgumentValues(); values.getIndexedArgumentValues().forEach((index, valueHolder) -> { - ValueHolder resolvedValue = resolveArgumentValue(valueResolver, - valueHolder); + ValueHolder resolvedValue = resolveArgumentValue(valueResolver, valueHolder); resolved.addIndexedArgumentValue(index, resolvedValue); }); } @@ -333,15 +320,14 @@ private ValueHolder resolveArgumentValue(BeanDefinitionValueResolver resolver, @Nullable private Object resolveArgument(AbstractAutowireCapableBeanFactory beanFactory, String beanName, Set autowiredBeans, MethodParameter parameter, - DependencyDescriptor dependencyDescriptor, - @Nullable ValueHolder argumentValue) { + DependencyDescriptor dependencyDescriptor, @Nullable ValueHolder argumentValue) { TypeConverter typeConverter = beanFactory.getTypeConverter(); Class parameterType = parameter.getParameterType(); if (argumentValue != null) { - return (!argumentValue.isConverted()) ? typeConverter - .convertIfNecessary(argumentValue.getValue(), parameterType) - : argumentValue.getConvertedValue(); + return (!argumentValue.isConverted()) ? + typeConverter.convertIfNecessary(argumentValue.getValue(), parameterType) : + argumentValue.getConvertedValue(); } try { try { @@ -387,9 +373,7 @@ private T instantiate(ConfigurableBeanFactory beanFactory, Executable executable "Unsupported executable " + executable.getClass().getName()); } - private Object instantiate(Constructor constructor, Object[] arguments) - throws Exception { - + private Object instantiate(Constructor constructor, Object[] arguments) throws Exception { Class declaringClass = constructor.getDeclaringClass(); if (ClassUtils.isInnerClass(declaringClass)) { Object enclosingInstance = createInstance(declaringClass.getEnclosingClass()); @@ -428,6 +412,10 @@ private Object createInstance(Class clazz) throws Exception { } + private static String toCommaSeparatedNames(Class... parameterTypes) { + return Arrays.stream(parameterTypes).map(Class::getName).collect(Collectors.joining(", ")); + } + /** * Performs lookup of the {@link Executable}. */ @@ -435,11 +423,6 @@ static abstract class ExecutableLookup { abstract Executable get(RegisteredBean registeredBean); - final String toCommaSeparatedNames(Class... parameterTypes) { - return Arrays.stream(parameterTypes).map(Class::getName) - .collect(Collectors.joining(", ")); - } - } @@ -460,20 +443,20 @@ private static class ConstructorLookup extends ExecutableLookup { public Executable get(RegisteredBean registeredBean) { Class beanClass = registeredBean.getBeanClass(); try { - Class[] actualParameterTypes = (!ClassUtils.isInnerClass(beanClass)) - ? this.parameterTypes : ObjectUtils.addObjectToArray( - this.parameterTypes, beanClass.getEnclosingClass(), 0); + Class[] actualParameterTypes = (!ClassUtils.isInnerClass(beanClass)) ? + this.parameterTypes : ObjectUtils.addObjectToArray( + this.parameterTypes, beanClass.getEnclosingClass(), 0); return beanClass.getDeclaredConstructor(actualParameterTypes); } catch (NoSuchMethodException ex) { - throw new IllegalArgumentException(String.format( - "%s cannot be found on %s", this, beanClass.getName()), ex); + throw new IllegalArgumentException( + "%s cannot be found on %s".formatted(this, beanClass.getName()), ex); } } @Override public String toString() { - return String.format("Constructor with parameter types [%s]", + return "Constructor with parameter types [%s]".formatted( toCommaSeparatedNames(this.parameterTypes)); } @@ -508,14 +491,13 @@ public Executable get(RegisteredBean registeredBean) { Method get() { Method method = ReflectionUtils.findMethod(this.declaringClass, this.methodName, this.parameterTypes); - Assert.notNull(method, () -> String.format("%s cannot be found", this)); + Assert.notNull(method, () -> "%s cannot be found".formatted(this)); return method; } @Override public String toString() { - return String.format( - "Factory method '%s' with parameter types [%s] declared on %s", + return "Factory method '%s' with parameter types [%s] declared on %s".formatted( this.methodName, toCommaSeparatedNames(this.parameterTypes), this.declaringClass); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeFragmentsDecorator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeFragmentsDecorator.java index 8351f9ce4baa..e4ff961262e1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeFragmentsDecorator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeFragmentsDecorator.java @@ -33,7 +33,7 @@ /** * A {@link BeanRegistrationCodeFragments} decorator implementation. Typically * used when part of the default code fragments have to customized, by extending - * this class and use it as part of + * this class and using it as part of * {@link BeanRegistrationAotContribution#withCustomCodeFragments(UnaryOperator)}. * * @author Phillip Webb @@ -42,7 +42,6 @@ */ public class BeanRegistrationCodeFragmentsDecorator implements BeanRegistrationCodeFragments { - private final BeanRegistrationCodeFragments delegate; @@ -52,9 +51,7 @@ protected BeanRegistrationCodeFragmentsDecorator(BeanRegistrationCodeFragments d } @Override - public ClassName getTarget(RegisteredBean registeredBean, - Executable constructorOrFactoryMethod) { - + public ClassName getTarget(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) { return this.delegate.getTarget(registeredBean, constructorOrFactoryMethod); } @@ -64,23 +61,19 @@ public CodeBlock generateNewBeanDefinitionCode(GenerationContext generationConte return this.delegate.generateNewBeanDefinitionCode(generationContext, beanType, beanRegistrationCode); - } @Override - public CodeBlock generateSetBeanDefinitionPropertiesCode( - GenerationContext generationContext, + public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition, Predicate attributeFilter) { return this.delegate.generateSetBeanDefinitionPropertiesCode( generationContext, beanRegistrationCode, beanDefinition, attributeFilter); - } @Override - public CodeBlock generateSetBeanInstanceSupplierCode( - GenerationContext generationContext, + public CodeBlock generateSetBeanInstanceSupplierCode(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode, CodeBlock instanceSupplierCode, List postProcessors) { @@ -90,8 +83,8 @@ public CodeBlock generateSetBeanInstanceSupplierCode( @Override public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext, - BeanRegistrationCode beanRegistrationCode, - Executable constructorOrFactoryMethod, boolean allowDirectSupplierShortcut) { + BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod, + boolean allowDirectSupplierShortcut) { return this.delegate.generateInstanceSupplierCode(generationContext, beanRegistrationCode, constructorOrFactoryMethod, allowDirectSupplierShortcut); @@ -101,8 +94,7 @@ public CodeBlock generateInstanceSupplierCode(GenerationContext generationContex public CodeBlock generateReturnCode(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { - return this.delegate.generateReturnCode(generationContext, - beanRegistrationCode); + return this.delegate.generateReturnCode(generationContext, beanRegistrationCode); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java index e29f60a42b06..bda1d4713519 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java @@ -30,7 +30,6 @@ import org.springframework.javapoet.ClassName; import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.MethodSpec; -import org.springframework.util.MultiValueMap; /** * AOT contribution from a {@link BeanRegistrationsAotProcessor} used to @@ -38,6 +37,7 @@ * * @author Phillip Webb * @author Sebastien Deleuze + * @author Stephane Nicoll * @since 6.0 * @see BeanRegistrationsAotProcessor */ @@ -46,16 +46,10 @@ class BeanRegistrationsAotContribution private static final String BEAN_FACTORY_PARAMETER_NAME = "beanFactory"; - private final Map registrations; - - private final MultiValueMap aliases; - - - BeanRegistrationsAotContribution( - Map registrations, MultiValueMap aliases) { + private final Map registrations; + BeanRegistrationsAotContribution(Map registrations) { this.registrations = registrations; - this.aliases = aliases; } @@ -86,8 +80,8 @@ private void generateRegisterBeanDefinitionsMethod(MethodSpec.Builder method, method.addParameter(DefaultListableBeanFactory.class, BEAN_FACTORY_PARAMETER_NAME); CodeBlock.Builder code = CodeBlock.builder(); - this.registrations.forEach((beanName, beanDefinitionMethodGenerator) -> { - MethodReference beanDefinitionMethod = beanDefinitionMethodGenerator + this.registrations.forEach((beanName, registration) -> { + MethodReference beanDefinitionMethod = registration.methodGenerator .generateBeanDefinitionMethod(generationContext, beanRegistrationsCode); CodeBlock methodInvocation = beanDefinitionMethod.toInvokeCodeBlock( @@ -105,12 +99,22 @@ private void generateRegisterAliasesMethod(MethodSpec.Builder method) { method.addParameter(DefaultListableBeanFactory.class, BEAN_FACTORY_PARAMETER_NAME); CodeBlock.Builder code = CodeBlock.builder(); - this.aliases.forEach((beanName, beanAliases) -> - beanAliases.forEach(alias -> code.addStatement("$L.registerAlias($S, $S)", BEAN_FACTORY_PARAMETER_NAME, - beanName, alias))); + this.registrations.forEach((beanName, registration) -> { + for (String alias : registration.aliases) { + code.addStatement("$L.registerAlias($S, $S)", + BEAN_FACTORY_PARAMETER_NAME, beanName, alias); + } + }); method.addCode(code.build()); } + /** + * Gather the necessary information to register a particular bean. + * @param methodGenerator the {@link BeanDefinitionMethodGenerator} to use + * @param aliases the bean aliases, if any + */ + record Registration(BeanDefinitionMethodGenerator methodGenerator, String[] aliases) {} + /** * {@link BeanRegistrationsCode} with generation support. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessor.java index ac3657f21a89..63bcf2fccce7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessor.java @@ -19,10 +19,10 @@ import java.util.LinkedHashMap; import java.util.Map; +import org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.Registration; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.RegisteredBean; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; +import org.springframework.lang.Nullable; /** * {@link BeanFactoryInitializationAotProcessor} that contributes code to @@ -30,31 +30,32 @@ * * @author Phillip Webb * @author Sebastien Deleuze + * @author Stephane Nicoll * @since 6.0 */ class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProcessor { @Override + @Nullable public BeanRegistrationsAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory = new BeanDefinitionMethodGeneratorFactory(beanFactory); - Map registrations = new LinkedHashMap<>(); - MultiValueMap aliases = new LinkedMultiValueMap<>(); + Map registrations = new LinkedHashMap<>(); + for (String beanName : beanFactory.getBeanDefinitionNames()) { RegisteredBean registeredBean = RegisteredBean.of(beanFactory, beanName); - BeanDefinitionMethodGenerator beanDefinitionMethodGenerator = beanDefinitionMethodGeneratorFactory - .getBeanDefinitionMethodGenerator(registeredBean); + BeanDefinitionMethodGenerator beanDefinitionMethodGenerator = + beanDefinitionMethodGeneratorFactory.getBeanDefinitionMethodGenerator(registeredBean); if (beanDefinitionMethodGenerator != null) { - registrations.put(beanName, beanDefinitionMethodGenerator); - } - for (String alias : beanFactory.getAliases(beanName)) { - aliases.add(beanName, alias); + registrations.put(beanName, new Registration(beanDefinitionMethodGenerator, + beanFactory.getAliases(beanName))); } } + if (registrations.isEmpty()) { return null; } - return new BeanRegistrationsAotContribution(registrations, aliases); + return new BeanRegistrationsAotContribution(registrations); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java index 9ae4f0db3a24..7f8104531d6e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java @@ -46,16 +46,16 @@ * Internal code generator to create an {@link InstanceSupplier}, usually in * the form of a {@link BeanInstanceSupplier} that retains the executable * that is used to instantiate the bean. - *

- * Generated code is usually a method reference that generate the - * {@link BeanInstanceSupplier}, but some shortcut can be used as well such - * as: + * + *

Generated code is usually a method reference that generate the + * {@link BeanInstanceSupplier}, but some shortcut can be used as well such as: *

  * {@code InstanceSupplier.of(TheGeneratedClass::getMyBeanInstance);}
  * 
* * @author Phillip Webb * @author Stephane Nicoll + * @author Juergen Hoeller * @since 6.0 */ class InstanceSupplierCodeGenerator { @@ -90,9 +90,7 @@ class InstanceSupplierCodeGenerator { } - CodeBlock generateCode(RegisteredBean registeredBean, - Executable constructorOrFactoryMethod) { - + CodeBlock generateCode(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) { if (constructorOrFactoryMethod instanceof Constructor constructor) { return generateCodeForConstructor(registeredBean, constructor); } @@ -108,6 +106,7 @@ private CodeBlock generateCodeForConstructor(RegisteredBean registeredBean, Cons Class beanClass = registeredBean.getBeanClass(); Class declaringClass = constructor.getDeclaringClass(); boolean dependsOnBean = ClassUtils.isInnerClass(declaringClass); + Visibility accessVisibility = getAccessVisibility(registeredBean, constructor); if (accessVisibility != Visibility.PRIVATE) { return generateCodeForAccessibleConstructor(beanName, beanClass, constructor, @@ -121,6 +120,7 @@ private CodeBlock generateCodeForAccessibleConstructor(String beanName, Class this.generationContext.getRuntimeHints().reflection().registerConstructor( constructor, ExecutableMode.INTROSPECT); + if (!dependsOnBean && constructor.getParameterCount() == 0) { if (!this.allowDirectSupplierShortcut) { return CodeBlock.of("$T.using($T::new)", InstanceSupplier.class, declaringClass); @@ -130,6 +130,7 @@ private CodeBlock generateCodeForAccessibleConstructor(String beanName, Class } return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class, declaringClass); } + GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> buildGetInstanceMethodForConstructor(method, beanName, beanClass, constructor, declaringClass, dependsOnBean, PRIVATE_STATIC)); @@ -141,6 +142,7 @@ private CodeBlock generateCodeForInaccessibleConstructor(String beanName, this.generationContext.getRuntimeHints().reflection() .registerConstructor(constructor, ExecutableMode.INVOKE); + GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> { method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addModifiers(PRIVATE_STATIC); @@ -148,6 +150,7 @@ private CodeBlock generateCodeForInaccessibleConstructor(String beanName, int parameterOffset = (!dependsOnBean) ? 0 : 1; method.addStatement(generateResolverForConstructor(beanClass, constructor, parameterOffset)); }); + return generateReturnStatement(generatedMethod); } @@ -158,14 +161,17 @@ private void buildGetInstanceMethodForConstructor(MethodSpec.Builder method, method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addModifiers(modifiers); method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass)); + int parameterOffset = (!dependsOnBean) ? 0 : 1; CodeBlock.Builder code = CodeBlock.builder(); code.add(generateResolverForConstructor(beanClass, constructor, parameterOffset)); boolean hasArguments = constructor.getParameterCount() > 0; + CodeBlock arguments = hasArguments ? new AutowiredArgumentsCodeGenerator(declaringClass, constructor) .generateCode(constructor.getParameterTypes(), parameterOffset) : NO_ARGS; + CodeBlock newInstance = generateNewInstanceCodeForConstructor(dependsOnBean, declaringClass, arguments); code.add(generateWithGeneratorCode(hasArguments, newInstance)); method.addStatement(code.build()); @@ -184,92 +190,99 @@ private CodeBlock generateNewInstanceCodeForConstructor(boolean dependsOnBean, if (!dependsOnBean) { return CodeBlock.of("new $T($L)", declaringClass, args); } + return CodeBlock.of("$L.getBeanFactory().getBean($T.class).new $L($L)", REGISTERED_BEAN_PARAMETER_NAME, declaringClass.getEnclosingClass(), declaringClass.getSimpleName(), args); } - private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, - Method factoryMethod) { - + private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, Method factoryMethod) { String beanName = registeredBean.getBeanName(); - Class beanClass = registeredBean.getBeanClass(); - Class declaringClass = ClassUtils - .getUserClass(factoryMethod.getDeclaringClass()); + Class declaringClass = ClassUtils.getUserClass(factoryMethod.getDeclaringClass()); boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers()); + Visibility accessVisibility = getAccessVisibility(registeredBean, factoryMethod); if (accessVisibility != Visibility.PRIVATE) { return generateCodeForAccessibleFactoryMethod( - beanName, beanClass, factoryMethod, declaringClass, dependsOnBean); + beanName, factoryMethod, declaringClass, dependsOnBean); } - return generateCodeForInaccessibleFactoryMethod(beanName, beanClass, factoryMethod, declaringClass); + return generateCodeForInaccessibleFactoryMethod(beanName, factoryMethod, declaringClass); } private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName, - Class beanClass, Method factoryMethod, Class declaringClass, boolean dependsOnBean) { + Method factoryMethod, Class declaringClass, boolean dependsOnBean) { this.generationContext.getRuntimeHints().reflection().registerMethod( factoryMethod, ExecutableMode.INTROSPECT); + if (!dependsOnBean && factoryMethod.getParameterCount() == 0) { + Class suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType()); CodeBlock.Builder code = CodeBlock.builder(); code.add("$T.<$T>forFactoryMethod($T.class, $S)", BeanInstanceSupplier.class, - beanClass, declaringClass, factoryMethod.getName()); + suppliedType, declaringClass, factoryMethod.getName()); code.add(".withGenerator($T::$L)", declaringClass, factoryMethod.getName()); return code.build(); } + GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> - buildGetInstanceMethodForFactoryMethod(method, beanName, beanClass, factoryMethod, + buildGetInstanceMethodForFactoryMethod(method, beanName, factoryMethod, declaringClass, dependsOnBean, PRIVATE_STATIC)); return generateReturnStatement(getInstanceMethod); } - private CodeBlock generateCodeForInaccessibleFactoryMethod(String beanName, Class beanClass, - Method factoryMethod, Class declaringClass) { + private CodeBlock generateCodeForInaccessibleFactoryMethod( + String beanName, Method factoryMethod, Class declaringClass) { this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INVOKE); GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> { + Class suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType()); method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addModifiers(PRIVATE_STATIC); - method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass)); + method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType)); method.addStatement(generateInstanceSupplierForFactoryMethod( - beanClass, factoryMethod, declaringClass, factoryMethod.getName())); + factoryMethod, suppliedType, declaringClass, factoryMethod.getName())); }); return generateReturnStatement(getInstanceMethod); } private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method, - String beanName, Class beanClass, Method factoryMethod, Class declaringClass, + String beanName, Method factoryMethod, Class declaringClass, boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) { String factoryMethodName = factoryMethod.getName(); + Class suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType()); + method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addModifiers(modifiers); - method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass)); + method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType)); + CodeBlock.Builder code = CodeBlock.builder(); code.add(generateInstanceSupplierForFactoryMethod( - beanClass, factoryMethod, declaringClass, factoryMethodName)); + factoryMethod, suppliedType, declaringClass, factoryMethodName)); + boolean hasArguments = factoryMethod.getParameterCount() > 0; CodeBlock arguments = hasArguments ? new AutowiredArgumentsCodeGenerator(declaringClass, factoryMethod) .generateCode(factoryMethod.getParameterTypes()) : NO_ARGS; + CodeBlock newInstance = generateNewInstanceCodeForMethod( dependsOnBean, declaringClass, factoryMethodName, arguments); code.add(generateWithGeneratorCode(hasArguments, newInstance)); method.addStatement(code.build()); } - private CodeBlock generateInstanceSupplierForFactoryMethod(Class beanClass, - Method factoryMethod, Class declaringClass, String factoryMethodName) { + private CodeBlock generateInstanceSupplierForFactoryMethod(Method factoryMethod, + Class suppliedType, Class declaringClass, String factoryMethodName) { if (factoryMethod.getParameterCount() == 0) { return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S)", - BeanInstanceSupplier.class, beanClass, declaringClass, - factoryMethodName); + BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName); } + CodeBlock parameterTypes = generateParameterTypesCode(factoryMethod.getParameterTypes(), 0); return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S, $L)", - BeanInstanceSupplier.class, beanClass, declaringClass, factoryMethodName, parameterTypes); + BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName, parameterTypes); } private CodeBlock generateNewInstanceCodeForMethod(boolean dependsOnBean, @@ -288,9 +301,9 @@ private CodeBlock generateReturnStatement(GeneratedMethod generatedMethod) { } private CodeBlock generateWithGeneratorCode(boolean hasArguments, CodeBlock newInstance) { - CodeBlock lambdaArguments = (hasArguments - ? CodeBlock.of("($L, $L)", REGISTERED_BEAN_PARAMETER_NAME, ARGS_PARAMETER_NAME) - : CodeBlock.of("($L)", REGISTERED_BEAN_PARAMETER_NAME)); + CodeBlock lambdaArguments = (hasArguments ? + CodeBlock.of("($L, $L)", REGISTERED_BEAN_PARAMETER_NAME, ARGS_PARAMETER_NAME) : + CodeBlock.of("($L)", REGISTERED_BEAN_PARAMETER_NAME)); Builder code = CodeBlock.builder(); code.add("\n"); code.indent().indent(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/ResolvableTypeCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/ResolvableTypeCodeGenerator.java index e259af65b8a9..e7b715dd006c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/ResolvableTypeCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/ResolvableTypeCodeGenerator.java @@ -45,7 +45,7 @@ private static CodeBlock generateCode(ResolvableType resolvableType, boolean all return CodeBlock.of("$T.NONE", ResolvableType.class); } Class type = ClassUtils.getUserClass(resolvableType.toClass()); - if (resolvableType.hasGenerics()) { + if (resolvableType.hasGenerics() && !resolvableType.hasUnresolvableGenerics()) { return generateCodeWithGenerics(resolvableType, type); } if (allowClassResult) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java index 836b868c74d6..a02f6ab3dbc3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -360,6 +360,25 @@ public ValueHolder getArgumentValue(int index, @Nullable Class requiredType, return valueHolder; } + /** + * Determine whether at least one argument value refers to a name. + * @since 6.0.3 + * @see ValueHolder#getName() + */ + public boolean containsNamedArgument() { + for (ValueHolder valueHolder : this.indexedArgumentValues.values()) { + if (valueHolder.getName() != null) { + return true; + } + } + for (ValueHolder valueHolder : this.genericArgumentValues) { + if (valueHolder.getName() != null) { + return true; + } + } + return false; + } + /** * Return the number of argument values held in this instance, * counting both indexed and generic argument values. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanReference.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanReference.java index a414c3e78297..fbb748ad3c14 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanReference.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -82,7 +82,7 @@ public RuntimeBeanReference(Class beanType) { * @since 5.2 */ public RuntimeBeanReference(Class beanType, boolean toParent) { - Assert.notNull(beanType, "'beanType' must not be empty"); + Assert.notNull(beanType, "'beanType' must not be null"); this.beanName = beanType.getName(); this.beanType = beanType; this.toParent = toParent; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java index b80727269f75..382068dd3ec9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -143,7 +143,7 @@ public String getTargetTypeName() { } /** - * Return whether this typed String value carries a target type . + * Return whether this typed String value carries a target type. */ public boolean hasTargetType() { return (this.targetType instanceof Class); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java index 536b597e6dd8..01a3ddcde573 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java @@ -184,8 +184,9 @@ protected void process(MatchCallback callback) { protected Yaml createYaml() { LoaderOptions loaderOptions = new LoaderOptions(); loaderOptions.setAllowDuplicateKeys(false); - return new Yaml(new FilteringConstructor(loaderOptions), new Representer(), - new DumperOptions(), loaderOptions); + DumperOptions dumperOptions = new DumperOptions(); + return new Yaml(new FilteringConstructor(loaderOptions), new Representer(dumperOptions), + dumperOptions, loaderOptions); } private boolean process(MatchCallback callback, Yaml yaml, Resource resource) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/serviceloader/AbstractServiceLoaderBasedFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/serviceloader/AbstractServiceLoaderBasedFactoryBean.java index 974940a951c8..3ee514663c68 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/serviceloader/AbstractServiceLoaderBasedFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/serviceloader/AbstractServiceLoaderBasedFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -69,7 +69,7 @@ public void setBeanClassLoader(@Nullable ClassLoader beanClassLoader) { */ @Override protected Object createInstance() { - Assert.notNull(getServiceType(), "Property 'serviceType' is required"); + Assert.state(getServiceType() != null, "Property 'serviceType' is required"); return getObjectToExpose(ServiceLoader.load(getServiceType(), this.beanClassLoader)); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 816619a66b8c..e2aa54aa5a51 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -599,8 +599,8 @@ protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { - if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { - throw (BeanCreationException) ex; + if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) { + throw bce; } else { throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex); @@ -862,9 +862,9 @@ protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefiniti // declaration without instantiating the containing bean at all. BeanDefinition factoryBeanDefinition = getBeanDefinition(factoryBeanName); Class factoryBeanClass; - if (factoryBeanDefinition instanceof AbstractBeanDefinition && - ((AbstractBeanDefinition) factoryBeanDefinition).hasBeanClass()) { - factoryBeanClass = ((AbstractBeanDefinition) factoryBeanDefinition).getBeanClass(); + if (factoryBeanDefinition instanceof AbstractBeanDefinition abstractBeanDefinition && + abstractBeanDefinition.hasBeanClass()) { + factoryBeanClass = abstractBeanDefinition.getBeanClass(); } else { RootBeanDefinition fbmbd = getMergedBeanDefinition(factoryBeanName, factoryBeanDefinition); @@ -975,8 +975,8 @@ private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, Root return (FactoryBean) bw.getWrappedInstance(); } Object beanInstance = getSingleton(beanName, false); - if (beanInstance instanceof FactoryBean) { - return (FactoryBean) beanInstance; + if (beanInstance instanceof FactoryBean factoryBean) { + return factoryBean; } if (isSingletonCurrentlyInCreation(beanName) || (mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) { @@ -1389,11 +1389,7 @@ protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable B } pvs = newPvs; } - - boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); - boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); - - if (hasInstAwareBpps) { + if (hasInstantiationAwareBeanPostProcessors()) { if (pvs == null) { pvs = mbd.getPropertyValues(); } @@ -1405,6 +1401,8 @@ protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable B pvs = pvsToUse; } } + + boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); if (needsDepCheck) { PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); checkDependencies(beanName, mbd, filteredPds, pvs); @@ -1677,8 +1675,8 @@ protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrap } deepCopy.add(pv); } - else if (convertible && originalValue instanceof TypedStringValue && - !((TypedStringValue) originalValue).isDynamic() && + else if (convertible && originalValue instanceof TypedStringValue typedStringValue && + !typedStringValue.isDynamic() && !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) { pv.setConvertedValue(convertedValue); deepCopy.add(pv); @@ -1709,8 +1707,8 @@ else if (convertible && originalValue instanceof TypedStringValue && private Object convertForProperty( @Nullable Object value, String propertyName, BeanWrapper bw, TypeConverter converter) { - if (converter instanceof BeanWrapperImpl) { - return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName); + if (converter instanceof BeanWrapperImpl beanWrapper) { + return beanWrapper.convertForProperty(value, propertyName); } else { PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); @@ -1761,17 +1759,17 @@ protected Object initializeBean(String beanName, Object bean, @Nullable RootBean private void invokeAwareMethods(String beanName, Object bean) { if (bean instanceof Aware) { - if (bean instanceof BeanNameAware) { - ((BeanNameAware) bean).setBeanName(beanName); + if (bean instanceof BeanNameAware beanNameAware) { + beanNameAware.setBeanName(beanName); } - if (bean instanceof BeanClassLoaderAware) { + if (bean instanceof BeanClassLoaderAware beanClassLoaderAware) { ClassLoader bcl = getBeanClassLoader(); if (bcl != null) { - ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl); + beanClassLoaderAware.setBeanClassLoader(bcl); } } - if (bean instanceof BeanFactoryAware) { - ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this); + if (bean instanceof BeanFactoryAware beanFactoryAware) { + beanFactoryAware.setBeanFactory(AbstractAutowireCapableBeanFactory.this); } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 509ef10f066d..a8bcb4a09dba 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -1054,7 +1054,7 @@ public Scope getRegisteredScope(String scopeName) { @Override public void setApplicationStartup(ApplicationStartup applicationStartup) { - Assert.notNull(applicationStartup, "applicationStartup should not be null"); + Assert.notNull(applicationStartup, "applicationStartup must not be null"); this.applicationStartup = applicationStartup; } 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 4985d0bc5be0..e5b364630ff2 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 @@ -232,11 +232,14 @@ public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, Class[] paramTypes = candidate.getParameterTypes(); if (resolvedValues != null) { try { - String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount); - if (paramNames == null) { - ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); - if (pnd != null) { - paramNames = pnd.getParameterNames(candidate); + String[] paramNames = null; + if (resolvedValues.containsNamedArgument()) { + paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount); + if (paramNames == null) { + ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); + if (pnd != null) { + paramNames = pnd.getParameterNames(candidate); + } } } argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, @@ -536,9 +539,11 @@ public BeanWrapper instantiateUsingFactoryMethod( // Resolved constructor arguments: type conversion and/or autowiring necessary. try { String[] paramNames = null; - ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); - if (pnd != null) { - paramNames = pnd.getParameterNames(candidate); + if (resolvedValues != null && resolvedValues.containsNamedArgument()) { + ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); + if (pnd != null) { + paramNames = pnd.getParameterNames(candidate); + } } argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, candidate, autowiring, candidates.size() == 1); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 9e7dacfde841..5c8cea793368 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -729,19 +729,12 @@ public
A findAnnotationOnBean( String beanName, Class annotationType, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException { - return findMergedAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit) - .synthesize(MergedAnnotation::isPresent).orElse(null); - } - - private MergedAnnotation findMergedAnnotationOnBean( - String beanName, Class annotationType, boolean allowFactoryBeanInit) { - Class beanType = getType(beanName, allowFactoryBeanInit); if (beanType != null) { MergedAnnotation annotation = MergedAnnotations.from(beanType, SearchStrategy.TYPE_HIERARCHY).get(annotationType); if (annotation.isPresent()) { - return annotation; + return annotation.synthesize(); } } if (containsBeanDefinition(beanName)) { @@ -753,7 +746,7 @@ private MergedAnnotation findMergedAnnotationOnBean( MergedAnnotation annotation = MergedAnnotations.from(beanClass, SearchStrategy.TYPE_HIERARCHY).get(annotationType); if (annotation.isPresent()) { - return annotation; + return annotation.synthesize(); } } } @@ -763,11 +756,48 @@ private MergedAnnotation findMergedAnnotationOnBean( MergedAnnotation annotation = MergedAnnotations.from(factoryMethod, SearchStrategy.TYPE_HIERARCHY).get(annotationType); if (annotation.isPresent()) { - return annotation; + return annotation.synthesize(); + } + } + } + return null; + } + + @Override + public Set findAllAnnotationsOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + Set annotations = new LinkedHashSet<>(); + Class beanType = getType(beanName, allowFactoryBeanInit); + if (beanType != null) { + MergedAnnotations.from(beanType, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(annotationType) + .filter(MergedAnnotation::isPresent) + .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); + } + if (containsBeanDefinition(beanName)) { + RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); + // Check raw bean class, e.g. in case of a proxy. + if (bd.hasBeanClass() && bd.getFactoryMethodName() == null) { + Class beanClass = bd.getBeanClass(); + if (beanClass != beanType) { + MergedAnnotations.from(beanClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(annotationType) + .filter(MergedAnnotation::isPresent) + .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); } } + // Check annotations declared on factory method, if any. + Method factoryMethod = bd.getResolvedFactoryMethod(); + if (factoryMethod != null) { + MergedAnnotations.from(factoryMethod, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(annotationType) + .filter(MergedAnnotation::isPresent) + .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); + } } - return MergedAnnotation.missing(); + return annotations; } @@ -1312,11 +1342,11 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str Class type = descriptor.getDependencyType(); Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { - if (value instanceof String) { - String strVal = resolveEmbeddedValue((String) value); + if (value instanceof String strValue) { + String resolvedValue = resolveEmbeddedValue(strValue); BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); - value = evaluateBeanDefinitionString(strVal, bd); + value = evaluateBeanDefinitionString(resolvedValue, bd); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); try { @@ -2095,11 +2125,11 @@ public Stream orderedStream() { return resolveStream(true); } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) private Stream resolveStream(boolean ordered) { DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, ordered); Object result = doResolveDependency(descriptorToUse, this.beanName, null, null); - return (result instanceof Stream ? (Stream) result : Stream.of(result)); + return (result instanceof Stream stream ? stream : Stream.of(result)); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java index c291046df364..960f86b7fdb1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -102,6 +102,22 @@ protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, Dependenc } } } + else { + // Pre-existing target type: In case of a generic FactoryBean type, + // unwrap nested generic type when matching a non-FactoryBean type. + Class resolvedClass = targetType.resolve(); + if (resolvedClass != null && FactoryBean.class.isAssignableFrom(resolvedClass)) { + Class typeToBeMatched = dependencyType.resolve(); + if (typeToBeMatched != null && !FactoryBean.class.isAssignableFrom(typeToBeMatched)) { + targetType = targetType.getGeneric(); + if (descriptor.fallbackMatchAllowed()) { + // Matching the Class-based type determination for FactoryBean + // objects in the lazy-determination getType code path below. + targetType = ResolvableType.forClass(targetType.resolve()); + } + } + } + } } if (targetType == null) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java index cd41026d6c74..eddbc7954720 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java @@ -45,7 +45,7 @@ default T getWithException() { } /** - * Gets the supplied instance. + * Get the supplied instance. * @param registeredBean the registered bean requesting the instance * @return the supplied instance * @throws Exception on error @@ -55,7 +55,7 @@ default T getWithException() { /** * Return the factory method that this supplier uses to create the * instance, or {@code null} if it is not known or this supplier uses - * another mean. + * another means. * @return the factory method used to create the instance, or {@code null} */ @Nullable @@ -65,7 +65,7 @@ default Method getFactoryMethod() { /** * Return a composed instance supplier that first obtains the instance from - * this supplier, and then applied the {@code after} function to obtain the + * this supplier and then applies the {@code after} function to obtain the * result. * @param the type of output of the {@code after} function, and of the * composed function @@ -74,8 +74,8 @@ default Method getFactoryMethod() { */ default InstanceSupplier andThen( ThrowingBiFunction after) { - Assert.notNull(after, "After must not be null"); - return new InstanceSupplier() { + Assert.notNull(after, "'after' function must not be null"); + return new InstanceSupplier<>() { @Override public V get(RegisteredBean registeredBean) throws Exception { @@ -119,7 +119,7 @@ static InstanceSupplier using(@Nullable Method factoryMethod, ThrowingSup && instanceSupplier.getFactoryMethod() == factoryMethod) { return instanceSupplier; } - return new InstanceSupplier() { + return new InstanceSupplier<>() { @Override public T get(RegisteredBean registeredBean) throws Exception { @@ -135,10 +135,9 @@ public Method getFactoryMethod() { } /** - * Lambda friendly method that can be used to create a + * Lambda friendly method that can be used to create an * {@link InstanceSupplier} and add post processors in a single call. For - * example: {@code - * InstanceSupplier.of(registeredBean -> ...).andThen(...)}. + * example: {@code InstanceSupplier.of(registeredBean -> ...).andThen(...)}. * @param the type of instance supplied by this supplier * @param instanceSupplier the source instance supplier * @return a new {@link InstanceSupplier} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/PropertiesBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/PropertiesBeanDefinitionReader.java index e85e080a3d3e..ebd2b6a1aa6f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/PropertiesBeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/PropertiesBeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java index e6c0eeee032f..f3d43d107ed6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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,9 +19,11 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; import org.springframework.beans.BeansException; @@ -472,4 +474,13 @@ public A findAnnotationOnBean( return (beanType != null ? AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType) : null); } + @Override + public Set findAllAnnotationsOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException { + + Class beanType = getType(beanName, allowFactoryBeanInit); + return (beanType != null ? + AnnotatedElementUtils.findAllMergedAnnotations(beanType, annotationType) : Collections.emptySet()); + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java index eea87d5524b4..1b348693c9b7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -18,7 +18,6 @@ import java.io.File; import java.io.IOException; -import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -30,6 +29,7 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.lang.Nullable; +import org.springframework.util.ResourceUtils; /** * {@code EntityResolver} implementation that tries to resolve entity references @@ -82,7 +82,7 @@ public InputSource resolveEntity(@Nullable String publicId, @Nullable String sys String resourcePath = null; try { String decodedSystemId = URLDecoder.decode(systemId, StandardCharsets.UTF_8); - String givenUrl = new URL(decodedSystemId).toString(); + String givenUrl = ResourceUtils.toURL(decodedSystemId).toString(); String systemRootUrl = new File("").toURI().toURL().toString(); // Try relative to resource base if currently in system root. if (givenUrl.startsWith(systemRootUrl)) { @@ -110,27 +110,55 @@ public InputSource resolveEntity(@Nullable String publicId, @Nullable String sys } } else if (systemId.endsWith(DTD_SUFFIX) || systemId.endsWith(XSD_SUFFIX)) { - // External dtd/xsd lookup via https even for canonical http declaration - String url = systemId; - if (url.startsWith("http:")) { - url = "https:" + url.substring(5); - } - try { - source = new InputSource(new URL(url).openStream()); - source.setPublicId(publicId); - source.setSystemId(systemId); - } - catch (IOException ex) { - if (logger.isDebugEnabled()) { - logger.debug("Could not resolve XML entity [" + systemId + "] through URL [" + url + "]", ex); - } - // Fall back to the parser's default behavior. - source = null; - } + source = resolveSchemaEntity(publicId, systemId); } } return source; } + /** + * A fallback method for {@link #resolveEntity(String, String)} that is used when a + * "schema" entity (DTD or XSD) cannot be resolved as a local resource. The default + * behavior is to perform remote resolution over HTTPS. + *

Subclasses can override this method to change the default behavior. + *

    + *
  • Return {@code null} to fall back to the parser's + * {@linkplain org.xml.sax.EntityResolver#resolveEntity(String, String) default behavior}.
  • + *
  • Throw an exception to prevent remote resolution of the DTD or XSD.
  • + *
+ * @param publicId the public identifier of the external entity being referenced, + * or null if none was supplied + * @param systemId the system identifier of the external entity being referenced, + * representing the URL of the DTD or XSD + * @return an InputSource object describing the new input source, or null to request + * that the parser open a regular URI connection to the system identifier + * @since 6.0.4 + */ + @Nullable + protected InputSource resolveSchemaEntity(@Nullable String publicId, String systemId) { + InputSource source; + // External dtd/xsd lookup via https even for canonical http declaration + String url = systemId; + if (url.startsWith("http:")) { + url = "https:" + url.substring(5); + } + if (logger.isWarnEnabled()) { + logger.warn("DTD/XSD XML entity [" + systemId + "] not found, falling back to remote https resolution"); + } + try { + source = new InputSource(ResourceUtils.toURL(url).openStream()); + source.setPublicId(publicId); + source.setSystemId(systemId); + } + catch (IOException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Could not resolve XML entity [" + systemId + "] through URL [" + url + "]", ex); + } + // Fall back to the parser's default behavior. + source = null; + } + return source; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java index 0ee0f5e80f60..13226e6ca0db 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -77,7 +77,7 @@ public void setAsText(String text) throws IllegalArgumentException { boolean nioPathCandidate = !text.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX); if (nioPathCandidate && !text.startsWith("/")) { try { - URI uri = new URI(text); + URI uri = ResourceUtils.toURI(text); if (uri.getScheme() != null) { nioPathCandidate = false; // Let's try NIO file system providers via Paths.get(URI) diff --git a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java index 2dd52740b6ae..43ccef4fd29b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -677,7 +677,7 @@ void setPrimitiveProperties() { assertThat(target.getMyLong().longValue()).isEqualTo(Long.MAX_VALUE); assertThat((double) target.getMyPrimitiveFloat()).isCloseTo(Float.MAX_VALUE, within(0.001)); - assertThat((double) target.getMyFloat().floatValue()).isCloseTo(Float.MAX_VALUE, within(0.001)); + assertThat((double) target.getMyFloat()).isCloseTo(Float.MAX_VALUE, within(0.001)); assertThat(target.getMyPrimitiveDouble()).isCloseTo(Double.MAX_VALUE, within(0.001)); assertThat(target.getMyDouble().doubleValue()).isCloseTo(Double.MAX_VALUE, within(0.001)); @@ -1010,11 +1010,11 @@ void setCollectionPropertyNonMatchingType() { accessor.setPropertyValue("list", list); assertThat(target.getCollection()).hasSize(1); assertThat(target.getCollection().containsAll(coll)).isTrue(); - assertThat(target.getSet().size()).isEqualTo(1); + assertThat(target.getSet()).hasSize(1); assertThat(target.getSet().containsAll(set)).isTrue(); - assertThat(target.getSortedSet().size()).isEqualTo(1); + assertThat(target.getSortedSet()).hasSize(1); assertThat(target.getSortedSet().containsAll(sortedSet)).isTrue(); - assertThat(target.getList().size()).isEqualTo(1); + assertThat(target.getList()).hasSize(1); assertThat(target.getList().containsAll(list)).isTrue(); } @@ -1037,11 +1037,11 @@ void setCollectionPropertyWithArrayValue() { accessor.setPropertyValue("list", list.toArray()); assertThat(target.getCollection()).hasSize(1); assertThat(target.getCollection().containsAll(coll)).isTrue(); - assertThat(target.getSet().size()).isEqualTo(1); + assertThat(target.getSet()).hasSize(1); assertThat(target.getSet().containsAll(set)).isTrue(); - assertThat(target.getSortedSet().size()).isEqualTo(1); + assertThat(target.getSortedSet()).hasSize(1); assertThat(target.getSortedSet().containsAll(sortedSet)).isTrue(); - assertThat(target.getList().size()).isEqualTo(1); + assertThat(target.getList()).hasSize(1); assertThat(target.getList().containsAll(list)).isTrue(); } @@ -1064,11 +1064,11 @@ void setCollectionPropertyWithIntArrayValue() { accessor.setPropertyValue("list", new int[]{3}); assertThat(target.getCollection()).hasSize(1); assertThat(target.getCollection().containsAll(coll)).isTrue(); - assertThat(target.getSet().size()).isEqualTo(1); + assertThat(target.getSet()).hasSize(1); assertThat(target.getSet().containsAll(set)).isTrue(); - assertThat(target.getSortedSet().size()).isEqualTo(1); + assertThat(target.getSortedSet()).hasSize(1); assertThat(target.getSortedSet().containsAll(sortedSet)).isTrue(); - assertThat(target.getList().size()).isEqualTo(1); + assertThat(target.getList()).hasSize(1); assertThat(target.getList().containsAll(list)).isTrue(); } @@ -1091,11 +1091,11 @@ void setCollectionPropertyWithIntegerValue() { accessor.setPropertyValue("list", 3); assertThat(target.getCollection()).hasSize(1); assertThat(target.getCollection().containsAll(coll)).isTrue(); - assertThat(target.getSet().size()).isEqualTo(1); + assertThat(target.getSet()).hasSize(1); assertThat(target.getSet().containsAll(set)).isTrue(); - assertThat(target.getSortedSet().size()).isEqualTo(1); + assertThat(target.getSortedSet()).hasSize(1); assertThat(target.getSortedSet().containsAll(sortedSet)).isTrue(); - assertThat(target.getList().size()).isEqualTo(1); + assertThat(target.getList()).hasSize(1); assertThat(target.getList().containsAll(list)).isTrue(); } @@ -1113,15 +1113,16 @@ void setCollectionPropertyWithStringValue() { Set list = new HashSet<>(); list.add("list1"); accessor.setPropertyValue("list", "list1"); - assertThat(target.getSet().size()).isEqualTo(1); + assertThat(target.getSet()).hasSize(1); assertThat(target.getSet().containsAll(set)).isTrue(); - assertThat(target.getSortedSet().size()).isEqualTo(1); + assertThat(target.getSortedSet()).hasSize(1); assertThat(target.getSortedSet().containsAll(sortedSet)).isTrue(); - assertThat(target.getList().size()).isEqualTo(1); + assertThat(target.getList()).hasSize(1); assertThat(target.getList().containsAll(list)).isTrue(); } @Test + @SuppressWarnings("unchecked") void setCollectionPropertyWithStringValueAndCustomEditor() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); @@ -1131,11 +1132,11 @@ void setCollectionPropertyWithStringValueAndCustomEditor() { accessor.setPropertyValue("set", "set1 "); accessor.setPropertyValue("sortedSet", "sortedSet1"); accessor.setPropertyValue("list", "list1 "); - assertThat(target.getSet().size()).isEqualTo(1); + assertThat(target.getSet()).hasSize(1); assertThat(target.getSet().contains("set1")).isTrue(); - assertThat(target.getSortedSet().size()).isEqualTo(1); + assertThat(target.getSortedSet()).hasSize(1); assertThat(target.getSortedSet().contains("sortedSet1")).isTrue(); - assertThat(target.getList().size()).isEqualTo(1); + assertThat(target.getList()).hasSize(1); assertThat(target.getList().contains("list1")).isTrue(); accessor.setPropertyValue("list", Collections.singletonList("list1 ")); @@ -1169,7 +1170,7 @@ void setMapPropertyNonMatchingType() { accessor.setPropertyValue("sortedMap", sortedMap); assertThat(target.getMap()).hasSize(1); assertThat(target.getMap().get("key")).isEqualTo("value"); - assertThat(target.getSortedMap().size()).isEqualTo(1); + assertThat(target.getSortedMap()).hasSize(1); assertThat(target.getSortedMap().get("sortedKey")).isEqualTo("sortedValue"); } diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java index 7510c8bc2765..6be56ed7ecf7 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java @@ -28,7 +28,6 @@ import java.time.DayOfWeek; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Locale; @@ -227,8 +226,8 @@ void copyPropertiesHonorsGenericTypeMatchesFromIntegerToInteger() { IntegerListHolder2 integerListHolder2 = new IntegerListHolder2(); BeanUtils.copyProperties(integerListHolder1, integerListHolder2); - assertThat(integerListHolder1.getList()).containsOnly(42); - assertThat(integerListHolder2.getList()).containsOnly(42); + assertThat(integerListHolder1.getList()).containsExactly(42); + assertThat(integerListHolder2.getList()).containsExactly(42); } /** @@ -236,7 +235,7 @@ void copyPropertiesHonorsGenericTypeMatchesFromIntegerToInteger() { */ @Test void copyPropertiesHonorsGenericTypeMatchesFromWildcardToWildcard() { - List list = Arrays.asList("foo", 42); + List list = List.of("foo", 42); WildcardListHolder1 wildcardListHolder1 = new WildcardListHolder1(); wildcardListHolder1.setList(list); WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2(); @@ -257,8 +256,8 @@ void copyPropertiesHonorsGenericTypeMatchesFromIntegerToWildcard() { WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2(); BeanUtils.copyProperties(integerListHolder1, wildcardListHolder2); - assertThat(integerListHolder1.getList()).containsOnly(42); - assertThat(wildcardListHolder2.getList()).isEqualTo(Arrays.asList(42)); + assertThat(integerListHolder1.getList()).containsExactly(42); + assertThat(wildcardListHolder2.getList()).isEqualTo(List.of(42)); } /** @@ -271,9 +270,8 @@ void copyPropertiesHonorsGenericTypeMatchesForUpperBoundedWildcard() { NumberUpperBoundedWildcardListHolder numberListHolder = new NumberUpperBoundedWildcardListHolder(); BeanUtils.copyProperties(integerListHolder1, numberListHolder); - assertThat(integerListHolder1.getList()).containsOnly(42); - assertThat(numberListHolder.getList()).hasSize(1); - assertThat(numberListHolder.getList().contains(Integer.valueOf(42))).isTrue(); + assertThat(integerListHolder1.getList()).containsExactly(42); + assertThat(numberListHolder.getList()).isEqualTo(List.of(42)); } /** @@ -282,7 +280,7 @@ void copyPropertiesHonorsGenericTypeMatchesForUpperBoundedWildcard() { @Test void copyPropertiesDoesNotCopyFromSuperTypeToSubType() { NumberHolder numberHolder = new NumberHolder(); - numberHolder.setNumber(Integer.valueOf(42)); + numberHolder.setNumber(42); IntegerHolder integerHolder = new IntegerHolder(); BeanUtils.copyProperties(numberHolder, integerHolder); @@ -300,7 +298,7 @@ void copyPropertiesDoesNotHonorGenericTypeMismatches() { LongListHolder longListHolder = new LongListHolder(); BeanUtils.copyProperties(integerListHolder, longListHolder); - assertThat(integerListHolder.getList()).containsOnly(42); + assertThat(integerListHolder.getList()).containsExactly(42); assertThat(longListHolder.getList()).isEmpty(); } @@ -314,13 +312,13 @@ void copyPropertiesDoesNotHonorGenericTypeMismatchesFromSubTypeToSuperType() { NumberListHolder numberListHolder = new NumberListHolder(); BeanUtils.copyProperties(integerListHolder, numberListHolder); - assertThat(integerListHolder.getList()).containsOnly(42); + assertThat(integerListHolder.getList()).containsExactly(42); assertThat(numberListHolder.getList()).isEmpty(); } @Test // gh-26531 void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception { - Order original = new Order("test", Arrays.asList("foo", "bar")); + Order original = new Order("test", List.of("foo", "bar")); // Create a Proxy that loses the generic type information for the getLineItems() method. OrderSummary proxy = proxyOrder(original); diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java index 78524e632b0c..a24a9bc88a3f 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -63,7 +63,7 @@ public void getPropertyValueNullValueInNestedPathNoDefaultConstructor() { @Test public void getPropertyValueAutoGrowArray() { assertNotNull(wrapper.getPropertyValue("array[0]")); - assertThat(bean.getArray().length).isEqualTo(1); + assertThat(bean.getArray()).hasSize(1); assertThat(bean.getArray()[0]).isInstanceOf(Bean.class); } @@ -76,7 +76,7 @@ public void setPropertyValueAutoGrowArray() { @Test public void getPropertyValueAutoGrowArrayBySeveralElements() { assertNotNull(wrapper.getPropertyValue("array[4]")); - assertThat(bean.getArray().length).isEqualTo(5); + assertThat(bean.getArray()).hasSize(5); assertThat(bean.getArray()[0]).isInstanceOf(Bean.class); assertThat(bean.getArray()[1]).isInstanceOf(Bean.class); assertThat(bean.getArray()[2]).isInstanceOf(Bean.class); @@ -91,7 +91,7 @@ public void getPropertyValueAutoGrowArrayBySeveralElements() { @Test public void getPropertyValueAutoGrow2dArray() { assertNotNull(wrapper.getPropertyValue("multiArray[0][0]")); - assertThat(bean.getMultiArray()[0].length).isEqualTo(1); + assertThat(bean.getMultiArray()[0]).hasSize(1); assertThat(bean.getMultiArray()[0][0]).isInstanceOf(Bean.class); } @@ -125,7 +125,7 @@ public void setPropertyValueAutoGrow3dArray() { @Test public void getPropertyValueAutoGrowList() { assertNotNull(wrapper.getPropertyValue("list[0]")); - assertThat(bean.getList().size()).isEqualTo(1); + assertThat(bean.getList()).hasSize(1); assertThat(bean.getList().get(0)).isInstanceOf(Bean.class); } @@ -138,7 +138,7 @@ public void setPropertyValueAutoGrowList() { @Test public void getPropertyValueAutoGrowListBySeveralElements() { assertNotNull(wrapper.getPropertyValue("list[4]")); - assertThat(bean.getList().size()).isEqualTo(5); + assertThat(bean.getList()).hasSize(5); assertThat(bean.getList().get(0)).isInstanceOf(Bean.class); assertThat(bean.getList().get(1)).isInstanceOf(Bean.class); assertThat(bean.getList().get(2)).isInstanceOf(Bean.class); @@ -161,7 +161,7 @@ public void getPropertyValueAutoGrowListFailsAgainstLimit() { @Test public void getPropertyValueAutoGrowMultiDimensionalList() { assertNotNull(wrapper.getPropertyValue("multiList[0][0]")); - assertThat(bean.getMultiList().get(0).size()).isEqualTo(1); + assertThat(bean.getMultiList().get(0)).hasSize(1); assertThat(bean.getMultiList().get(0).get(0)).isInstanceOf(Bean.class); } diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java index 95bd30cfb846..3dad8d527a66 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java @@ -62,7 +62,7 @@ public void testCustomEnumArrayWithSingleValue() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumArray", "VALUE_1"); - assertThat(gb.getCustomEnumArray().length).isEqualTo(1); + assertThat(gb.getCustomEnumArray()).hasSize(1); assertThat(gb.getCustomEnumArray()[0]).isEqualTo(CustomEnum.VALUE_1); } @@ -71,7 +71,7 @@ public void testCustomEnumArrayWithMultipleValues() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumArray", new String[] {"VALUE_1", "VALUE_2"}); - assertThat(gb.getCustomEnumArray().length).isEqualTo(2); + assertThat(gb.getCustomEnumArray()).hasSize(2); assertThat(gb.getCustomEnumArray()[0]).isEqualTo(CustomEnum.VALUE_1); assertThat(gb.getCustomEnumArray()[1]).isEqualTo(CustomEnum.VALUE_2); } @@ -81,7 +81,7 @@ public void testCustomEnumArrayWithMultipleValuesAsCsv() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumArray", "VALUE_1,VALUE_2"); - assertThat(gb.getCustomEnumArray().length).isEqualTo(2); + assertThat(gb.getCustomEnumArray()).hasSize(2); assertThat(gb.getCustomEnumArray()[0]).isEqualTo(CustomEnum.VALUE_1); assertThat(gb.getCustomEnumArray()[1]).isEqualTo(CustomEnum.VALUE_2); } @@ -91,7 +91,7 @@ public void testCustomEnumSetWithSingleValue() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumSet", "VALUE_1"); - assertThat(gb.getCustomEnumSet().size()).isEqualTo(1); + assertThat(gb.getCustomEnumSet()).hasSize(1); assertThat(gb.getCustomEnumSet().contains(CustomEnum.VALUE_1)).isTrue(); } @@ -100,7 +100,7 @@ public void testCustomEnumSetWithMultipleValues() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumSet", new String[] {"VALUE_1", "VALUE_2"}); - assertThat(gb.getCustomEnumSet().size()).isEqualTo(2); + assertThat(gb.getCustomEnumSet()).hasSize(2); assertThat(gb.getCustomEnumSet().contains(CustomEnum.VALUE_1)).isTrue(); assertThat(gb.getCustomEnumSet().contains(CustomEnum.VALUE_2)).isTrue(); } @@ -110,7 +110,7 @@ public void testCustomEnumSetWithMultipleValuesAsCsv() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumSet", "VALUE_1,VALUE_2"); - assertThat(gb.getCustomEnumSet().size()).isEqualTo(2); + assertThat(gb.getCustomEnumSet()).hasSize(2); assertThat(gb.getCustomEnumSet().contains(CustomEnum.VALUE_1)).isTrue(); assertThat(gb.getCustomEnumSet().contains(CustomEnum.VALUE_2)).isTrue(); } @@ -120,7 +120,7 @@ public void testCustomEnumSetWithGetterSetterMismatch() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumSetMismatch", new String[] {"VALUE_1", "VALUE_2"}); - assertThat(gb.getCustomEnumSet().size()).isEqualTo(2); + assertThat(gb.getCustomEnumSet()).hasSize(2); assertThat(gb.getCustomEnumSet().contains(CustomEnum.VALUE_1)).isTrue(); assertThat(gb.getCustomEnumSet().contains(CustomEnum.VALUE_2)).isTrue(); } @@ -132,7 +132,7 @@ public void testStandardEnumSetWithMultipleValues() { bw.setConversionService(new DefaultConversionService()); assertThat(gb.getStandardEnumSet()).isNull(); bw.setPropertyValue("standardEnumSet", new String[] {"VALUE_1", "VALUE_2"}); - assertThat(gb.getStandardEnumSet().size()).isEqualTo(2); + assertThat(gb.getStandardEnumSet()).hasSize(2); assertThat(gb.getStandardEnumSet().contains(CustomEnum.VALUE_1)).isTrue(); assertThat(gb.getStandardEnumSet().contains(CustomEnum.VALUE_2)).isTrue(); } @@ -144,7 +144,7 @@ public void testStandardEnumSetWithAutoGrowing() { bw.setAutoGrowNestedPaths(true); assertThat(gb.getStandardEnumSet()).isNull(); bw.getPropertyValue("standardEnumSet.class"); - assertThat(gb.getStandardEnumSet().size()).isEqualTo(0); + assertThat(gb.getStandardEnumSet()).isEmpty(); } @Test @@ -157,7 +157,7 @@ public void testStandardEnumMapWithMultipleValues() { map.put("VALUE_1", 1); map.put("VALUE_2", 2); bw.setPropertyValue("standardEnumMap", map); - assertThat(gb.getStandardEnumMap().size()).isEqualTo(2); + assertThat(gb.getStandardEnumMap()).hasSize(2); assertThat(gb.getStandardEnumMap().get(CustomEnum.VALUE_1)).isEqualTo(1); assertThat(gb.getStandardEnumMap().get(CustomEnum.VALUE_2)).isEqualTo(2); } @@ -169,7 +169,7 @@ public void testStandardEnumMapWithAutoGrowing() { bw.setAutoGrowNestedPaths(true); assertThat(gb.getStandardEnumMap()).isNull(); bw.setPropertyValue("standardEnumMap[VALUE_1]", 1); - assertThat(gb.getStandardEnumMap().size()).isEqualTo(1); + assertThat(gb.getStandardEnumMap()).hasSize(1); assertThat(gb.getStandardEnumMap().get(CustomEnum.VALUE_1)).isEqualTo(1); } diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java index 760e98cf9351..16bc4de0e2a0 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -282,7 +282,7 @@ void testGenericMapOfMapsWithElementConversion() { gb.setMapOfMaps(map); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("mapOfMaps[mykey][10]", "5"); - assertThat(bw.getPropertyValue("mapOfMaps[mykey][10]")).isEqualTo(Long.valueOf(5)); + assertThat(bw.getPropertyValue("mapOfMaps[mykey][10]")).isEqualTo(5L); assertThat(gb.getMapOfMaps().get("mykey").get(10)).isEqualTo(Long.valueOf(5)); } diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java index 30ab8b1716db..c933699da38a 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java @@ -77,6 +77,21 @@ void aliasedSetterThroughDefaultMethod() { assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom"); } + @Test + void replaceWrappedInstance() { + GetterBean target = new GetterBean(); + BeanWrapperImpl accessor = createAccessor(target); + accessor.setPropertyValue("name", "tom"); + assertThat(target.getAliasedName()).isEqualTo("tom"); + assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom"); + + target = new GetterBean(); + accessor.setWrappedInstance(target); + accessor.setPropertyValue("name", "tom"); + assertThat(target.getAliasedName()).isEqualTo("tom"); + assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom"); + } + @Test void setValidAndInvalidPropertyValuesShouldContainExceptionDetails() { TestBean target = new TestBean(); @@ -216,6 +231,10 @@ void propertyDescriptors() throws Exception { assertThat(accessor.isReadableProperty("inputStream")).isFalse(); assertThat(accessor.isReadableProperty("filename")).isTrue(); assertThat(accessor.isReadableProperty("description")).isTrue(); + + accessor = createAccessor(new ActiveResource()); + + assertThat(accessor.isReadableProperty("resource")).isTrue(); } @Test @@ -376,6 +395,19 @@ public String getObject() { } + @SuppressWarnings("try") + public static class ActiveResource implements AutoCloseable { + + public ActiveResource getResource() { + return this; + } + + @Override + public void close() throws Exception { + } + } + + public static class GetterWithOptional { public TestBean value; diff --git a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java index f3a1b5f1a506..a8ea5fae9eaf 100644 --- a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java @@ -200,11 +200,11 @@ public Class getProp() { } { // always passes BeanInfo info = Introspector.getBeanInfo(Bean.class); - assertThat(info.getPropertyDescriptors().length).isEqualTo(2); + assertThat(info.getPropertyDescriptors()).hasSize(2); } { // failed prior to fix for SPR-9453 BeanInfo info = new ExtendedBeanInfo(Introspector.getBeanInfo(Bean.class)); - assertThat(info.getPropertyDescriptors().length).isEqualTo(2); + assertThat(info.getPropertyDescriptors()).hasSize(2); } } @@ -585,7 +585,7 @@ class C extends B { assertThat(hasReadMethodForProperty(ebi, "foo")).isTrue(); assertThat(hasWriteMethodForProperty(ebi, "foo")).isTrue(); - assertThat(ebi.getPropertyDescriptors().length).isEqualTo(bi.getPropertyDescriptors().length); + assertThat(ebi.getPropertyDescriptors()).hasSize(bi.getPropertyDescriptors().length); } @Test @@ -711,7 +711,7 @@ void propertyCountsMatch() throws Exception { BeanInfo bi = Introspector.getBeanInfo(TestBean.class); BeanInfo ebi = new ExtendedBeanInfo(bi); - assertThat(ebi.getPropertyDescriptors().length).isEqualTo(bi.getPropertyDescriptors().length); + assertThat(ebi.getPropertyDescriptors()).hasSize(bi.getPropertyDescriptors().length); } @Test @@ -731,7 +731,7 @@ class ExtendedTestBean extends TestBean { } } assertThat(found).isTrue(); - assertThat(ebi.getPropertyDescriptors().length).isEqualTo(bi.getPropertyDescriptors().length+1); + assertThat(ebi.getPropertyDescriptors()).hasSize(bi.getPropertyDescriptors().length+1); } /** diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java index b3230e53e0ee..a19e9cda2606 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -103,14 +103,14 @@ public void testHierarchicalCountBeansWithOverride() { public void testHierarchicalNamesWithNoMatch() { List names = Arrays.asList( BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, NoOp.class)); - assertThat(names.size()).isEqualTo(0); + assertThat(names).isEmpty(); } @Test public void testHierarchicalNamesWithMatchOnlyInRoot() { List names = Arrays.asList( BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, IndexedTestBean.class)); - assertThat(names.size()).isEqualTo(1); + assertThat(names).hasSize(1); assertThat(names.contains("indexedBean")).isTrue(); // Distinguish from default ListableBeanFactory behavior assertThat(listableBeanFactory.getBeanNamesForType(IndexedTestBean.class).length == 0).isTrue(); @@ -121,7 +121,7 @@ public void testGetBeanNamesForTypeWithOverride() { List names = Arrays.asList( BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class)); // includes 2 TestBeans from FactoryBeans (DummyFactory definitions) - assertThat(names.size()).isEqualTo(4); + assertThat(names).hasSize(4); assertThat(names.contains("test")).isTrue(); assertThat(names.contains("test3")).isTrue(); assertThat(names.contains("testFactory1")).isTrue(); @@ -150,7 +150,7 @@ public void testFindsBeansOfTypeWithStaticFactory() { lbf.addBean("t4", t4); Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, ITestBean.class, true, true); - assertThat(beans.size()).isEqualTo(4); + assertThat(beans).hasSize(4); assertThat(beans.get("t1")).isEqualTo(t1); assertThat(beans.get("t2")).isEqualTo(t2); assertThat(beans.get("t3")).isEqualTo(t3.getObject()); @@ -158,12 +158,12 @@ public void testFindsBeansOfTypeWithStaticFactory() { assertThat(condition).isTrue(); beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, DummyFactory.class, true, true); - assertThat(beans.size()).isEqualTo(2); + assertThat(beans).hasSize(2); assertThat(beans.get("&t3")).isEqualTo(t3); assertThat(beans.get("&t4")).isEqualTo(t4); beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, FactoryBean.class, true, true); - assertThat(beans.size()).isEqualTo(2); + assertThat(beans).hasSize(2); assertThat(beans.get("&t3")).isEqualTo(t3); assertThat(beans.get("&t4")).isEqualTo(t4); } @@ -185,7 +185,7 @@ public void testFindsBeansOfTypeWithDefaultFactory() { Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, true, false); - assertThat(beans.size()).isEqualTo(6); + assertThat(beans).hasSize(6); assertThat(beans.get("test3")).isEqualTo(test3); assertThat(beans.get("test")).isEqualTo(test); assertThat(beans.get("t1")).isEqualTo(t1); @@ -199,7 +199,7 @@ public void testFindsBeansOfTypeWithDefaultFactory() { beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, false, true); Object testFactory1 = this.listableBeanFactory.getBean("testFactory1"); - assertThat(beans.size()).isEqualTo(5); + assertThat(beans).hasSize(5); assertThat(beans.get("test")).isEqualTo(test); assertThat(beans.get("testFactory1")).isEqualTo(testFactory1); assertThat(beans.get("t1")).isEqualTo(t1); @@ -207,7 +207,7 @@ public void testFindsBeansOfTypeWithDefaultFactory() { assertThat(beans.get("t3")).isEqualTo(t3.getObject()); beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, true, true); - assertThat(beans.size()).isEqualTo(8); + assertThat(beans).hasSize(8); assertThat(beans.get("test3")).isEqualTo(test3); assertThat(beans.get("test")).isEqualTo(test); assertThat(beans.get("testFactory1")).isEqualTo(testFactory1); @@ -220,14 +220,14 @@ public void testFindsBeansOfTypeWithDefaultFactory() { assertThat(condition).isTrue(); beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, DummyFactory.class, true, true); - assertThat(beans.size()).isEqualTo(4); + assertThat(beans).hasSize(4); assertThat(beans.get("&testFactory1")).isEqualTo(this.listableBeanFactory.getBean("&testFactory1")); assertThat(beans.get("&testFactory2")).isEqualTo(this.listableBeanFactory.getBean("&testFactory2")); assertThat(beans.get("&t3")).isEqualTo(t3); assertThat(beans.get("&t4")).isEqualTo(t4); beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, FactoryBean.class, true, true); - assertThat(beans.size()).isEqualTo(4); + assertThat(beans).hasSize(4); assertThat(beans.get("&testFactory1")).isEqualTo(this.listableBeanFactory.getBean("&testFactory1")); assertThat(beans.get("&testFactory2")).isEqualTo(this.listableBeanFactory.getBean("&testFactory2")); assertThat(beans.get("&t3")).isEqualTo(t3); @@ -241,22 +241,22 @@ public void testHierarchicalResolutionWithOverride() { Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, true, false); - assertThat(beans.size()).isEqualTo(2); + assertThat(beans).hasSize(2); assertThat(beans.get("test3")).isEqualTo(test3); assertThat(beans.get("test")).isEqualTo(test); beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, false, false); - assertThat(beans.size()).isEqualTo(1); + assertThat(beans).hasSize(1); assertThat(beans.get("test")).isEqualTo(test); beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, false, true); Object testFactory1 = this.listableBeanFactory.getBean("testFactory1"); - assertThat(beans.size()).isEqualTo(2); + assertThat(beans).hasSize(2); assertThat(beans.get("test")).isEqualTo(test); assertThat(beans.get("testFactory1")).isEqualTo(testFactory1); beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, true, true); - assertThat(beans.size()).isEqualTo(4); + assertThat(beans).hasSize(4); assertThat(beans.get("test3")).isEqualTo(test3); assertThat(beans.get("test")).isEqualTo(test); assertThat(beans.get("testFactory1")).isEqualTo(testFactory1); @@ -264,12 +264,12 @@ public void testHierarchicalResolutionWithOverride() { assertThat(condition).isTrue(); beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, DummyFactory.class, true, true); - assertThat(beans.size()).isEqualTo(2); + assertThat(beans).hasSize(2); assertThat(beans.get("&testFactory1")).isEqualTo(this.listableBeanFactory.getBean("&testFactory1")); assertThat(beans.get("&testFactory2")).isEqualTo(this.listableBeanFactory.getBean("&testFactory2")); beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, FactoryBean.class, true, true); - assertThat(beans.size()).isEqualTo(2); + assertThat(beans).hasSize(2); assertThat(beans.get("&testFactory1")).isEqualTo(this.listableBeanFactory.getBean("&testFactory1")); assertThat(beans.get("&testFactory2")).isEqualTo(this.listableBeanFactory.getBean("&testFactory2")); } @@ -278,14 +278,14 @@ public void testHierarchicalResolutionWithOverride() { public void testHierarchicalNamesForAnnotationWithNoMatch() { List names = Arrays.asList( BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, Override.class)); - assertThat(names.size()).isEqualTo(0); + assertThat(names).isEmpty(); } @Test public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() { List names = Arrays.asList( BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, TestAnnotation.class)); - assertThat(names.size()).isEqualTo(1); + assertThat(names).hasSize(1); assertThat(names.contains("annotatedBean")).isTrue(); // Distinguish from default ListableBeanFactory behavior assertThat(listableBeanFactory.getBeanNamesForAnnotation(TestAnnotation.class).length == 0).isTrue(); @@ -297,7 +297,7 @@ public void testGetBeanNamesForAnnotationWithOverride() { this.listableBeanFactory.registerSingleton("anotherAnnotatedBean", annotatedBean); List names = Arrays.asList( BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, TestAnnotation.class)); - assertThat(names.size()).isEqualTo(2); + assertThat(names).hasSize(2); assertThat(names.contains("annotatedBean")).isTrue(); assertThat(names.contains("anotherAnnotatedBean")).isTrue(); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index c97a99678095..affdb618a6a4 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -983,7 +983,7 @@ void customEditor() { bd.setPropertyValues(pvs); lbf.registerBeanDefinition("testBean", bd); TestBean testBean = (TestBean) lbf.getBean("testBean"); - assertThat(testBean.getMyFloat().floatValue() == 1.1f).isTrue(); + assertThat(testBean.getMyFloat() == 1.1f).isTrue(); } @Test @@ -1005,7 +1005,7 @@ void customConverter() { bd.setPropertyValues(pvs); lbf.registerBeanDefinition("testBean", bd); TestBean testBean = (TestBean) lbf.getBean("testBean"); - assertThat(testBean.getMyFloat().floatValue() == 1.1f).isTrue(); + assertThat(testBean.getMyFloat() == 1.1f).isTrue(); } @Test @@ -1021,7 +1021,7 @@ void customEditorWithBeanReference() { lbf.registerBeanDefinition("testBean", bd); lbf.registerSingleton("myFloat", "1,1"); TestBean testBean = (TestBean) lbf.getBean("testBean"); - assertThat(testBean.getMyFloat().floatValue() == 1.1f).isTrue(); + assertThat(testBean.getMyFloat() == 1.1f).isTrue(); } @Test @@ -1037,7 +1037,7 @@ void customTypeConverter() { TestBean testBean = (TestBean) lbf.getBean("testBean"); assertThat(testBean.getName()).isEqualTo("myName"); assertThat(testBean.getAge()).isEqualTo(5); - assertThat(testBean.getMyFloat().floatValue() == 1.1f).isTrue(); + assertThat(testBean.getMyFloat() == 1.1f).isTrue(); } @Test @@ -1054,7 +1054,7 @@ void customTypeConverterWithBeanReference() { TestBean testBean = (TestBean) lbf.getBean("testBean"); assertThat(testBean.getName()).isEqualTo("myName"); assertThat(testBean.getAge()).isEqualTo(5); - assertThat(testBean.getMyFloat().floatValue() == 1.1f).isTrue(); + assertThat(testBean.getMyFloat() == 1.1f).isTrue(); } @Test @@ -1075,12 +1075,12 @@ void registerExistingSingletonWithReference() { assertThat(test.getSpouse()).isEqualTo(singletonObject); Map beansOfType = lbf.getBeansOfType(TestBean.class, false, true); - assertThat(beansOfType.size()).isEqualTo(2); + assertThat(beansOfType).hasSize(2); assertThat(beansOfType.containsValue(test)).isTrue(); assertThat(beansOfType.containsValue(singletonObject)).isTrue(); beansOfType = lbf.getBeansOfType(null, false, true); - assertThat(beansOfType.size()).isEqualTo(2); + assertThat(beansOfType).hasSize(2); Iterator beanNames = lbf.getBeanNamesIterator(); assertThat(beanNames.next()).isEqualTo("test"); @@ -1113,7 +1113,7 @@ void registerExistingSingletonWithNameOverriding() { assertThat(test.getSpouse()).isEqualTo(singletonObject); Map beansOfType = lbf.getBeansOfType(TestBean.class, false, true); - assertThat(beansOfType.size()).isEqualTo(2); + assertThat(beansOfType).hasSize(2); assertThat(beansOfType.containsValue(test)).isTrue(); assertThat(beansOfType.containsValue(singletonObject)).isTrue(); @@ -1123,7 +1123,7 @@ void registerExistingSingletonWithNameOverriding() { assertThat(beanNames.next()).isEqualTo("test"); assertThat(beanNames.next()).isEqualTo("singletonObject"); assertThat(beanNames.hasNext()).isFalse(); - assertThat(beansOfType.size()).isEqualTo(2); + assertThat(beansOfType).hasSize(2); assertThat(lbf.containsSingleton("test")).isTrue(); assertThat(lbf.containsSingleton("singletonObject")).isTrue(); @@ -1669,18 +1669,18 @@ void getBeanByTypeInstanceWithAmbiguity() { for (ConstructorDependency instance : provider) { resolved.add(instance); } - assertThat(resolved.size()).isEqualTo(2); + assertThat(resolved).hasSize(2); assertThat(resolved.contains(lbf.getBean("bd1"))).isTrue(); assertThat(resolved.contains(lbf.getBean("bd2"))).isTrue(); resolved = new HashSet<>(); provider.forEach(resolved::add); - assertThat(resolved.size()).isEqualTo(2); + assertThat(resolved).hasSize(2); assertThat(resolved.contains(lbf.getBean("bd1"))).isTrue(); assertThat(resolved.contains(lbf.getBean("bd2"))).isTrue(); resolved = provider.stream().collect(Collectors.toSet()); - assertThat(resolved.size()).isEqualTo(2); + assertThat(resolved).hasSize(2); assertThat(resolved.contains(lbf.getBean("bd1"))).isTrue(); assertThat(resolved.contains(lbf.getBean("bd2"))).isTrue(); } @@ -1718,18 +1718,18 @@ void getBeanByTypeInstanceWithPrimary() { for (ConstructorDependency instance : provider) { resolved.add(instance); } - assertThat(resolved.size()).isEqualTo(2); + assertThat(resolved).hasSize(2); assertThat(resolved.contains(lbf.getBean("bd1"))).isTrue(); assertThat(resolved.contains(lbf.getBean("bd2"))).isTrue(); resolved = new HashSet<>(); provider.forEach(resolved::add); - assertThat(resolved.size()).isEqualTo(2); + assertThat(resolved).hasSize(2); assertThat(resolved.contains(lbf.getBean("bd1"))).isTrue(); assertThat(resolved.contains(lbf.getBean("bd2"))).isTrue(); resolved = provider.stream().collect(Collectors.toSet()); - assertThat(resolved.size()).isEqualTo(2); + assertThat(resolved).hasSize(2); assertThat(resolved.contains(lbf.getBean("bd1"))).isTrue(); assertThat(resolved.contains(lbf.getBean("bd2"))).isTrue(); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index e9b72d71a5b9..f2b110f58fea 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -159,7 +159,7 @@ public void testExtendedResourceInjection() { assertThat(bean.getBeanFactory()).isSameAs(bf); String[] depBeans = bf.getDependenciesForBean("annotatedBean"); - assertThat(depBeans.length).isEqualTo(2); + assertThat(depBeans).hasSize(2); assertThat(depBeans[0]).isEqualTo("testBean"); assertThat(depBeans[1]).isEqualTo("nestedTestBean"); } @@ -266,10 +266,10 @@ public void testOptionalResourceInjection() { assertThat(bean.getTestBean3()).isSameAs(tb); assertThat(bean.getTestBean4()).isSameAs(tb); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); - assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); } @@ -294,10 +294,10 @@ public void testOptionalResourceInjectionWithSingletonRemoval() { assertThat(bean.getTestBean3()).isSameAs(tb); assertThat(bean.getTestBean4()).isSameAs(tb); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); - assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); @@ -309,10 +309,10 @@ public void testOptionalResourceInjectionWithSingletonRemoval() { assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean4()).isNull(); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); - assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); @@ -324,10 +324,10 @@ public void testOptionalResourceInjectionWithSingletonRemoval() { assertThat(bean.getTestBean3()).isSameAs(tb); assertThat(bean.getTestBean4()).isSameAs(tb); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); - assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); } @@ -351,10 +351,10 @@ public void testOptionalResourceInjectionWithBeanDefinitionRemoval() { assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean")); assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean")); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); - assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); @@ -366,10 +366,10 @@ public void testOptionalResourceInjectionWithBeanDefinitionRemoval() { assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean4()).isNull(); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); - assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); @@ -381,10 +381,10 @@ public void testOptionalResourceInjectionWithBeanDefinitionRemoval() { assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean")); assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean")); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); - assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); } @@ -411,13 +411,13 @@ public void testOptionalCollectionResourceInjection() { assertThat(bean.getTestBean3()).isSameAs(tb); assertThat(bean.getTestBean4()).isSameAs(tb); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().size()).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans().get(0)).isSameAs(ntb1); assertThat(bean.getNestedTestBeans().get(1)).isSameAs(ntb2); - assertThat(bean.nestedTestBeansSetter.size()).isEqualTo(2); + assertThat(bean.nestedTestBeansSetter).hasSize(2); assertThat(bean.nestedTestBeansSetter.get(0)).isSameAs(ntb1); assertThat(bean.nestedTestBeansSetter.get(1)).isSameAs(ntb2); - assertThat(bean.nestedTestBeansField.size()).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField.get(0)).isSameAs(ntb1); assertThat(bean.nestedTestBeansField.get(1)).isSameAs(ntb2); } @@ -442,11 +442,11 @@ public void testOptionalCollectionResourceInjectionWithSingleElement() { assertThat(bean.getTestBean3()).isSameAs(tb); assertThat(bean.getTestBean4()).isSameAs(tb); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().size()).isEqualTo(1); + assertThat(bean.getNestedTestBeans()).hasSize(1); assertThat(bean.getNestedTestBeans().get(0)).isSameAs(ntb1); - assertThat(bean.nestedTestBeansSetter.size()).isEqualTo(1); + assertThat(bean.nestedTestBeansSetter).hasSize(1); assertThat(bean.nestedTestBeansSetter.get(0)).isSameAs(ntb1); - assertThat(bean.nestedTestBeansField.size()).isEqualTo(1); + assertThat(bean.nestedTestBeansField).hasSize(1); assertThat(bean.nestedTestBeansField.get(0)).isSameAs(ntb1); } @@ -496,10 +496,10 @@ public void testOrderedResourceInjection() { assertThat(bean.getTestBean3()).isSameAs(tb); assertThat(bean.getTestBean4()).isSameAs(tb); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb2); assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb1); - assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb2); assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb1); } @@ -522,10 +522,10 @@ public void testAnnotationOrderedResourceInjection() { assertThat(bean.getTestBean3()).isSameAs(tb); assertThat(bean.getTestBean4()).isSameAs(tb); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb2); assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb1); - assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb2); assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb1); } @@ -554,13 +554,13 @@ public void testOrderedCollectionResourceInjection() { assertThat(bean.getTestBean3()).isSameAs(tb); assertThat(bean.getTestBean4()).isSameAs(tb); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().size()).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans().get(0)).isSameAs(ntb2); assertThat(bean.getNestedTestBeans().get(1)).isSameAs(ntb1); - assertThat(bean.nestedTestBeansSetter.size()).isEqualTo(2); + assertThat(bean.nestedTestBeansSetter).hasSize(2); assertThat(bean.nestedTestBeansSetter.get(0)).isSameAs(ntb2); assertThat(bean.nestedTestBeansSetter.get(1)).isSameAs(ntb1); - assertThat(bean.nestedTestBeansField.size()).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField.get(0)).isSameAs(ntb2); assertThat(bean.nestedTestBeansField.get(1)).isSameAs(ntb1); } @@ -587,13 +587,13 @@ public void testAnnotationOrderedCollectionResourceInjection() { assertThat(bean.getTestBean3()).isSameAs(tb); assertThat(bean.getTestBean4()).isSameAs(tb); assertThat(bean.getIndexedTestBean()).isSameAs(itb); - assertThat(bean.getNestedTestBeans().size()).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans().get(0)).isSameAs(ntb2); assertThat(bean.getNestedTestBeans().get(1)).isSameAs(ntb1); - assertThat(bean.nestedTestBeansSetter.size()).isEqualTo(2); + assertThat(bean.nestedTestBeansSetter).hasSize(2); assertThat(bean.nestedTestBeansSetter.get(0)).isSameAs(ntb2); assertThat(bean.nestedTestBeansSetter.get(1)).isSameAs(ntb1); - assertThat(bean.nestedTestBeansField.size()).isEqualTo(2); + assertThat(bean.nestedTestBeansField).hasSize(2); assertThat(bean.nestedTestBeansField.get(0)).isSameAs(ntb2); assertThat(bean.nestedTestBeansField.get(1)).isSameAs(ntb1); } @@ -772,7 +772,7 @@ public void testConstructorResourceInjectionWithMultipleCandidates() { ConstructorsResourceInjectionBean bean = (ConstructorsResourceInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean4()).isSameAs(tb); - assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); } @@ -798,7 +798,7 @@ public void testConstructorResourceInjectionWithCollectionAndNullFromFactoryBean ConstructorsCollectionResourceInjectionBean bean = (ConstructorsCollectionResourceInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean4()).isSameAs(tb); - assertThat(bean.getNestedTestBeans().size()).isEqualTo(1); + assertThat(bean.getNestedTestBeans()).hasSize(1); assertThat(bean.getNestedTestBeans().get(0)).isSameAs(ntb2); Map map = bf.getBeansOfType(NestedTestBean.class); @@ -820,7 +820,7 @@ public void testConstructorResourceInjectionWithMultipleCandidatesAsCollection() ConstructorsCollectionResourceInjectionBean bean = (ConstructorsCollectionResourceInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean4()).isSameAs(tb); - assertThat(bean.getNestedTestBeans().size()).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans().get(0)).isSameAs(ntb1); assertThat(bean.getNestedTestBeans().get(1)).isSameAs(ntb2); } @@ -838,7 +838,7 @@ public void testConstructorResourceInjectionWithMultipleOrderedCandidates() { ConstructorsResourceInjectionBean bean = (ConstructorsResourceInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean4()).isSameAs(tb); - assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb2); assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb1); } @@ -856,7 +856,7 @@ public void testConstructorResourceInjectionWithMultipleCandidatesAsOrderedColle ConstructorsCollectionResourceInjectionBean bean = (ConstructorsCollectionResourceInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean4()).isSameAs(tb); - assertThat(bean.getNestedTestBeans().size()).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans().get(0)).isSameAs(ntb2); assertThat(bean.getNestedTestBeans().get(1)).isSameAs(ntb1); } @@ -873,7 +873,7 @@ public void testSingleConstructorInjectionWithMultipleCandidatesAsRequiredVararg SingleConstructorVarargBean bean = (SingleConstructorVarargBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isSameAs(tb); - assertThat(bean.getNestedTestBeans().size()).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans().get(0)).isSameAs(ntb2); assertThat(bean.getNestedTestBeans().get(1)).isSameAs(ntb1); } @@ -902,7 +902,7 @@ public void testSingleConstructorInjectionWithMultipleCandidatesAsRequiredCollec SingleConstructorRequiredCollectionBean bean = (SingleConstructorRequiredCollectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isSameAs(tb); - assertThat(bean.getNestedTestBeans().size()).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans().get(0)).isSameAs(ntb2); assertThat(bean.getNestedTestBeans().get(1)).isSameAs(ntb1); } @@ -931,7 +931,7 @@ public void testSingleConstructorInjectionWithMultipleCandidatesAsOrderedCollect SingleConstructorOptionalCollectionBean bean = (SingleConstructorOptionalCollectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isSameAs(tb); - assertThat(bean.getNestedTestBeans().size()).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans().get(0)).isSameAs(ntb2); assertThat(bean.getNestedTestBeans().get(1)).isSameAs(ntb1); } @@ -996,12 +996,12 @@ public void testConstructorInjectionWithMap() { bf.registerBeanDefinition("testBean2", tb2); MapConstructorInjectionBean bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(1); + assertThat(bean.getTestBeanMap()).hasSize(1); assertThat(bean.getTestBeanMap().get("testBean1")).isSameAs(tb1); assertThat(bean.getTestBeanMap().get("testBean2")).isNull(); bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(1); + assertThat(bean.getTestBeanMap()).hasSize(1); assertThat(bean.getTestBeanMap().get("testBean1")).isSameAs(tb1); assertThat(bean.getTestBeanMap().get("testBean2")).isNull(); } @@ -1017,14 +1017,14 @@ public void testFieldInjectionWithMap() { bf.registerSingleton("testBean2", tb2); MapFieldInjectionBean bean = (MapFieldInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(2); + assertThat(bean.getTestBeanMap()).hasSize(2); assertThat(bean.getTestBeanMap().keySet().contains("testBean1")).isTrue(); assertThat(bean.getTestBeanMap().keySet().contains("testBean2")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb1)).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb2)).isTrue(); bean = (MapFieldInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(2); + assertThat(bean.getTestBeanMap()).hasSize(2); assertThat(bean.getTestBeanMap().keySet().contains("testBean1")).isTrue(); assertThat(bean.getTestBeanMap().keySet().contains("testBean2")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb1)).isTrue(); @@ -1040,13 +1040,13 @@ public void testMethodInjectionWithMap() { bf.registerSingleton("testBean", tb); MapMethodInjectionBean bean = (MapMethodInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(1); + assertThat(bean.getTestBeanMap()).hasSize(1); assertThat(bean.getTestBeanMap().keySet().contains("testBean")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb)).isTrue(); assertThat(bean.getTestBean()).isSameAs(tb); bean = (MapMethodInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(1); + assertThat(bean.getTestBeanMap()).hasSize(1); assertThat(bean.getTestBeanMap().keySet().contains("testBean")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb)).isTrue(); assertThat(bean.getTestBean()).isSameAs(tb); @@ -1072,7 +1072,7 @@ public void testMethodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandid MapMethodInjectionBean bean = (MapMethodInjectionBean) bf.getBean("annotatedBean"); TestBean tb = (TestBean) bf.getBean("testBean1"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(1); + assertThat(bean.getTestBeanMap()).hasSize(1); assertThat(bean.getTestBeanMap().keySet().contains("testBean1")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb)).isTrue(); assertThat(bean.getTestBean()).isSameAs(tb); @@ -1215,7 +1215,7 @@ public void testSelfReferenceWithOther() { SelfInjectionBean bean = (SelfInjectionBean) bf.getBean("annotatedBean"); SelfInjectionBean bean2 = (SelfInjectionBean) bf.getBean("annotatedBean2"); assertThat(bean.reference).isSameAs(bean2); - assertThat(bean.referenceCollection.size()).isEqualTo(1); + assertThat(bean.referenceCollection).hasSize(1); assertThat(bean.referenceCollection.get(0)).isSameAs(bean2); } @@ -1326,16 +1326,16 @@ public void testObjectProviderInjectionWithPrototype() { assertThat(bean.consumeUniqueTestBean()).isEqualTo(bf.getBean("testBean")); List testBeans = bean.iterateTestBeans(); - assertThat(testBeans.size()).isEqualTo(1); + assertThat(testBeans).hasSize(1); assertThat(testBeans.contains(bf.getBean("testBean"))).isTrue(); testBeans = bean.forEachTestBeans(); - assertThat(testBeans.size()).isEqualTo(1); + assertThat(testBeans).hasSize(1); assertThat(testBeans.contains(bf.getBean("testBean"))).isTrue(); testBeans = bean.streamTestBeans(); - assertThat(testBeans.size()).isEqualTo(1); + assertThat(testBeans).hasSize(1); assertThat(testBeans.contains(bf.getBean("testBean"))).isTrue(); testBeans = bean.sortedTestBeans(); - assertThat(testBeans.size()).isEqualTo(1); + assertThat(testBeans).hasSize(1); assertThat(testBeans.contains(bf.getBean("testBean"))).isTrue(); } @@ -1354,16 +1354,16 @@ public void testObjectProviderInjectionWithSingletonTarget() { assertThat(bean.consumeUniqueTestBean()).isEqualTo(bf.getBean("testBean")); List testBeans = bean.iterateTestBeans(); - assertThat(testBeans.size()).isEqualTo(1); + assertThat(testBeans).hasSize(1); assertThat(testBeans.contains(bf.getBean("testBean"))).isTrue(); testBeans = bean.forEachTestBeans(); - assertThat(testBeans.size()).isEqualTo(1); + assertThat(testBeans).hasSize(1); assertThat(testBeans.contains(bf.getBean("testBean"))).isTrue(); testBeans = bean.streamTestBeans(); - assertThat(testBeans.size()).isEqualTo(1); + assertThat(testBeans).hasSize(1); assertThat(testBeans.contains(bf.getBean("testBean"))).isTrue(); testBeans = bean.sortedTestBeans(); - assertThat(testBeans.size()).isEqualTo(1); + assertThat(testBeans).hasSize(1); assertThat(testBeans.contains(bf.getBean("testBean"))).isTrue(); } @@ -1405,19 +1405,19 @@ public void testObjectProviderInjectionWithTargetNotUnique() { assertThat(bean.consumeUniqueTestBean()).isNull(); List testBeans = bean.iterateTestBeans(); - assertThat(testBeans.size()).isEqualTo(2); + assertThat(testBeans).hasSize(2); assertThat(testBeans.get(0)).isSameAs(bf.getBean("testBean1")); assertThat(testBeans.get(1)).isSameAs(bf.getBean("testBean2")); testBeans = bean.forEachTestBeans(); - assertThat(testBeans.size()).isEqualTo(2); + assertThat(testBeans).hasSize(2); assertThat(testBeans.get(0)).isSameAs(bf.getBean("testBean1")); assertThat(testBeans.get(1)).isSameAs(bf.getBean("testBean2")); testBeans = bean.streamTestBeans(); - assertThat(testBeans.size()).isEqualTo(2); + assertThat(testBeans).hasSize(2); assertThat(testBeans.get(0)).isSameAs(bf.getBean("testBean1")); assertThat(testBeans.get(1)).isSameAs(bf.getBean("testBean2")); testBeans = bean.sortedTestBeans(); - assertThat(testBeans.size()).isEqualTo(2); + assertThat(testBeans).hasSize(2); assertThat(testBeans.get(0)).isSameAs(bf.getBean("testBean1")); assertThat(testBeans.get(1)).isSameAs(bf.getBean("testBean2")); } @@ -1443,19 +1443,19 @@ public void testObjectProviderInjectionWithTargetPrimary() { assertThat(bf.containsSingleton("testBean2")).isFalse(); List testBeans = bean.iterateTestBeans(); - assertThat(testBeans.size()).isEqualTo(2); + assertThat(testBeans).hasSize(2); assertThat(testBeans.get(0)).isSameAs(bf.getBean("testBean1")); assertThat(testBeans.get(1)).isSameAs(bf.getBean("testBean2")); testBeans = bean.forEachTestBeans(); - assertThat(testBeans.size()).isEqualTo(2); + assertThat(testBeans).hasSize(2); assertThat(testBeans.get(0)).isSameAs(bf.getBean("testBean1")); assertThat(testBeans.get(1)).isSameAs(bf.getBean("testBean2")); testBeans = bean.streamTestBeans(); - assertThat(testBeans.size()).isEqualTo(2); + assertThat(testBeans).hasSize(2); assertThat(testBeans.get(0)).isSameAs(bf.getBean("testBean1")); assertThat(testBeans.get(1)).isSameAs(bf.getBean("testBean2")); testBeans = bean.sortedTestBeans(); - assertThat(testBeans.size()).isEqualTo(2); + assertThat(testBeans).hasSize(2); assertThat(testBeans.get(0)).isSameAs(bf.getBean("testBean2")); assertThat(testBeans.get(1)).isSameAs(bf.getBean("testBean1")); } @@ -1474,7 +1474,7 @@ public void testObjectProviderInjectionWithUnresolvedOrderedStream() { ObjectProviderInjectionBean bean = (ObjectProviderInjectionBean) bf.getBean("annotatedBean"); List testBeans = bean.sortedTestBeans(); - assertThat(testBeans.size()).isEqualTo(2); + assertThat(testBeans).hasSize(2); assertThat(testBeans.get(0)).isSameAs(bf.getBean("testBean2")); assertThat(testBeans.get(1)).isSameAs(bf.getBean("testBean1")); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java index 079f39556036..443ea6c5f021 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -195,7 +195,7 @@ public void testConstructorResourceInjectionWithMultipleCandidatesAsCollection() ConstructorsCollectionResourceInjectionBean bean = (ConstructorsCollectionResourceInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean3()).isNull(); assertThat(bean.getTestBean4()).isSameAs(tb); - assertThat(bean.getNestedTestBeans().size()).isEqualTo(2); + assertThat(bean.getNestedTestBeans()).hasSize(2); assertThat(bean.getNestedTestBeans().get(0)).isSameAs(ntb1); assertThat(bean.getNestedTestBeans().get(1)).isSameAs(ntb2); } @@ -222,14 +222,14 @@ public void testConstructorInjectionWithMap() { bf.registerSingleton("testBean2", tb1); MapConstructorInjectionBean bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(2); + assertThat(bean.getTestBeanMap()).hasSize(2); assertThat(bean.getTestBeanMap().keySet().contains("testBean1")).isTrue(); assertThat(bean.getTestBeanMap().keySet().contains("testBean2")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb1)).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb2)).isTrue(); bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(2); + assertThat(bean.getTestBeanMap()).hasSize(2); assertThat(bean.getTestBeanMap().keySet().contains("testBean1")).isTrue(); assertThat(bean.getTestBeanMap().keySet().contains("testBean2")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb1)).isTrue(); @@ -247,14 +247,14 @@ public void testFieldInjectionWithMap() { bf.registerSingleton("testBean2", tb1); MapFieldInjectionBean bean = (MapFieldInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(2); + assertThat(bean.getTestBeanMap()).hasSize(2); assertThat(bean.getTestBeanMap().keySet().contains("testBean1")).isTrue(); assertThat(bean.getTestBeanMap().keySet().contains("testBean2")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb1)).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb2)).isTrue(); bean = (MapFieldInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(2); + assertThat(bean.getTestBeanMap()).hasSize(2); assertThat(bean.getTestBeanMap().keySet().contains("testBean1")).isTrue(); assertThat(bean.getTestBeanMap().keySet().contains("testBean2")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb1)).isTrue(); @@ -270,13 +270,13 @@ public void testMethodInjectionWithMap() { bf.registerSingleton("testBean", tb); MapMethodInjectionBean bean = (MapMethodInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(1); + assertThat(bean.getTestBeanMap()).hasSize(1); assertThat(bean.getTestBeanMap().keySet().contains("testBean")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb)).isTrue(); assertThat(bean.getTestBean()).isSameAs(tb); bean = (MapMethodInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(1); + assertThat(bean.getTestBeanMap()).hasSize(1); assertThat(bean.getTestBeanMap().keySet().contains("testBean")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb)).isTrue(); assertThat(bean.getTestBean()).isSameAs(tb); @@ -301,7 +301,7 @@ public void testMethodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandid MapMethodInjectionBean bean = (MapMethodInjectionBean) bf.getBean("annotatedBean"); TestBean tb = (TestBean) bf.getBean("testBean1"); - assertThat(bean.getTestBeanMap().size()).isEqualTo(1); + assertThat(bean.getTestBeanMap()).hasSize(1); assertThat(bean.getTestBeanMap().keySet().contains("testBean1")).isTrue(); assertThat(bean.getTestBeanMap().values().contains(tb)).isTrue(); assertThat(bean.getTestBean()).isSameAs(tb); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/AotServicesTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/AotServicesTests.java index f1098d11b845..a4ddb202a5d1 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/AotServicesTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/AotServicesTests.java @@ -238,10 +238,9 @@ static class TestSpringFactoriesClassLoader extends ClassLoader { @Override public Enumeration getResources(String name) throws IOException { - return (!"META-INF/spring/aot.factories".equals(name)) - ? super.getResources(name) - : super.getResources("org/springframework/beans/factory/aot/" - + this.factoriesName); + return (!"META-INF/spring/aot.factories".equals(name) ? + super.getResources(name) : + super.getResources("org/springframework/beans/factory/aot/" + this.factoriesName)); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolverTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolverTests.java index e1ec79c3546f..857592f43232 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolverTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolverTests.java @@ -74,12 +74,11 @@ void resolveWhenRegisteredBeanIsNullThrowsException() { @Test void resolveWhenMethodIsMissingThrowsException() { RegisteredBean registeredBean = registerTestBean(this.beanFactory); + AutowiredMethodArgumentsResolver resolver = AutowiredMethodArgumentsResolver.forMethod("missing", InputStream.class); assertThatIllegalArgumentException() - .isThrownBy(() -> AutowiredMethodArgumentsResolver - .forMethod("missing", InputStream.class).resolve(registeredBean)) - .withMessage( - "Method 'missing' with parameter types [java.io.InputStream] declared on " - + TestBean.class.getName()); + .isThrownBy(() -> resolver.resolve(registeredBean)) + .withMessage("Method 'missing' with parameter types [java.io.InputStream] declared on %s could not be found.", + TestBean.class.getName()); } @Test diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java index d90f7534e326..f0d79dcad1af 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java @@ -88,8 +88,7 @@ class BeanDefinitionMethodGeneratorTests { @Test void generateBeanDefinitionMethodGeneratesMethod() { - RegisteredBean registeredBean = registerBean( - new RootBeanDefinition(TestBean.class)); + RegisteredBean registeredBean = registerBean(new RootBeanDefinition(TestBean.class)); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, Collections.emptyList()); @@ -172,10 +171,8 @@ void generateBeanDefinitionMethodWhenHasGenericsGeneratesMethod() { @Test void generateBeanDefinitionMethodWhenHasInstancePostProcessorGeneratesMethod() { - RegisteredBean registeredBean = registerBean( - new RootBeanDefinition(TestBean.class)); - BeanRegistrationAotContribution aotContribution = (generationContext, - beanRegistrationCode) -> { + RegisteredBean registeredBean = registerBean(new RootBeanDefinition(TestBean.class)); + BeanRegistrationAotContribution aotContribution = (generationContext, beanRegistrationCode) -> { GeneratedMethod generatedMethod = beanRegistrationCode.getMethods().add("postProcess", method -> method.addModifiers(Modifier.STATIC) .addParameter(RegisteredBean.class, "registeredBean") @@ -183,16 +180,14 @@ void generateBeanDefinitionMethodWhenHasInstancePostProcessorGeneratesMethod() { .returns(TestBean.class).addCode("return new $T($S);", TestBean.class, "postprocessed")); beanRegistrationCode.addInstancePostProcessor(generatedMethod.toMethodReference()); }; - List aotContributions = Collections - .singletonList(aotContribution); + List aotContributions = Collections.singletonList(aotContribution); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, aotContributions); MethodReference method = generator.generateBeanDefinitionMethod( this.generationContext, this.beanRegistrationsCode); compile(method, (actual, compiled) -> { assertThat(actual.getBeanClass()).isEqualTo(TestBean.class); - InstanceSupplier supplier = (InstanceSupplier) actual - .getInstanceSupplier(); + InstanceSupplier supplier = (InstanceSupplier) actual.getInstanceSupplier(); try { TestBean instance = (TestBean) supplier.get(registeredBean); assertThat(instance.getName()).isEqualTo("postprocessed"); @@ -204,9 +199,10 @@ void generateBeanDefinitionMethodWhenHasInstancePostProcessorGeneratesMethod() { }); } - @Test // gh-28748 + @Test // gh-28748 void generateBeanDefinitionMethodWhenHasInstancePostProcessorAndFactoryMethodGeneratesMethod() { - this.beanFactory.registerBeanDefinition("testBeanConfiguration", new RootBeanDefinition(TestBeanConfiguration.class)); + this.beanFactory.registerBeanDefinition("testBeanConfiguration", + new RootBeanDefinition(TestBeanConfiguration.class)); RootBeanDefinition beanDefinition = new RootBeanDefinition(TestBean.class); beanDefinition.setFactoryBeanName("testBeanConfiguration"); beanDefinition.setFactoryMethodName("testBean"); @@ -220,8 +216,7 @@ void generateBeanDefinitionMethodWhenHasInstancePostProcessorAndFactoryMethodGen .returns(TestBean.class).addCode("return new $T($S);", TestBean.class, "postprocessed")); beanRegistrationCode.addInstancePostProcessor(generatedMethod.toMethodReference()); }; - List aotContributions = Collections - .singletonList(aotContribution); + List aotContributions = Collections.singletonList(aotContribution); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, aotContributions); MethodReference method = generator.generateBeanDefinitionMethod( @@ -244,10 +239,9 @@ void generateBeanDefinitionMethodWhenHasInstancePostProcessorAndFactoryMethodGen @Test void generateBeanDefinitionMethodWhenHasCodeFragmentsCustomizerGeneratesMethod() { - RegisteredBean registeredBean = registerBean( - new RootBeanDefinition(TestBean.class)); - BeanRegistrationAotContribution aotContribution = BeanRegistrationAotContribution - .withCustomCodeFragments(this::customizeBeanDefinitionCode); + RegisteredBean registeredBean = registerBean(new RootBeanDefinition(TestBean.class)); + BeanRegistrationAotContribution aotContribution = + BeanRegistrationAotContribution.withCustomCodeFragments(this::customizeBeanDefinitionCode); List aotContributions = Collections.singletonList(aotContribution); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, aotContributions); @@ -260,21 +254,16 @@ void generateBeanDefinitionMethodWhenHasCodeFragmentsCustomizerGeneratesMethod() }); } - private BeanRegistrationCodeFragments customizeBeanDefinitionCode( - BeanRegistrationCodeFragments codeFragments) { + private BeanRegistrationCodeFragments customizeBeanDefinitionCode(BeanRegistrationCodeFragments codeFragments) { return new BeanRegistrationCodeFragmentsDecorator(codeFragments) { - @Override - public CodeBlock generateNewBeanDefinitionCode( - GenerationContext generationContext, - ResolvableType beanType, - BeanRegistrationCode beanRegistrationCode) { + public CodeBlock generateNewBeanDefinitionCode(GenerationContext generationContext, + ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) { CodeBlock.Builder code = CodeBlock.builder(); code.addStatement("// I am custom"); code.add(super.generateNewBeanDefinitionCode(generationContext, beanType, beanRegistrationCode)); return code.build(); } - }; } @@ -301,10 +290,9 @@ void generateBeanDefinitionMethodWhenHasAttributeFilterGeneratesMethod() { beanDefinition.setAttribute("a", "A"); beanDefinition.setAttribute("b", "B"); RegisteredBean registeredBean = registerBean(beanDefinition); - BeanRegistrationAotContribution aotContribution = BeanRegistrationAotContribution - .withCustomCodeFragments(this::customizeAttributeFilter); - List aotContributions = Collections - .singletonList(aotContribution); + BeanRegistrationAotContribution aotContribution = + BeanRegistrationAotContribution.withCustomCodeFragments(this::customizeAttributeFilter); + List aotContributions = Collections.singletonList(aotContribution); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, aotContributions); @@ -316,20 +304,15 @@ void generateBeanDefinitionMethodWhenHasAttributeFilterGeneratesMethod() { }); } - private BeanRegistrationCodeFragments customizeAttributeFilter( - BeanRegistrationCodeFragments codeFragments) { + private BeanRegistrationCodeFragments customizeAttributeFilter(BeanRegistrationCodeFragments codeFragments) { return new BeanRegistrationCodeFragmentsDecorator(codeFragments) { - @Override - public CodeBlock generateSetBeanDefinitionPropertiesCode( - GenerationContext generationContext, - BeanRegistrationCode beanRegistrationCode, - RootBeanDefinition beanDefinition, + public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext, + BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition, Predicate attributeFilter) { return super.generateSetBeanDefinitionPropertiesCode(generationContext, beanRegistrationCode, beanDefinition, "a"::equals); } - }; } @@ -471,8 +454,7 @@ void generateBeanDefinitionMethodWhenHasAotContributionsAppliesContributions() { @Test @CompileWithForkedClassLoader void generateBeanDefinitionMethodWhenPackagePrivateBean() { - RegisteredBean registeredBean = registerBean( - new RootBeanDefinition(PackagePrivateTestBean.class)); + RegisteredBean registeredBean = registerBean(new RootBeanDefinition(PackagePrivateTestBean.class)); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, Collections.emptyList()); @@ -502,6 +484,13 @@ void generateBeanDefinitionMethodWhenBeanIsInJavaxPackage() { testBeanDefinitionMethodInCurrentFile(DocumentBuilderFactory.class, beanDefinition); } + @Test + void generateBeanDefinitionMethodWhenBeanIsOfPrimitiveType() { + RootBeanDefinition beanDefinition = (RootBeanDefinition) BeanDefinitionBuilder + .rootBeanDefinition(Boolean.class).setFactoryMethod("parseBoolean").addConstructorArgValue("true").getBeanDefinition(); + testBeanDefinitionMethodInCurrentFile(Boolean.class, beanDefinition); + } + private void testBeanDefinitionMethodInCurrentFile(Class targetType, RootBeanDefinition beanDefinition) { RegisteredBean registeredBean = registerBean(new RootBeanDefinition(beanDefinition)); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( @@ -525,8 +514,7 @@ private RegisteredBean registerBean(RootBeanDefinition beanDefinition) { return RegisteredBean.of(this.beanFactory, beanName); } - private void compile(MethodReference method, - BiConsumer result) { + private void compile(MethodReference method, BiConsumer result) { this.beanRegistrationsCode.getTypeBuilder().set(type -> { CodeBlock methodInvocation = method.toInvokeCodeBlock(ArgumentCodeGenerator.none(), this.beanRegistrationsCode.getClassName()); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java index 1f44cfe7709f..6ed07a5fd53c 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -46,16 +45,16 @@ import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.MethodSpec; import org.springframework.javapoet.ParameterizedTypeName; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.Registration; /** * Tests for {@link BeanRegistrationsAotContribution}. * * @author Phillip Webb * @author Sebastien Deleuze + * @author Stephane Nicoll */ class BeanRegistrationsAotContributionTests { @@ -80,15 +79,12 @@ class BeanRegistrationsAotContributionTests { @Test void applyToAppliesContribution() { - Map registrations = new LinkedHashMap<>(); RegisteredBean registeredBean = registerBean( new RootBeanDefinition(TestBean.class)); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, Collections.emptyList()); - registrations.put("testBean", generator); - BeanRegistrationsAotContribution contribution = new BeanRegistrationsAotContribution( - registrations, new LinkedMultiValueMap<>()); + BeanRegistrationsAotContribution contribution = createContribution(generator); contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode); compile((consumer, compiled) -> { DefaultListableBeanFactory freshBeanFactory = new DefaultListableBeanFactory(); @@ -99,17 +95,12 @@ void applyToAppliesContribution() { @Test void applyToAppliesContributionWithAliases() { - Map registrations = new LinkedHashMap<>(); RegisteredBean registeredBean = registerBean( new RootBeanDefinition(TestBean.class)); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, Collections.emptyList()); - registrations.put("testBean", generator); - MultiValueMap aliases = new LinkedMultiValueMap<>(); - aliases.add("testBean", "testAlias"); - BeanRegistrationsAotContribution contribution = new BeanRegistrationsAotContribution( - registrations, aliases); + BeanRegistrationsAotContribution contribution = createContribution(generator, "testAlias"); contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode); compile((consumer, compiled) -> { DefaultListableBeanFactory freshBeanFactory = new DefaultListableBeanFactory(); @@ -123,15 +114,12 @@ void applyToWhenHasNameGeneratesPrefixedFeatureName() { this.generationContext = new TestGenerationContext( new ClassNameGenerator(TestGenerationContext.TEST_TARGET, "Management")); this.beanFactoryInitializationCode = new MockBeanFactoryInitializationCode(this.generationContext); - Map registrations = new LinkedHashMap<>(); RegisteredBean registeredBean = registerBean( new RootBeanDefinition(TestBean.class)); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, Collections.emptyList()); - registrations.put("testBean", generator); - BeanRegistrationsAotContribution contribution = new BeanRegistrationsAotContribution( - registrations, new LinkedMultiValueMap<>()); + BeanRegistrationsAotContribution contribution = createContribution(generator); contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode); compile((consumer, compiled) -> { SourceFile sourceFile = compiled.getSourceFile(".*BeanDefinitions"); @@ -142,7 +130,6 @@ void applyToWhenHasNameGeneratesPrefixedFeatureName() { @Test void applyToCallsRegistrationsWithBeanRegistrationsCode() { List beanRegistrationsCodes = new ArrayList<>(); - Map registrations = new LinkedHashMap<>(); RegisteredBean registeredBean = registerBean( new RootBeanDefinition(TestBean.class)); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( @@ -159,9 +146,7 @@ MethodReference generateBeanDefinitionMethod( } }; - registrations.put("testBean", generator); - BeanRegistrationsAotContribution contribution = new BeanRegistrationsAotContribution( - registrations, new LinkedMultiValueMap<>()); + BeanRegistrationsAotContribution contribution = createContribution(generator); contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode); assertThat(beanRegistrationsCodes).hasSize(1); BeanRegistrationsCode actual = beanRegistrationsCodes.get(0); @@ -199,4 +184,9 @@ private void compile( result.accept(compiled.getInstance(Consumer.class), compiled)); } + private BeanRegistrationsAotContribution createContribution( + BeanDefinitionMethodGenerator methodGenerator,String... aliases) { + return new BeanRegistrationsAotContribution(Map.of("testBean", new Registration(methodGenerator, aliases))); + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java index f6cd6b20a508..cb1e39f106db 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java @@ -62,9 +62,10 @@ void processAheadOfTimeReturnsBeanRegistrationsAotContributionWithAliases() { beanFactory.registerAlias("test", "testAlias"); BeanRegistrationsAotContribution contribution = processor .processAheadOfTime(beanFactory); - assertThat(contribution).extracting("aliases") - .asInstanceOf(InstanceOfAssertFactories.MAP).hasEntrySatisfying("test", value -> - assertThat(value).asList().singleElement().isEqualTo("testAlias")); + assertThat(contribution).extracting("registrations").asInstanceOf(InstanceOfAssertFactories.MAP) + .hasEntrySatisfying("test", registration -> + assertThat(registration).extracting("aliases").asInstanceOf(InstanceOfAssertFactories.ARRAY) + .singleElement().isEqualTo("testAlias")); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java index f7a52dbcdf09..693025cee205 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java @@ -399,19 +399,19 @@ private void doTestPropertyPlaceholderConfigurer(boolean parentChildSeparation) assertThat(tb1.getName()).isEqualTo("namemyvarmyvar${"); assertThat(tb2.getName()).isEqualTo("myvarname98"); assertThat(tb1.getSpouse()).isEqualTo(tb2); - assertThat(tb1.getSomeMap().size()).isEqualTo(1); + assertThat(tb1.getSomeMap()).hasSize(1); assertThat(tb1.getSomeMap().get("myKey")).isEqualTo("myValue"); - assertThat(tb2.getStringArray().length).isEqualTo(2); + assertThat(tb2.getStringArray()).hasSize(2); assertThat(tb2.getStringArray()[0]).isEqualTo(System.getProperty("os.name")); assertThat(tb2.getStringArray()[1]).isEqualTo("98"); - assertThat(tb2.getFriends().size()).isEqualTo(2); + assertThat(tb2.getFriends()).hasSize(2); assertThat(tb2.getFriends().iterator().next()).isEqualTo("na98me"); assertThat(tb2.getFriends().toArray()[1]).isEqualTo(tb2); - assertThat(tb2.getSomeSet().size()).isEqualTo(3); + assertThat(tb2.getSomeSet()).hasSize(3); assertThat(tb2.getSomeSet().contains("na98me")).isTrue(); assertThat(tb2.getSomeSet().contains(tb2)).isTrue(); assertThat(tb2.getSomeSet().contains(98)).isTrue(); - assertThat(tb2.getSomeMap().size()).isEqualTo(6); + assertThat(tb2.getSomeMap()).hasSize(6); assertThat(tb2.getSomeMap().get("key98")).isEqualTo("98"); assertThat(tb2.getSomeMap().get("key98ref")).isEqualTo(tb2); assertThat(tb2.getSomeMap().get("key1")).isEqualTo(tb2); @@ -577,7 +577,7 @@ public void testPropertyPlaceholderConfigurerWithSelfReferencingPlaceholderInAli TestBean tb = (TestBean) factory.getBean("tb"); assertThat(tb).isNotNull(); - assertThat(factory.getAliases("tb").length).isEqualTo(0); + assertThat(factory.getAliases("tb")).isEmpty(); } @Test diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/SimpleScopeTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/SimpleScopeTests.java index c66e65e2489f..21d207257454 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/SimpleScopeTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/SimpleScopeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -63,7 +63,7 @@ public Object get(String name, ObjectFactory objectFactory) { beanFactory.registerScope("myScope", scope); String[] scopeNames = beanFactory.getRegisteredScopeNames(); - assertThat(scopeNames.length).isEqualTo(1); + assertThat(scopeNames).hasSize(1); assertThat(scopeNames[0]).isEqualTo("myScope"); assertThat(beanFactory.getRegisteredScope("myScope")).isSameAs(scope); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java index b7e05ed64747..abef27bf44d1 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -47,7 +47,7 @@ public class YamlMapFactoryBeanTests { public void testSetIgnoreResourceNotFound() { this.factory.setResolutionMethod(YamlMapFactoryBean.ResolutionMethod.OVERRIDE_AND_IGNORE); this.factory.setResources(new FileSystemResource("non-exsitent-file.yml")); - assertThat(this.factory.getObject().size()).isEqualTo(0); + assertThat(this.factory.getObject()).isEmpty(); } @Test @@ -61,7 +61,7 @@ public void testSetBarfOnResourceNotFound() { @Test public void testGetObject() { this.factory.setResources(new ByteArrayResource("foo: bar".getBytes())); - assertThat(this.factory.getObject().size()).isEqualTo(1); + assertThat(this.factory.getObject()).hasSize(1); } @SuppressWarnings("unchecked") @@ -70,8 +70,8 @@ public void testOverrideAndRemoveDefaults() { this.factory.setResources(new ByteArrayResource("foo:\n bar: spam".getBytes()), new ByteArrayResource("foo:\n spam: bar".getBytes())); - assertThat(this.factory.getObject().size()).isEqualTo(1); - assertThat(((Map) this.factory.getObject().get("foo")).size()).isEqualTo(2); + assertThat(this.factory.getObject()).hasSize(1); + assertThat(((Map) this.factory.getObject().get("foo"))).hasSize(2); } @Test @@ -88,7 +88,7 @@ public InputStream getInputStream() throws IOException { } }, new ByteArrayResource("foo:\n spam: bar".getBytes())); - assertThat(this.factory.getObject().size()).isEqualTo(1); + assertThat(this.factory.getObject()).hasSize(1); } @Test @@ -96,7 +96,7 @@ public void testMapWithPeriodsInKey() { this.factory.setResources(new ByteArrayResource("foo:\n ? key1.key2\n : value".getBytes())); Map map = this.factory.getObject(); - assertThat(map.size()).isEqualTo(1); + assertThat(map).hasSize(1); assertThat(map.containsKey("foo")).isTrue(); Object object = map.get("foo"); boolean condition = object instanceof LinkedHashMap; @@ -112,15 +112,15 @@ public void testMapWithIntegerValue() { this.factory.setResources(new ByteArrayResource("foo:\n ? key1.key2\n : 3".getBytes())); Map map = this.factory.getObject(); - assertThat(map.size()).isEqualTo(1); + assertThat(map).hasSize(1); assertThat(map.containsKey("foo")).isTrue(); Object object = map.get("foo"); boolean condition = object instanceof LinkedHashMap; assertThat(condition).isTrue(); @SuppressWarnings("unchecked") Map sub = (Map) object; - assertThat(sub.size()).isEqualTo(1); - assertThat(sub.get("key1.key2")).isEqualTo(Integer.valueOf(3)); + assertThat(sub).hasSize(1); + assertThat(sub.get("key1.key2")).isEqualTo(3); } @Test diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java index edb1f994f33c..5a14c49ac20b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -52,7 +52,7 @@ class YamlProcessorTests { void arrayConvertedToIndexedBeanReference() { setYaml("foo: bar\nbar: [1,2,3]"); this.processor.process((properties, map) -> { - assertThat(properties.size()).isEqualTo(4); + assertThat(properties).hasSize(4); assertThat(properties.get("foo")).isEqualTo("bar"); assertThat(properties.getProperty("foo")).isEqualTo("bar"); assertThat(properties.get("bar[0]")).isEqualTo(1); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java index de770309e058..81678a7c0c2b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -181,7 +181,7 @@ public void beanDefinitionMerging() { RootBeanDefinition mergedBd = new RootBeanDefinition(bd); mergedBd.overrideFrom(childBd); assertThat(mergedBd.getConstructorArgumentValues().getArgumentCount()).isEqualTo(2); - assertThat(mergedBd.getPropertyValues().size()).isEqualTo(2); + assertThat(mergedBd.getPropertyValues()).hasSize(2); assertThat(mergedBd).isEqualTo(bd); mergedBd.getConstructorArgumentValues().getArgumentValue(1, null).setValue(9); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java index 5c6e02536317..c71e1947e77b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -166,7 +166,7 @@ void testGenericListOfArraysProperty() { new ClassPathResource("genericBeanTests.xml", getClass())); GenericBean gb = (GenericBean) bf.getBean("listOfArrays"); - assertThat(gb.getListOfArrays().size()).isEqualTo(1); + assertThat(gb.getListOfArrays()).hasSize(1); String[] array = gb.getListOfArrays().get(0); assertThat(array).hasSize(2); assertThat(array[0]).isEqualTo("value1"); @@ -338,7 +338,7 @@ void testGenericMapMapConstructor() { assertThat(gb.getPlainMap()).hasSize(2); assertThat(gb.getPlainMap().get("1")).isEqualTo("0"); assertThat(gb.getPlainMap().get("2")).isEqualTo("3"); - assertThat(gb.getShortMap().size()).isEqualTo(2); + assertThat(gb.getShortMap()).hasSize(2); assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); } @@ -361,7 +361,7 @@ void testGenericMapMapConstructorWithSameRefAndConversion() { assertThat(gb.getPlainMap()).hasSize(2); assertThat(gb.getPlainMap().get("1")).isEqualTo("0"); assertThat(gb.getPlainMap().get("2")).isEqualTo("3"); - assertThat(gb.getShortMap().size()).isEqualTo(2); + assertThat(gb.getShortMap()).hasSize(2); assertThat(gb.getShortMap().get(Short.valueOf("1"))).isEqualTo(0); assertThat(gb.getShortMap().get(Short.valueOf("2"))).isEqualTo(3); } @@ -589,7 +589,7 @@ void testGenericListBean() throws Exception { new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); List list = (List) bf.getBean("list"); - assertThat(list.size()).isEqualTo(1); + assertThat(list).hasSize(1); assertThat(list.get(0)).isEqualTo(new URL("http://localhost:8080")); } @@ -599,7 +599,7 @@ void testGenericSetBean() throws Exception { new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); Set set = (Set) bf.getBean("set"); - assertThat(set.size()).isEqualTo(1); + assertThat(set).hasSize(1); assertThat(set.iterator().next()).isEqualTo(new URL("http://localhost:8080")); } @@ -643,7 +643,7 @@ void testSetBean() throws Exception { new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); UrlSet us = (UrlSet) bf.getBean("setBean"); - assertThat(us.size()).isEqualTo(1); + assertThat(us).hasSize(1); assertThat(us.iterator().next()).isEqualTo(new URL("https://www.springframework.org")); } @@ -762,7 +762,7 @@ void parameterizedInstanceFactoryMethodWithInvalidClassName() { assertThat(bf.getType("mock")).isNull(); assertThat(bf.getType("mock")).isNull(); Map beans = bf.getBeansOfType(Runnable.class); - assertThat(beans).hasSize(0); + assertThat(beans).isEmpty(); } @Test @@ -828,8 +828,8 @@ void testGenericMatchingWithBeanNameDifferentiation() { assertThat(numberStoreNames).hasSize(2); assertThat(numberStoreNames[0]).isEqualTo("doubleStore"); assertThat(numberStoreNames[1]).isEqualTo("floatStore"); - assertThat(doubleStoreNames).hasSize(0); - assertThat(floatStoreNames).hasSize(0); + assertThat(doubleStoreNames).isEmpty(); + assertThat(floatStoreNames).isEmpty(); } @Test @@ -879,17 +879,17 @@ void testGenericMatchingWithFullTypeDifferentiation() { for (NumberStore instance : numberStoreProvider) { resolved.add(instance); } - assertThat(resolved.size()).isEqualTo(2); + assertThat(resolved).hasSize(2); assertThat(resolved.get(0)).isSameAs(bf.getBean("store1")); assertThat(resolved.get(1)).isSameAs(bf.getBean("store2")); resolved = numberStoreProvider.stream().toList(); - assertThat(resolved.size()).isEqualTo(2); + assertThat(resolved).hasSize(2); assertThat(resolved.get(0)).isSameAs(bf.getBean("store1")); assertThat(resolved.get(1)).isSameAs(bf.getBean("store2")); resolved = numberStoreProvider.orderedStream().toList(); - assertThat(resolved.size()).isEqualTo(2); + assertThat(resolved).hasSize(2); assertThat(resolved.get(0)).isSameAs(bf.getBean("store2")); assertThat(resolved.get(1)).isSameAs(bf.getBean("store1")); @@ -897,30 +897,30 @@ void testGenericMatchingWithFullTypeDifferentiation() { for (NumberStore instance : doubleStoreProvider) { resolved.add(instance); } - assertThat(resolved.size()).isEqualTo(1); + assertThat(resolved).hasSize(1); assertThat(resolved.contains(bf.getBean("store1"))).isTrue(); resolved = doubleStoreProvider.stream().collect(Collectors.toList()); - assertThat(resolved.size()).isEqualTo(1); + assertThat(resolved).hasSize(1); assertThat(resolved.contains(bf.getBean("store1"))).isTrue(); resolved = doubleStoreProvider.orderedStream().collect(Collectors.toList()); - assertThat(resolved.size()).isEqualTo(1); + assertThat(resolved).hasSize(1); assertThat(resolved.contains(bf.getBean("store1"))).isTrue(); resolved = new ArrayList<>(); for (NumberStore instance : floatStoreProvider) { resolved.add(instance); } - assertThat(resolved.size()).isEqualTo(1); + assertThat(resolved).hasSize(1); assertThat(resolved.contains(bf.getBean("store2"))).isTrue(); resolved = floatStoreProvider.stream().collect(Collectors.toList()); - assertThat(resolved.size()).isEqualTo(1); + assertThat(resolved).hasSize(1); assertThat(resolved.contains(bf.getBean("store2"))).isTrue(); resolved = floatStoreProvider.orderedStream().collect(Collectors.toList()); - assertThat(resolved.size()).isEqualTo(1); + assertThat(resolved).hasSize(1); assertThat(resolved.contains(bf.getBean("store2"))).isTrue(); } @@ -939,7 +939,7 @@ void testGenericMatchingWithUnresolvedOrderedStream() { ObjectProvider> numberStoreProvider = bf.getBeanProvider(ResolvableType.forClass(NumberStore.class)); List> resolved = numberStoreProvider.orderedStream().toList(); - assertThat(resolved.size()).isEqualTo(2); + assertThat(resolved).hasSize(2); assertThat(resolved.get(0)).isSameAs(bf.getBean("store2")); assertThat(resolved.get(1)).isSameAs(bf.getBean("store1")); } @@ -963,9 +963,9 @@ public static class NamedUrlMap extends HashMap { public static class CollectionDependentBean { public CollectionDependentBean(NamedUrlList list, NamedUrlSet set, NamedUrlMap map) { - assertThat(list.size()).isEqualTo(1); - assertThat(set.size()).isEqualTo(1); - assertThat(map.size()).isEqualTo(1); + assertThat(list).hasSize(1); + assertThat(set).hasSize(1); + assertThat(map).hasSize(1); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java index 7c22251d8ab3..47a476b3d819 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java @@ -45,13 +45,13 @@ public void testSingletons() { assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2); assertThat(beanRegistry.getSingletonCount()).isEqualTo(2); String[] names = beanRegistry.getSingletonNames(); - assertThat(names.length).isEqualTo(2); + assertThat(names).hasSize(2); assertThat(names[0]).isEqualTo("tb"); assertThat(names[1]).isEqualTo("tb2"); beanRegistry.destroySingletons(); assertThat(beanRegistry.getSingletonCount()).isEqualTo(0); - assertThat(beanRegistry.getSingletonNames().length).isEqualTo(0); + assertThat(beanRegistry.getSingletonNames()).isEmpty(); } @Test @@ -66,13 +66,13 @@ public void testDisposableBean() { assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb); assertThat(beanRegistry.getSingletonCount()).isEqualTo(1); String[] names = beanRegistry.getSingletonNames(); - assertThat(names.length).isEqualTo(1); + assertThat(names).hasSize(1); assertThat(names[0]).isEqualTo("tb"); assertThat(tb.wasDestroyed()).isFalse(); beanRegistry.destroySingletons(); assertThat(beanRegistry.getSingletonCount()).isEqualTo(0); - assertThat(beanRegistry.getSingletonNames().length).isEqualTo(0); + assertThat(beanRegistry.getSingletonNames()).isEmpty(); assertThat(tb.wasDestroyed()).isTrue(); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/InstanceSupplierTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/InstanceSupplierTests.java index 5cb43d26199a..c66a897b3ba1 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/InstanceSupplierTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/InstanceSupplierTests.java @@ -61,7 +61,7 @@ void andThenWhenFunctionIsNullThrowsException() { InstanceSupplier supplier = registeredBean -> "test"; ThrowingBiFunction after = null; assertThatIllegalArgumentException().isThrownBy(() -> supplier.andThen(after)) - .withMessage("After must not be null"); + .withMessage("'after' function must not be null"); } @Test diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireBeanFactoryTests.java index f660a8af020d..fe190c8b9f5c 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireBeanFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -27,7 +27,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.DependencyDescriptor; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.util.ClassUtils; @@ -148,7 +148,7 @@ public void testAutowireCandidateWithConstructorDescriptor() throws Exception { lbf.registerBeanDefinition(MARK, person2); MethodParameter param = new MethodParameter(QualifiedTestBean.class.getDeclaredConstructor(Person.class), 0); DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(param, false); - param.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); + param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); assertThat(param.getParameterName()).isEqualTo("tpb"); assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue(); assertThat(lbf.isAutowireCandidate(JUERGEN, qualifiedDescriptor)).isTrue(); @@ -174,9 +174,9 @@ public void testAutowireCandidateWithMethodDescriptor() throws Exception { new MethodParameter(QualifiedTestBean.class.getDeclaredMethod("autowireNonqualified", Person.class), 0); DependencyDescriptor qualifiedDescriptor = new DependencyDescriptor(qualifiedParam, false); DependencyDescriptor nonqualifiedDescriptor = new DependencyDescriptor(nonqualifiedParam, false); - qualifiedParam.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); + qualifiedParam.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); assertThat(qualifiedParam.getParameterName()).isEqualTo("tpb"); - nonqualifiedParam.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); + nonqualifiedParam.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); assertThat(nonqualifiedParam.getParameterName()).isEqualTo("tpb"); assertThat(lbf.isAutowireCandidate(JUERGEN, null)).isTrue(); assertThat(lbf.isAutowireCandidate(JUERGEN, nonqualifiedDescriptor)).isTrue(); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/CollectionMergingTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/CollectionMergingTests.java index 04b82ca200a8..85a78bab9f67 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/CollectionMergingTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/CollectionMergingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -65,7 +65,7 @@ public void mergeListWithInnerBeanAsListElement() throws Exception { TestBean bean = (TestBean) this.beanFactory.getBean("childWithListOfRefs"); List list = bean.getSomeList(); assertThat(list).isNotNull(); - assertThat(list.size()).isEqualTo(3); + assertThat(list).hasSize(3); assertThat(list.get(2)).isNotNull(); boolean condition = list.get(2) instanceof TestBean; assertThat(condition).isTrue(); @@ -85,7 +85,7 @@ public void mergeSetWithInnerBeanAsSetElement() throws Exception { TestBean bean = (TestBean) this.beanFactory.getBean("childWithSetOfRefs"); Set set = bean.getSomeSet(); assertThat(set).isNotNull(); - assertThat(set.size()).isEqualTo(2); + assertThat(set).hasSize(2); Iterator it = set.iterator(); it.next(); Object o = it.next(); @@ -110,7 +110,7 @@ public void mergeMapWithInnerBeanAsMapEntryValue() throws Exception { TestBean bean = (TestBean) this.beanFactory.getBean("childWithMapOfRefs"); Map map = bean.getSomeMap(); assertThat(map).isNotNull(); - assertThat(map.size()).isEqualTo(2); + assertThat(map).hasSize(2); assertThat(map.get("Rob")).isNotNull(); boolean condition = map.get("Rob") instanceof TestBean; assertThat(condition).isTrue(); @@ -142,7 +142,7 @@ public void mergeListWithInnerBeanAsListElementInConstructor() throws Exception TestBean bean = (TestBean) this.beanFactory.getBean("childWithListOfRefsInConstructor"); List list = bean.getSomeList(); assertThat(list).isNotNull(); - assertThat(list.size()).isEqualTo(3); + assertThat(list).hasSize(3); assertThat(list.get(2)).isNotNull(); boolean condition = list.get(2) instanceof TestBean; assertThat(condition).isTrue(); @@ -162,7 +162,7 @@ public void mergeSetWithInnerBeanAsSetElementInConstructor() throws Exception { TestBean bean = (TestBean) this.beanFactory.getBean("childWithSetOfRefsInConstructor"); Set set = bean.getSomeSet(); assertThat(set).isNotNull(); - assertThat(set.size()).isEqualTo(2); + assertThat(set).hasSize(2); Iterator it = set.iterator(); it.next(); Object o = it.next(); @@ -187,7 +187,7 @@ public void mergeMapWithInnerBeanAsMapEntryValueInConstructor() throws Exception TestBean bean = (TestBean) this.beanFactory.getBean("childWithMapOfRefsInConstructor"); Map map = bean.getSomeMap(); assertThat(map).isNotNull(); - assertThat(map.size()).isEqualTo(2); + assertThat(map).hasSize(2); assertThat(map.get("Rob")).isNotNull(); boolean condition = map.get("Rob") instanceof TestBean; assertThat(condition).isTrue(); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/EventPublicationTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/EventPublicationTests.java index 29e2561fc756..693e2a90540c 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/EventPublicationTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/EventPublicationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -27,6 +27,7 @@ import org.springframework.beans.factory.parsing.AliasDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.ComponentDefinition; +import org.springframework.beans.factory.parsing.DefaultsDefinition; import org.springframework.beans.factory.parsing.ImportDefinition; import org.springframework.beans.factory.parsing.PassThroughSourceExtractor; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -40,7 +41,7 @@ * @author Juergen Hoeller */ @SuppressWarnings("rawtypes") -public class EventPublicationTests { +class EventPublicationTests { private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); @@ -49,7 +50,7 @@ public class EventPublicationTests { @BeforeEach - public void setUp() throws Exception { + void setUp() throws Exception { XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this.beanFactory); reader.setEventListener(this.eventListener); reader.setSourceExtractor(new PassThroughSourceExtractor()); @@ -57,74 +58,64 @@ public void setUp() throws Exception { } @Test - public void defaultsEventReceived() throws Exception { - List defaultsList = this.eventListener.getDefaults(); - boolean condition2 = !defaultsList.isEmpty(); - assertThat(condition2).isTrue(); - boolean condition1 = defaultsList.get(0) instanceof DocumentDefaultsDefinition; - assertThat(condition1).isTrue(); + void defaultsEventReceived() throws Exception { + List defaultsList = this.eventListener.getDefaults(); + assertThat(defaultsList).isNotEmpty(); + assertThat(defaultsList.get(0)).isInstanceOf(DocumentDefaultsDefinition.class); DocumentDefaultsDefinition defaults = (DocumentDefaultsDefinition) defaultsList.get(0); assertThat(defaults.getLazyInit()).isEqualTo("true"); assertThat(defaults.getAutowire()).isEqualTo("constructor"); assertThat(defaults.getInitMethod()).isEqualTo("myInit"); assertThat(defaults.getDestroyMethod()).isEqualTo("myDestroy"); assertThat(defaults.getMerge()).isEqualTo("true"); - boolean condition = defaults.getSource() instanceof Element; - assertThat(condition).isTrue(); + assertThat(defaults.getSource()).isInstanceOf(Element.class); } @Test - public void beanEventReceived() throws Exception { + void beanEventReceived() throws Exception { ComponentDefinition componentDefinition1 = this.eventListener.getComponentDefinition("testBean"); - boolean condition3 = componentDefinition1 instanceof BeanComponentDefinition; - assertThat(condition3).isTrue(); - assertThat(componentDefinition1.getBeanDefinitions().length).isEqualTo(1); + assertThat(componentDefinition1).isInstanceOf(BeanComponentDefinition.class); + assertThat(componentDefinition1.getBeanDefinitions()).hasSize(1); BeanDefinition beanDefinition1 = componentDefinition1.getBeanDefinitions()[0]; assertThat(beanDefinition1.getConstructorArgumentValues().getGenericArgumentValue(String.class).getValue()).isEqualTo(new TypedStringValue("Rob Harrop")); - assertThat(componentDefinition1.getBeanReferences().length).isEqualTo(1); + assertThat(componentDefinition1.getBeanReferences()).hasSize(1); assertThat(componentDefinition1.getBeanReferences()[0].getBeanName()).isEqualTo("testBean2"); - assertThat(componentDefinition1.getInnerBeanDefinitions().length).isEqualTo(1); + assertThat(componentDefinition1.getInnerBeanDefinitions()).hasSize(1); BeanDefinition innerBd1 = componentDefinition1.getInnerBeanDefinitions()[0]; assertThat(innerBd1.getConstructorArgumentValues().getGenericArgumentValue(String.class).getValue()).isEqualTo(new TypedStringValue("ACME")); - boolean condition2 = componentDefinition1.getSource() instanceof Element; - assertThat(condition2).isTrue(); + assertThat(componentDefinition1.getSource()).isInstanceOf(Element.class); ComponentDefinition componentDefinition2 = this.eventListener.getComponentDefinition("testBean2"); - boolean condition1 = componentDefinition2 instanceof BeanComponentDefinition; - assertThat(condition1).isTrue(); - assertThat(componentDefinition1.getBeanDefinitions().length).isEqualTo(1); + assertThat(componentDefinition2).isInstanceOf(BeanComponentDefinition.class); + assertThat(componentDefinition1.getBeanDefinitions()).hasSize(1); BeanDefinition beanDefinition2 = componentDefinition2.getBeanDefinitions()[0]; assertThat(beanDefinition2.getPropertyValues().getPropertyValue("name").getValue()).isEqualTo(new TypedStringValue("Juergen Hoeller")); - assertThat(componentDefinition2.getBeanReferences().length).isEqualTo(0); - assertThat(componentDefinition2.getInnerBeanDefinitions().length).isEqualTo(1); + assertThat(componentDefinition2.getBeanReferences()).isEmpty(); + assertThat(componentDefinition2.getInnerBeanDefinitions()).hasSize(1); BeanDefinition innerBd2 = componentDefinition2.getInnerBeanDefinitions()[0]; assertThat(innerBd2.getPropertyValues().getPropertyValue("name").getValue()).isEqualTo(new TypedStringValue("Eva Schallmeiner")); - boolean condition = componentDefinition2.getSource() instanceof Element; - assertThat(condition).isTrue(); + assertThat(componentDefinition2.getSource()).isInstanceOf(Element.class); } @Test - public void aliasEventReceived() throws Exception { - List aliases = this.eventListener.getAliases("testBean"); - assertThat(aliases.size()).isEqualTo(2); - AliasDefinition aliasDefinition1 = (AliasDefinition) aliases.get(0); + void aliasEventReceived() throws Exception { + List aliases = this.eventListener.getAliases("testBean"); + assertThat(aliases).hasSize(2); + AliasDefinition aliasDefinition1 = aliases.get(0); assertThat(aliasDefinition1.getAlias()).isEqualTo("testBeanAlias1"); - boolean condition1 = aliasDefinition1.getSource() instanceof Element; - assertThat(condition1).isTrue(); - AliasDefinition aliasDefinition2 = (AliasDefinition) aliases.get(1); + assertThat(aliasDefinition1.getSource()).isInstanceOf(Element.class); + AliasDefinition aliasDefinition2 = aliases.get(1); assertThat(aliasDefinition2.getAlias()).isEqualTo("testBeanAlias2"); - boolean condition = aliasDefinition2.getSource() instanceof Element; - assertThat(condition).isTrue(); + assertThat(aliasDefinition2.getSource()).isInstanceOf(Element.class); } @Test - public void importEventReceived() throws Exception { - List imports = this.eventListener.getImports(); - assertThat(imports.size()).isEqualTo(1); - ImportDefinition importDefinition = (ImportDefinition) imports.get(0); + void importEventReceived() throws Exception { + List imports = this.eventListener.getImports(); + assertThat(imports).hasSize(1); + ImportDefinition importDefinition = imports.get(0); assertThat(importDefinition.getImportedResource()).isEqualTo("beanEventsImported.xml"); - boolean condition = importDefinition.getSource() instanceof Element; - assertThat(condition).isTrue(); + assertThat(importDefinition.getSource()).isInstanceOf(Element.class); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/ResourceEntityResolverTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/ResourceEntityResolverTests.java new file mode 100644 index 000000000000..b6904fce9f4e --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/ResourceEntityResolverTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2002-2022 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.beans.factory.xml; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; +import org.xml.sax.InputSource; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.lang.Nullable; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Unit tests for ResourceEntityResolver. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.0.4 + */ +class ResourceEntityResolverTests { + + @ParameterizedTest + @ValueSource(strings = { "https://example.org/schema/", "https://example.org/schema.xml" }) + void resolveEntityDoesNotCallFallbackIfNotSchema(String systemId) throws Exception { + ConfigurableFallbackEntityResolver resolver = new ConfigurableFallbackEntityResolver(true); + + assertThat(resolver.resolveEntity("testPublicId", systemId)).isNull(); + assertThat(resolver.fallbackInvoked).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "https://example.org/schema.dtd", "https://example.org/schema.xsd" }) + void resolveEntityCallsFallbackThatReturnsNull(String systemId) throws Exception { + ConfigurableFallbackEntityResolver resolver = new ConfigurableFallbackEntityResolver(null); + + assertThat(resolver.resolveEntity("testPublicId", systemId)).isNull(); + assertThat(resolver.fallbackInvoked).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "https://example.org/schema.dtd", "https://example.org/schema.xsd" }) + void resolveEntityCallsFallbackThatThrowsException(String systemId) { + ConfigurableFallbackEntityResolver resolver = new ConfigurableFallbackEntityResolver(true); + + assertThatExceptionOfType(ResolutionRejectedException.class) + .isThrownBy(() -> resolver.resolveEntity("testPublicId", systemId)); + assertThat(resolver.fallbackInvoked).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { "https://example.org/schema.dtd", "https://example.org/schema.xsd" }) + void resolveEntityCallsFallbackThatReturnsInputSource(String systemId) throws Exception { + InputSource expected = Mockito.mock(InputSource.class); + ConfigurableFallbackEntityResolver resolver = new ConfigurableFallbackEntityResolver(expected); + + assertThat(resolver.resolveEntity("testPublicId", systemId)).isSameAs(expected); + assertThat(resolver.fallbackInvoked).isTrue(); + } + + + private static final class NoOpResourceLoader implements ResourceLoader { + + @Override + public Resource getResource(String location) { + return null; + } + + @Override + public ClassLoader getClassLoader() { + return ResourceEntityResolverTests.class.getClassLoader(); + } + } + + private static class ConfigurableFallbackEntityResolver extends ResourceEntityResolver { + + private final boolean shouldThrow; + + @Nullable + private final InputSource returnValue; + + boolean fallbackInvoked = false; + + + private ConfigurableFallbackEntityResolver(boolean shouldThrow) { + super(new NoOpResourceLoader()); + this.shouldThrow = shouldThrow; + this.returnValue = null; + } + + private ConfigurableFallbackEntityResolver(@Nullable InputSource returnValue) { + super(new NoOpResourceLoader()); + this.shouldThrow = false; + this.returnValue = returnValue; + } + + + @Nullable + @Override + protected InputSource resolveSchemaEntity(String publicId, String systemId) { + this.fallbackInvoked = true; + if (this.shouldThrow) { + throw new ResolutionRejectedException(); + } + return this.returnValue; + } + } + + static class ResolutionRejectedException extends RuntimeException {} + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java index 083d6f9de35e..c431485456f0 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java @@ -54,7 +54,7 @@ public class UtilNamespaceHandlerTests { @BeforeEach - public void setUp() { + void setUp() { this.beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this.beanFactory); reader.setEventListener(this.listener); @@ -63,19 +63,19 @@ public void setUp() { @Test - public void testConstant() { + void testConstant() { Integer min = (Integer) this.beanFactory.getBean("min"); assertThat(min.intValue()).isEqualTo(Integer.MIN_VALUE); } @Test - public void testConstantWithDefaultName() { + void testConstantWithDefaultName() { Integer max = (Integer) this.beanFactory.getBean("java.lang.Integer.MAX_VALUE"); assertThat(max.intValue()).isEqualTo(Integer.MAX_VALUE); } @Test - public void testEvents() { + void testEvents() { ComponentDefinition propertiesComponent = this.listener.getComponentDefinition("myProperties"); assertThat(propertiesComponent).as("Event for 'myProperties' not sent").isNotNull(); AbstractBeanDefinition propertiesBean = (AbstractBeanDefinition) propertiesComponent.getBeanDefinitions()[0]; @@ -88,109 +88,106 @@ public void testEvents() { } @Test - public void testNestedProperties() { + void testNestedProperties() { TestBean bean = (TestBean) this.beanFactory.getBean("testBean"); Properties props = bean.getSomeProperties(); assertThat(props.get("foo")).as("Incorrect property value").isEqualTo("bar"); } @Test - public void testPropertyPath() { + void testPropertyPath() { String name = (String) this.beanFactory.getBean("name"); assertThat(name).isEqualTo("Rob Harrop"); } @Test - public void testNestedPropertyPath() { + void testNestedPropertyPath() { TestBean bean = (TestBean) this.beanFactory.getBean("testBean"); assertThat(bean.getName()).isEqualTo("Rob Harrop"); } @Test - public void testSimpleMap() { - Map map = (Map) this.beanFactory.getBean("simpleMap"); + void testSimpleMap() { + Map map = (Map) this.beanFactory.getBean("simpleMap"); assertThat(map.get("foo")).isEqualTo("bar"); - Map map2 = (Map) this.beanFactory.getBean("simpleMap"); + Map map2 = (Map) this.beanFactory.getBean("simpleMap"); assertThat(map == map2).isTrue(); } @Test - public void testScopedMap() { - Map map = (Map) this.beanFactory.getBean("scopedMap"); + void testScopedMap() { + Map map = (Map) this.beanFactory.getBean("scopedMap"); assertThat(map.get("foo")).isEqualTo("bar"); - Map map2 = (Map) this.beanFactory.getBean("scopedMap"); + Map map2 = (Map) this.beanFactory.getBean("scopedMap"); assertThat(map2.get("foo")).isEqualTo("bar"); assertThat(map != map2).isTrue(); } @Test - public void testSimpleList() { - List list = (List) this.beanFactory.getBean("simpleList"); + void testSimpleList() { + List list = (List) this.beanFactory.getBean("simpleList"); assertThat(list.get(0)).isEqualTo("Rob Harrop"); - List list2 = (List) this.beanFactory.getBean("simpleList"); + List list2 = (List) this.beanFactory.getBean("simpleList"); assertThat(list == list2).isTrue(); } @Test - public void testScopedList() { - List list = (List) this.beanFactory.getBean("scopedList"); + void testScopedList() { + List list = (List) this.beanFactory.getBean("scopedList"); assertThat(list.get(0)).isEqualTo("Rob Harrop"); - List list2 = (List) this.beanFactory.getBean("scopedList"); + List list2 = (List) this.beanFactory.getBean("scopedList"); assertThat(list2.get(0)).isEqualTo("Rob Harrop"); assertThat(list != list2).isTrue(); } @Test - public void testSimpleSet() { - Set set = (Set) this.beanFactory.getBean("simpleSet"); + void testSimpleSet() { + Set set = (Set) this.beanFactory.getBean("simpleSet"); assertThat(set.contains("Rob Harrop")).isTrue(); - Set set2 = (Set) this.beanFactory.getBean("simpleSet"); + Set set2 = (Set) this.beanFactory.getBean("simpleSet"); assertThat(set == set2).isTrue(); } @Test - public void testScopedSet() { - Set set = (Set) this.beanFactory.getBean("scopedSet"); + void testScopedSet() { + Set set = (Set) this.beanFactory.getBean("scopedSet"); assertThat(set.contains("Rob Harrop")).isTrue(); - Set set2 = (Set) this.beanFactory.getBean("scopedSet"); + Set set2 = (Set) this.beanFactory.getBean("scopedSet"); assertThat(set2.contains("Rob Harrop")).isTrue(); assertThat(set != set2).isTrue(); } @Test - public void testMapWithRef() { - Map map = (Map) this.beanFactory.getBean("mapWithRef"); - boolean condition = map instanceof TreeMap; - assertThat(condition).isTrue(); + void testMapWithRef() { + Map map = (Map) this.beanFactory.getBean("mapWithRef"); + assertThat(map).isInstanceOf(TreeMap.class); assertThat(map.get("bean")).isEqualTo(this.beanFactory.getBean("testBean")); } @Test - public void testMapWithTypes() { - Map map = (Map) this.beanFactory.getBean("mapWithTypes"); - boolean condition = map instanceof LinkedCaseInsensitiveMap; - assertThat(condition).isTrue(); + void testMapWithTypes() { + Map map = (Map) this.beanFactory.getBean("mapWithTypes"); + assertThat(map).isInstanceOf(LinkedCaseInsensitiveMap.class); assertThat(map.get("bean")).isEqualTo(this.beanFactory.getBean("testBean")); } @Test - public void testNestedCollections() { + void testNestedCollections() { TestBean bean = (TestBean) this.beanFactory.getBean("nestedCollectionsBean"); - List list = bean.getSomeList(); - assertThat(list.size()).isEqualTo(1); + List list = bean.getSomeList(); + assertThat(list).hasSize(1); assertThat(list.get(0)).isEqualTo("foo"); - Set set = bean.getSomeSet(); - assertThat(set.size()).isEqualTo(1); + Set set = bean.getSomeSet(); + assertThat(set).hasSize(1); assertThat(set.contains("bar")).isTrue(); - Map map = bean.getSomeMap(); - assertThat(map.size()).isEqualTo(1); - boolean condition = map.get("foo") instanceof Set; - assertThat(condition).isTrue(); - Set innerSet = (Set) map.get("foo"); - assertThat(innerSet.size()).isEqualTo(1); + Map map = bean.getSomeMap(); + assertThat(map).hasSize(1); + assertThat(map.get("foo")).isInstanceOf(Set.class); + Set innerSet = (Set) map.get("foo"); + assertThat(innerSet).hasSize(1); assertThat(innerSet.contains("bar")).isTrue(); TestBean bean2 = (TestBean) this.beanFactory.getBean("nestedCollectionsBean"); @@ -203,18 +200,18 @@ public void testNestedCollections() { } @Test - public void testNestedShortcutCollections() { + void testNestedShortcutCollections() { TestBean bean = (TestBean) this.beanFactory.getBean("nestedShortcutCollections"); - assertThat(bean.getStringArray().length).isEqualTo(1); + assertThat(bean.getStringArray()).hasSize(1); assertThat(bean.getStringArray()[0]).isEqualTo("fooStr"); - List list = bean.getSomeList(); - assertThat(list.size()).isEqualTo(1); + List list = bean.getSomeList(); + assertThat(list).hasSize(1); assertThat(list.get(0)).isEqualTo("foo"); - Set set = bean.getSomeSet(); - assertThat(set.size()).isEqualTo(1); + Set set = bean.getSomeSet(); + assertThat(set).hasSize(1); assertThat(set.contains("bar")).isTrue(); TestBean bean2 = (TestBean) this.beanFactory.getBean("nestedShortcutCollections"); @@ -227,20 +224,20 @@ public void testNestedShortcutCollections() { } @Test - public void testNestedInCollections() { + void testNestedInCollections() { TestBean bean = (TestBean) this.beanFactory.getBean("nestedCustomTagBean"); - List list = bean.getSomeList(); - assertThat(list.size()).isEqualTo(1); + List list = bean.getSomeList(); + assertThat(list).hasSize(1); assertThat(list.get(0)).isEqualTo(Integer.MIN_VALUE); - Set set = bean.getSomeSet(); - assertThat(set.size()).isEqualTo(2); + Set set = bean.getSomeSet(); + assertThat(set).hasSize(2); assertThat(set.contains(Thread.State.NEW)).isTrue(); assertThat(set.contains(Thread.State.RUNNABLE)).isTrue(); - Map map = bean.getSomeMap(); - assertThat(map.size()).isEqualTo(1); + Map map = bean.getSomeMap(); + assertThat(map).hasSize(1); assertThat(map.get("min")).isEqualTo(CustomEnum.VALUE_1); TestBean bean2 = (TestBean) this.beanFactory.getBean("nestedCustomTagBean"); @@ -253,93 +250,93 @@ public void testNestedInCollections() { } @Test - public void testCircularCollections() { + void testCircularCollections() { TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionsBean"); - List list = bean.getSomeList(); - assertThat(list.size()).isEqualTo(1); + List list = bean.getSomeList(); + assertThat(list).hasSize(1); assertThat(list.get(0)).isEqualTo(bean); - Set set = bean.getSomeSet(); - assertThat(set.size()).isEqualTo(1); + Set set = bean.getSomeSet(); + assertThat(set).hasSize(1); assertThat(set.contains(bean)).isTrue(); - Map map = bean.getSomeMap(); - assertThat(map.size()).isEqualTo(1); + Map map = bean.getSomeMap(); + assertThat(map).hasSize(1); assertThat(map.get("foo")).isEqualTo(bean); } @Test - public void testCircularCollectionBeansStartingWithList() { + void testCircularCollectionBeansStartingWithList() { this.beanFactory.getBean("circularList"); TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionBeansBean"); - List list = bean.getSomeList(); + List list = bean.getSomeList(); assertThat(Proxy.isProxyClass(list.getClass())).isTrue(); - assertThat(list.size()).isEqualTo(1); + assertThat(list).hasSize(1); assertThat(list.get(0)).isEqualTo(bean); - Set set = bean.getSomeSet(); + Set set = bean.getSomeSet(); assertThat(Proxy.isProxyClass(set.getClass())).isFalse(); - assertThat(set.size()).isEqualTo(1); + assertThat(set).hasSize(1); assertThat(set.contains(bean)).isTrue(); - Map map = bean.getSomeMap(); + Map map = bean.getSomeMap(); assertThat(Proxy.isProxyClass(map.getClass())).isFalse(); - assertThat(map.size()).isEqualTo(1); + assertThat(map).hasSize(1); assertThat(map.get("foo")).isEqualTo(bean); } @Test - public void testCircularCollectionBeansStartingWithSet() { + void testCircularCollectionBeansStartingWithSet() { this.beanFactory.getBean("circularSet"); TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionBeansBean"); - List list = bean.getSomeList(); + List list = bean.getSomeList(); assertThat(Proxy.isProxyClass(list.getClass())).isFalse(); - assertThat(list.size()).isEqualTo(1); + assertThat(list).hasSize(1); assertThat(list.get(0)).isEqualTo(bean); - Set set = bean.getSomeSet(); + Set set = bean.getSomeSet(); assertThat(Proxy.isProxyClass(set.getClass())).isTrue(); - assertThat(set.size()).isEqualTo(1); + assertThat(set).hasSize(1); assertThat(set.contains(bean)).isTrue(); - Map map = bean.getSomeMap(); + Map map = bean.getSomeMap(); assertThat(Proxy.isProxyClass(map.getClass())).isFalse(); - assertThat(map.size()).isEqualTo(1); + assertThat(map).hasSize(1); assertThat(map.get("foo")).isEqualTo(bean); } @Test - public void testCircularCollectionBeansStartingWithMap() { + void testCircularCollectionBeansStartingWithMap() { this.beanFactory.getBean("circularMap"); TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionBeansBean"); - List list = bean.getSomeList(); + List list = bean.getSomeList(); assertThat(Proxy.isProxyClass(list.getClass())).isFalse(); - assertThat(list.size()).isEqualTo(1); + assertThat(list).hasSize(1); assertThat(list.get(0)).isEqualTo(bean); - Set set = bean.getSomeSet(); + Set set = bean.getSomeSet(); assertThat(Proxy.isProxyClass(set.getClass())).isFalse(); - assertThat(set.size()).isEqualTo(1); + assertThat(set).hasSize(1); assertThat(set.contains(bean)).isTrue(); - Map map = bean.getSomeMap(); + Map map = bean.getSomeMap(); assertThat(Proxy.isProxyClass(map.getClass())).isTrue(); - assertThat(map.size()).isEqualTo(1); + assertThat(map).hasSize(1); assertThat(map.get("foo")).isEqualTo(bean); } @Test - public void testNestedInConstructor() { + void testNestedInConstructor() { TestBean bean = (TestBean) this.beanFactory.getBean("constructedTestBean"); assertThat(bean.getName()).isEqualTo("Rob Harrop"); } @Test - public void testLoadProperties() { + void testLoadProperties() { Properties props = (Properties) this.beanFactory.getBean("myProperties"); assertThat(props.get("foo")).as("Incorrect property value").isEqualTo("bar"); assertThat(props.get("foo2")).as("Incorrect property value").isNull(); @@ -348,7 +345,7 @@ public void testLoadProperties() { } @Test - public void testScopedProperties() { + void testScopedProperties() { Properties props = (Properties) this.beanFactory.getBean("myScopedProperties"); assertThat(props.get("foo")).as("Incorrect property value").isEqualTo("bar"); assertThat(props.get("foo2")).as("Incorrect property value").isNull(); @@ -359,35 +356,35 @@ public void testScopedProperties() { } @Test - public void testLocalProperties() { + void testLocalProperties() { Properties props = (Properties) this.beanFactory.getBean("myLocalProperties"); assertThat(props.get("foo")).as("Incorrect property value").isNull(); assertThat(props.get("foo2")).as("Incorrect property value").isEqualTo("bar2"); } @Test - public void testMergedProperties() { + void testMergedProperties() { Properties props = (Properties) this.beanFactory.getBean("myMergedProperties"); assertThat(props.get("foo")).as("Incorrect property value").isEqualTo("bar"); assertThat(props.get("foo2")).as("Incorrect property value").isEqualTo("bar2"); } @Test - public void testLocalOverrideDefault() { + void testLocalOverrideDefault() { Properties props = (Properties) this.beanFactory.getBean("defaultLocalOverrideProperties"); assertThat(props.get("foo")).as("Incorrect property value").isEqualTo("bar"); assertThat(props.get("foo2")).as("Incorrect property value").isEqualTo("local2"); } @Test - public void testLocalOverrideFalse() { + void testLocalOverrideFalse() { Properties props = (Properties) this.beanFactory.getBean("falseLocalOverrideProperties"); assertThat(props.get("foo")).as("Incorrect property value").isEqualTo("bar"); assertThat(props.get("foo2")).as("Incorrect property value").isEqualTo("local2"); } @Test - public void testLocalOverrideTrue() { + void testLocalOverrideTrue() { Properties props = (Properties) this.beanFactory.getBean("trueLocalOverrideProperties"); assertThat(props.get("foo")).as("Incorrect property value").isEqualTo("local"); assertThat(props.get("foo2")).as("Incorrect property value").isEqualTo("local2"); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java index b9c66fe39c84..e67aa1d97299 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java @@ -332,9 +332,9 @@ public void testObjectArray() throws Exception { public void testIntegerArray() throws Exception { HasMap hasMap = (HasMap) this.beanFactory.getBean("integerArray"); assertThat(hasMap.getIntegerArray().length == 3).isTrue(); - assertThat(hasMap.getIntegerArray()[0].intValue() == 0).isTrue(); - assertThat(hasMap.getIntegerArray()[1].intValue() == 1).isTrue(); - assertThat(hasMap.getIntegerArray()[2].intValue() == 2).isTrue(); + assertThat(hasMap.getIntegerArray()[0] == 0).isTrue(); + assertThat(hasMap.getIntegerArray()[1] == 1).isTrue(); + assertThat(hasMap.getIntegerArray()[2] == 2).isTrue(); } @Test @@ -356,12 +356,12 @@ public void testClassList() throws Exception { @Test public void testProps() throws Exception { HasMap hasMap = (HasMap) this.beanFactory.getBean("props"); - assertThat(hasMap.getProps().size()).isEqualTo(2); + assertThat(hasMap.getProps()).hasSize(2); assertThat(hasMap.getProps().getProperty("foo")).isEqualTo("bar"); assertThat(hasMap.getProps().getProperty("2")).isEqualTo("TWO"); HasMap hasMap2 = (HasMap) this.beanFactory.getBean("propsViaMap"); - assertThat(hasMap2.getProps().size()).isEqualTo(2); + assertThat(hasMap2.getProps()).hasSize(2); assertThat(hasMap2.getProps().getProperty("foo")).isEqualTo("bar"); assertThat(hasMap2.getProps().getProperty("2")).isEqualTo("TWO"); } @@ -432,7 +432,7 @@ public void testChoiceBetweenSetAndMap() { boolean condition = sam.getObject() instanceof Map; assertThat(condition).as("Didn't choose constructor with Map argument").isTrue(); Map map = (Map) sam.getObject(); - assertThat(map.size()).isEqualTo(3); + assertThat(map).hasSize(3); assertThat(map.get("key1")).isEqualTo("val1"); assertThat(map.get("key2")).isEqualTo("val2"); assertThat(map.get("key3")).isEqualTo("val3"); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java index 69768da8278f..c71bd28ace5f 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -109,7 +109,7 @@ public void withFreshInputStream() { private void testBeanDefinitions(BeanDefinitionRegistry registry) { assertThat(registry.getBeanDefinitionCount()).isEqualTo(24); - assertThat(registry.getBeanDefinitionNames().length).isEqualTo(24); + assertThat(registry.getBeanDefinitionNames()).hasSize(24); assertThat(Arrays.asList(registry.getBeanDefinitionNames()).contains("rod")).isTrue(); assertThat(Arrays.asList(registry.getBeanDefinitionNames()).contains("aliased")).isTrue(); assertThat(registry.containsBeanDefinition("rod")).isTrue(); @@ -118,7 +118,7 @@ private void testBeanDefinitions(BeanDefinitionRegistry registry) { assertThat(registry.getBeanDefinition("aliased").getBeanClassName()).isEqualTo(TestBean.class.getName()); assertThat(registry.isAlias("youralias")).isTrue(); String[] aliases = registry.getAliases("aliased"); - assertThat(aliases.length).isEqualTo(2); + assertThat(aliases).hasSize(2); assertThat(ObjectUtils.containsElement(aliases, "myalias")).isTrue(); assertThat(ObjectUtils.containsElement(aliases, "youralias")).isTrue(); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlListableBeanFactoryTests.java index 4b5c005788da..791340e2ec3c 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlListableBeanFactoryTests.java @@ -138,7 +138,7 @@ public void autoAliasing() { TestBean alias1 = (TestBean) getBeanFactory().getBean("myalias"); assertThat(tb1 == alias1).isTrue(); List tb1Aliases = Arrays.asList(getBeanFactory().getAliases("aliased")); - assertThat(tb1Aliases.size()).isEqualTo(2); + assertThat(tb1Aliases).hasSize(2); assertThat(tb1Aliases.contains("myalias")).isTrue(); assertThat(tb1Aliases.contains("youralias")).isTrue(); assertThat(beanNames.contains("aliased")).isTrue(); @@ -156,7 +156,7 @@ public void autoAliasing() { assertThat(tb2 == alias3b).isTrue(); List tb2Aliases = Arrays.asList(getBeanFactory().getAliases("multiAliased")); - assertThat(tb2Aliases.size()).isEqualTo(4); + assertThat(tb2Aliases).hasSize(4); assertThat(tb2Aliases.contains("alias1")).isTrue(); assertThat(tb2Aliases.contains("alias2")).isTrue(); assertThat(tb2Aliases.contains("alias3")).isTrue(); @@ -173,7 +173,7 @@ public void autoAliasing() { assertThat(tb3 == alias4).isTrue(); assertThat(tb3 == alias5).isTrue(); List tb3Aliases = Arrays.asList(getBeanFactory().getAliases("aliasWithoutId1")); - assertThat(tb3Aliases.size()).isEqualTo(2); + assertThat(tb3Aliases).hasSize(2); assertThat(tb3Aliases.contains("aliasWithoutId2")).isTrue(); assertThat(tb3Aliases.contains("aliasWithoutId3")).isTrue(); assertThat(beanNames.contains("aliasWithoutId1")).isTrue(); @@ -184,7 +184,7 @@ public void autoAliasing() { assertThat(tb4.getName()).isNull(); Map drs = getListableBeanFactory().getBeansOfType(DummyReferencer.class, false, false); - assertThat(drs.size()).isEqualTo(5); + assertThat(drs).hasSize(5); assertThat(drs.containsKey(DummyReferencer.class.getName() + "#0")).isTrue(); assertThat(drs.containsKey(DummyReferencer.class.getName() + "#1")).isTrue(); assertThat(drs.containsKey(DummyReferencer.class.getName() + "#2")).isTrue(); diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java index a1ce558b785a..50062d8a824d 100644 --- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -502,7 +502,7 @@ void testCharacterEditor() { CharBean cb = new CharBean(); BeanWrapper bw = new BeanWrapperImpl(cb); - bw.setPropertyValue("myChar", Character.valueOf('c')); + bw.setPropertyValue("myChar", 'c'); assertThat(cb.getMyChar()).isEqualTo('c'); bw.setPropertyValue("myChar", "c"); @@ -1357,12 +1357,12 @@ void testConversionToOldCollections() throws PropertyVetoException { bw.registerCustomEditor(Hashtable.class, new CustomMapEditor(Hashtable.class)); bw.setPropertyValue("vector", new String[] {"a", "b"}); - assertThat(tb.getVector().size()).isEqualTo(2); + assertThat(tb.getVector()).hasSize(2); assertThat(tb.getVector().get(0)).isEqualTo("a"); assertThat(tb.getVector().get(1)).isEqualTo("b"); bw.setPropertyValue("hashtable", Collections.singletonMap("foo", "bar")); - assertThat(tb.getHashtable().size()).isEqualTo(1); + assertThat(tb.getHashtable()).hasSize(1); assertThat(tb.getHashtable().get("foo")).isEqualTo("bar"); } @@ -1393,7 +1393,7 @@ public void setAsText(String text) throws IllegalArgumentException { } }); bw.setPropertyValue("array", new String[] {"a", "b"}); - assertThat(tb.getArray().length).isEqualTo(2); + assertThat(tb.getArray()).hasSize(2); assertThat(tb.getArray()[0].getName()).isEqualTo("a"); assertThat(tb.getArray()[1].getName()).isEqualTo("b"); } diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/PropertiesEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/PropertiesEditorTests.java index 32fb123680ba..772f44e1a988 100644 --- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/PropertiesEditorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/PropertiesEditorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -139,7 +139,7 @@ public void nullValue() { PropertiesEditor pe= new PropertiesEditor(); pe.setAsText(null); Properties p = (Properties) pe.getValue(); - assertThat(p.size()).isEqualTo(0); + assertThat(p).isEmpty(); } @Test @@ -163,7 +163,7 @@ public void usingMapAsValueSource() throws Exception { boolean condition = value instanceof Properties; assertThat(condition).isTrue(); Properties props = (Properties) value; - assertThat(props.size()).isEqualTo(3); + assertThat(props).hasSize(3); assertThat(props.getProperty("one")).isEqualTo("1"); assertThat(props.getProperty("two")).isEqualTo("2"); assertThat(props.getProperty("three")).isEqualTo("3"); diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/TestBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/TestBean.java index ec9b74286330..e9f9e107e835 100644 --- a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/TestBean.java +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/TestBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -74,7 +74,7 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt private Date date = new Date(); - private Float myFloat = Float.valueOf(0.0f); + private Float myFloat = 0.0f; private Collection friends = new ArrayList<>(); diff --git a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/TypeHelper.java b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/TypeHelper.java index 8789d3f530fd..11400ae00dc2 100644 --- a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/TypeHelper.java +++ b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/TypeHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context-indexer/src/test/java/org/springframework/context/index/processor/CandidateComponentsIndexerTests.java b/spring-context-indexer/src/test/java/org/springframework/context/index/processor/CandidateComponentsIndexerTests.java index be3d30dc4207..718060f579aa 100644 --- a/spring-context-indexer/src/test/java/org/springframework/context/index/processor/CandidateComponentsIndexerTests.java +++ b/spring-context-indexer/src/test/java/org/springframework/context/index/processor/CandidateComponentsIndexerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -82,13 +82,13 @@ void createCompiler(@TempDir Path tempDir) throws IOException { @Test void noCandidate() { CandidateComponentsMetadata metadata = compile(SampleNone.class); - assertThat(metadata.getItems()).hasSize(0); + assertThat(metadata.getItems()).isEmpty(); } @Test void noAnnotation() { CandidateComponentsMetadata metadata = compile(CandidateComponentsIndexerTests.class); - assertThat(metadata.getItems()).hasSize(0); + assertThat(metadata.getItems()).isEmpty(); } @Test @@ -214,7 +214,7 @@ void embeddedCandidatesAreDetected() @Test void embeddedNonStaticCandidateAreIgnored() { CandidateComponentsMetadata metadata = compile(SampleNonStaticEmbedded.class); - assertThat(metadata.getItems()).hasSize(0); + assertThat(metadata.getItems()).isEmpty(); } private void testComponent(Class... classes) { diff --git a/spring-context-indexer/src/test/java/org/springframework/context/index/processor/Metadata.java b/spring-context-indexer/src/test/java/org/springframework/context/index/processor/Metadata.java index f0464b9333e5..74c39cfedb7b 100644 --- a/spring-context-indexer/src/test/java/org/springframework/context/index/processor/Metadata.java +++ b/spring-context-indexer/src/test/java/org/springframework/context/index/processor/Metadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultJCacheOperationSource.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultJCacheOperationSource.java index 77d031dc8585..4bde292d8fa5 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultJCacheOperationSource.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/DefaultJCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -166,7 +166,7 @@ public void setBeanFactory(BeanFactory beanFactory) { public void afterSingletonsInstantiated() { // Make sure that the cache resolver is initialized. An exception cache resolver is only // required if the exceptionCacheName attribute is set on an operation. - Assert.notNull(getDefaultCacheResolver(), "Cache resolver should have been initialized"); + Assert.state(getDefaultCacheResolver() != null, "Cache resolver should have been initialized"); } diff --git a/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java b/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java index 990b692a7873..047dcd98650b 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java +++ b/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java index 624b72cdc24f..6034e47822bf 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java index 15185bba6c37..82015a57549f 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -527,8 +527,8 @@ private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IO if (schedulerFactory == null) { // Create local SchedulerFactory instance (typically a StdSchedulerFactory) schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass); - if (schedulerFactory instanceof StdSchedulerFactory) { - initSchedulerFactory((StdSchedulerFactory) schedulerFactory); + if (schedulerFactory instanceof StdSchedulerFactory stdSchedulerFactory) { + initSchedulerFactory(stdSchedulerFactory); } else if (this.configLocation != null || this.quartzProperties != null || this.taskExecutor != null || this.dataSource != null) { @@ -622,11 +622,11 @@ private Scheduler prepareScheduler(SchedulerFactory schedulerFactory) throws Sch this.jobFactory = new AdaptableJobFactory(); } if (this.jobFactory != null) { - if (this.applicationContext != null && this.jobFactory instanceof ApplicationContextAware) { - ((ApplicationContextAware) this.jobFactory).setApplicationContext(this.applicationContext); + if (this.applicationContext != null && this.jobFactory instanceof ApplicationContextAware applicationContextAware) { + applicationContextAware.setApplicationContext(this.applicationContext); } - if (this.jobFactory instanceof SchedulerContextAware) { - ((SchedulerContextAware) this.jobFactory).setSchedulerContext(scheduler.getContext()); + if (this.jobFactory instanceof SchedulerContextAware schedulerContextAware) { + schedulerContextAware.setSchedulerContext(scheduler.getContext()); } scheduler.setJobFactory(this.jobFactory); } diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBeanRuntimeHints.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBeanRuntimeHints.java index daf2b17d358d..c2d727a45588 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBeanRuntimeHints.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBeanRuntimeHints.java @@ -38,6 +38,7 @@ class SchedulerFactoryBeanRuntimeHints implements RuntimeHintsRegistrar { private final ReflectiveRuntimeHintsRegistrar reflectiveRegistrar = new ReflectiveRuntimeHintsRegistrar(); + @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { if (!ClassUtils.isPresent(SCHEDULER_FACTORY_CLASS_NAME, classLoader)) { @@ -53,4 +54,5 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) { private void typeHint(Builder typeHint) { typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS).onReachableType(SchedulerFactoryBean.class); } + } diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AbstractCacheOperationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AbstractCacheOperationTests.java index c7a0cfcd4776..18ca02df38f8 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AbstractCacheOperationTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AbstractCacheOperationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -62,7 +62,7 @@ protected
CacheMethodDetails create(Class annotatio Class targetType, String methodName, Class... parameterTypes) { Method method = ReflectionUtils.findMethod(targetType, methodName, parameterTypes); - Assert.notNull(method, "requested method '" + methodName + "'does not exist"); + Assert.notNull(method, () -> "requested method '" + methodName + "'does not exist"); A cacheAnnotation = method.getAnnotation(annotationType); return new DefaultCacheMethodDetails<>(method, cacheAnnotation, getCacheName(cacheAnnotation)); } diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java index bcc9276243bd..693db9b13c71 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -202,7 +202,7 @@ protected > T getCacheOperation( private JCacheOperation getCacheOperation(Class targetType, String methodName, Class... parameterTypes) { Method method = ReflectionUtils.findMethod(targetType, methodName, parameterTypes); - Assert.notNull(method, "requested method '" + methodName + "'does not exist"); + Assert.notNull(method, () -> "requested method '" + methodName + "'does not exist"); return source.getCacheOperation(method, targetType); } diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CachePutOperationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CachePutOperationTests.java index 534094c0c934..dee2f07ba3d0 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CachePutOperationTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CachePutOperationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -45,7 +45,7 @@ public void simplePut() { CachePutOperation operation = createSimpleOperation(); CacheInvocationParameter[] allParameters = operation.getAllParameters(2L, sampleInstance); - assertThat(allParameters.length).isEqualTo(2); + assertThat(allParameters).hasSize(2); assertCacheInvocationParameter(allParameters[0], Long.class, 2L, 0); assertCacheInvocationParameter(allParameters[1], SampleObject.class, sampleInstance, 1); diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllOperationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllOperationTests.java index 7327a4059c50..083ae9048b14 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllOperationTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllOperationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -42,7 +42,7 @@ public void simpleRemoveAll() { CacheRemoveAllOperation operation = createSimpleOperation(); CacheInvocationParameter[] allParameters = operation.getAllParameters(); - assertThat(allParameters.length).isEqualTo(0); + assertThat(allParameters).isEmpty(); } } diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheRemoveOperationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheRemoveOperationTests.java index b8ae9595a1b3..63c792d74c92 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheRemoveOperationTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheRemoveOperationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -42,7 +42,7 @@ public void simpleRemove() { CacheRemoveOperation operation = createSimpleOperation(); CacheInvocationParameter[] allParameters = operation.getAllParameters(2L); - assertThat(allParameters.length).isEqualTo(1); + assertThat(allParameters).hasSize(1); assertCacheInvocationParameter(allParameters[0], Long.class, 2L, 0); } diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapterTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapterTests.java index 1b1e91495b71..8a5ad47f3044 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapterTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -46,7 +46,7 @@ public void resolveSimpleCache() throws Exception { CacheResolverAdapter adapter = new CacheResolverAdapter(getCacheResolver(dummyContext, "testCache")); Collection caches = adapter.resolveCaches(dummyContext); assertThat(caches).isNotNull(); - assertThat(caches.size()).isEqualTo(1); + assertThat(caches).hasSize(1); assertThat(caches.iterator().next().getName()).isEqualTo("testCache"); } diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResultOperationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResultOperationTests.java index aff8f3ebe611..0764e1788e45 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResultOperationTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResultOperationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,11 +57,11 @@ public void simpleGet() { assertThat(operation.getExceptionCacheResolver()).isEqualTo(defaultExceptionCacheResolver); CacheInvocationParameter[] allParameters = operation.getAllParameters(2L); - assertThat(allParameters.length).isEqualTo(1); + assertThat(allParameters).hasSize(1); assertCacheInvocationParameter(allParameters[0], Long.class, 2L, 0); CacheInvocationParameter[] keyParameters = operation.getKeyParameters(2L); - assertThat(keyParameters.length).isEqualTo(1); + assertThat(keyParameters).hasSize(1); assertCacheInvocationParameter(keyParameters[0], Long.class, 2L, 0); } @@ -72,7 +72,7 @@ public void multiParameterKey() { CacheResultOperation operation = createDefaultOperation(methodDetails); CacheInvocationParameter[] keyParameters = operation.getKeyParameters(3L, Boolean.TRUE, "Foo"); - assertThat(keyParameters.length).isEqualTo(2); + assertThat(keyParameters).hasSize(2); assertCacheInvocationParameter(keyParameters[0], Long.class, 3L, 0); assertCacheInvocationParameter(keyParameters[1], String.class, "Foo", 2); } @@ -107,11 +107,11 @@ public void annotatedGet() { CacheInvocationParameter[] parameters = operation.getAllParameters(2L, "foo"); Set firstParameterAnnotations = parameters[0].getAnnotations(); - assertThat(firstParameterAnnotations.size()).isEqualTo(1); + assertThat(firstParameterAnnotations).hasSize(1); assertThat(firstParameterAnnotations.iterator().next().annotationType()).isEqualTo(CacheKey.class); Set secondParameterAnnotations = parameters[1].getAnnotations(); - assertThat(secondParameterAnnotations.size()).isEqualTo(1); + assertThat(secondParameterAnnotations).hasSize(1); assertThat(secondParameterAnnotations.iterator().next().annotationType()).isEqualTo(Value.class); } diff --git a/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java b/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java index 503044c97838..3833641d6bf1 100644 --- a/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java +++ b/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -79,22 +79,22 @@ public void javaMailSenderWithSimpleMessage() throws MessagingException, IOExcep assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(1); + assertThat(sender.transport.getSentMessages()).hasSize(1); MimeMessage sentMessage = sender.transport.getSentMessage(0); List
froms = Arrays.asList(sentMessage.getFrom()); - assertThat(froms.size()).isEqualTo(1); + assertThat(froms).hasSize(1); assertThat(((InternetAddress) froms.get(0)).getAddress()).isEqualTo("me@mail.org"); List
replyTos = Arrays.asList(sentMessage.getReplyTo()); assertThat(((InternetAddress) replyTos.get(0)).getAddress()).isEqualTo("reply@mail.org"); List
tos = Arrays.asList(sentMessage.getRecipients(Message.RecipientType.TO)); - assertThat(tos.size()).isEqualTo(1); + assertThat(tos).hasSize(1); assertThat(((InternetAddress) tos.get(0)).getAddress()).isEqualTo("you@mail.org"); List
ccs = Arrays.asList(sentMessage.getRecipients(Message.RecipientType.CC)); - assertThat(ccs.size()).isEqualTo(2); + assertThat(ccs).hasSize(2); assertThat(((InternetAddress) ccs.get(0)).getAddress()).isEqualTo("he@mail.org"); assertThat(((InternetAddress) ccs.get(1)).getAddress()).isEqualTo("she@mail.org"); List
bccs = Arrays.asList(sentMessage.getRecipients(Message.RecipientType.BCC)); - assertThat(bccs.size()).isEqualTo(2); + assertThat(bccs).hasSize(2); assertThat(((InternetAddress) bccs.get(0)).getAddress()).isEqualTo("us@mail.org"); assertThat(((InternetAddress) bccs.get(1)).getAddress()).isEqualTo("them@mail.org"); assertThat(sentMessage.getSentDate().getTime()).isEqualTo(sentDate.getTime()); @@ -120,14 +120,14 @@ public void javaMailSenderWithSimpleMessages() throws MessagingException { assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(2); + assertThat(sender.transport.getSentMessages()).hasSize(2); MimeMessage sentMessage1 = sender.transport.getSentMessage(0); List
tos1 = Arrays.asList(sentMessage1.getRecipients(Message.RecipientType.TO)); - assertThat(tos1.size()).isEqualTo(1); + assertThat(tos1).hasSize(1); assertThat(((InternetAddress) tos1.get(0)).getAddress()).isEqualTo("he@mail.org"); MimeMessage sentMessage2 = sender.transport.getSentMessage(1); List
tos2 = Arrays.asList(sentMessage2.getRecipients(Message.RecipientType.TO)); - assertThat(tos2.size()).isEqualTo(1); + assertThat(tos2).hasSize(1); assertThat(((InternetAddress) tos2.get(0)).getAddress()).isEqualTo("she@mail.org"); } @@ -146,7 +146,7 @@ public void javaMailSenderWithMimeMessage() throws MessagingException { assertThat(sender.transport.getConnectedUsername()).isEqualTo("username"); assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(1); + assertThat(sender.transport.getSentMessages()).hasSize(1); assertThat(sender.transport.getSentMessage(0)).isEqualTo(mimeMessage); } @@ -167,7 +167,7 @@ public void javaMailSenderWithMimeMessages() throws MessagingException { assertThat(sender.transport.getConnectedUsername()).isEqualTo("username"); assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(2); + assertThat(sender.transport.getSentMessages()).hasSize(2); assertThat(sender.transport.getSentMessage(0)).isEqualTo(mimeMessage1); assertThat(sender.transport.getSentMessage(1)).isEqualTo(mimeMessage2); } @@ -191,7 +191,7 @@ public void javaMailSenderWithMimeMessagePreparator() { assertThat(sender.transport.getConnectedUsername()).isEqualTo("username"); assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(1); + assertThat(sender.transport.getSentMessages()).hasSize(1); assertThat(sender.transport.getSentMessage(0)).isEqualTo(messages.get(0)); } @@ -218,7 +218,7 @@ public void javaMailSenderWithMimeMessagePreparators() { assertThat(sender.transport.getConnectedUsername()).isEqualTo("username"); assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(2); + assertThat(sender.transport.getSentMessages()).hasSize(2); assertThat(sender.transport.getSentMessage(0)).isEqualTo(messages.get(0)); assertThat(sender.transport.getSentMessage(1)).isEqualTo(messages.get(1)); } @@ -242,7 +242,7 @@ public void javaMailSenderWithMimeMessageHelper() throws MessagingException { assertThat(sender.transport.getConnectedUsername()).isEqualTo("username"); assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(1); + assertThat(sender.transport.getSentMessages()).hasSize(1); assertThat(sender.transport.getSentMessage(0)).isEqualTo(message.getMimeMessage()); } @@ -266,7 +266,7 @@ public void javaMailSenderWithMimeMessageHelperAndSpecificEncoding() throws Mess assertThat(sender.transport.getConnectedUsername()).isEqualTo("username"); assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(1); + assertThat(sender.transport.getSentMessages()).hasSize(1); assertThat(sender.transport.getSentMessage(0)).isEqualTo(message.getMimeMessage()); } @@ -291,7 +291,7 @@ public void javaMailSenderWithMimeMessageHelperAndDefaultEncoding() throws Messa assertThat(sender.transport.getConnectedUsername()).isEqualTo("username"); assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(1); + assertThat(sender.transport.getSentMessages()).hasSize(1); assertThat(sender.transport.getSentMessage(0)).isEqualTo(message.getMimeMessage()); } @@ -349,7 +349,7 @@ protected Transport getTransport(Session sess) throws NoSuchProviderException { assertThat(sender.transport.getConnectedUsername()).isEqualTo("username"); assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(1); + assertThat(sender.transport.getSentMessages()).hasSize(1); assertThat(sender.transport.getSentMessage(0)).isEqualTo(mimeMessage); } @@ -377,7 +377,7 @@ protected Transport getTransport(Session sess) throws NoSuchProviderException { assertThat(sender.transport.getConnectedUsername()).isEqualTo("username"); assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(1); + assertThat(sender.transport.getSentMessages()).hasSize(1); assertThat(sender.transport.getSentMessage(0)).isEqualTo(mimeMessage); } @@ -427,9 +427,9 @@ public void failedSimpleMessage() throws MessagingException { assertThat(sender.transport.getConnectedUsername()).isEqualTo("username"); assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(1); + assertThat(sender.transport.getSentMessages()).hasSize(1); assertThat(sender.transport.getSentMessage(0).getAllRecipients()[0]).isEqualTo(new InternetAddress("she@mail.org")); - assertThat(ex.getFailedMessages().size()).isEqualTo(1); + assertThat(ex.getFailedMessages()).hasSize(1); assertThat(ex.getFailedMessages().keySet().iterator().next()).isEqualTo(simpleMessage1); Object subEx = ex.getFailedMessages().values().iterator().next(); boolean condition = subEx instanceof MessagingException; @@ -460,9 +460,9 @@ public void failedMimeMessage() throws MessagingException { assertThat(sender.transport.getConnectedUsername()).isEqualTo("username"); assertThat(sender.transport.getConnectedPassword()).isEqualTo("password"); assertThat(sender.transport.isCloseCalled()).isTrue(); - assertThat(sender.transport.getSentMessages().size()).isEqualTo(1); + assertThat(sender.transport.getSentMessages()).hasSize(1); assertThat(sender.transport.getSentMessage(0)).isEqualTo(mimeMessage2); - assertThat(ex.getFailedMessages().size()).isEqualTo(1); + assertThat(ex.getFailedMessages()).hasSize(1); assertThat(ex.getFailedMessages().keySet().iterator().next()).isEqualTo(mimeMessage1); Object subEx = ex.getFailedMessages().values().iterator().next(); boolean condition = subEx instanceof MessagingException; diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java index 502c64fe2b35..37eede6f2a5b 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java b/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java index 02b291eb24a1..e1987c70d4b6 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/Caching.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context/src/main/java/org/springframework/context/MessageSourceAware.java b/spring-context/src/main/java/org/springframework/context/MessageSourceAware.java index 504dc6a34349..390021c7fc37 100644 --- a/spring-context/src/main/java/org/springframework/context/MessageSourceAware.java +++ b/spring-context/src/main/java/org/springframework/context/MessageSourceAware.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,13 @@ import org.springframework.beans.factory.Aware; /** - * Interface to be implemented by any object that wishes to be notified - * of the MessageSource (typically the ApplicationContext) that it runs in. + * Interface to be implemented by any object that wishes to be notified of the + * {@link MessageSource} (typically the ApplicationContext) that it runs in. * - *

Note that the MessageSource can usually also be passed on as bean - * reference (to arbitrary bean properties or constructor arguments), because - * it is defined as bean with name "messageSource" in the application context. + *

Note that the {@code MessageSource} can usually also be passed in as a bean + * reference (via arbitrary bean properties or constructor arguments), because + * it is defined as a bean with name {@code "messageSource"} in the application + * context. * * @author Juergen Hoeller * @author Chris Beams @@ -34,7 +35,7 @@ public interface MessageSourceAware extends Aware { /** - * Set the MessageSource that this object runs in. + * Set the {@link MessageSource} that this object runs in. *

Invoked after population of normal bean properties but before an init * callback like InitializingBean's afterPropertiesSet or a custom init-method. * Invoked before ApplicationContextAware's setApplicationContext. diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 37922364e5f4..7c717714c75a 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -79,6 +79,7 @@ * @author Ramnivas Laddad * @author Chris Beams * @author Stephane Nicoll + * @author Sam Brannen * @since 2.5 * @see org.springframework.core.type.classreading.MetadataReaderFactory * @see org.springframework.core.type.AnnotationMetadata @@ -340,13 +341,13 @@ private boolean indexSupportsIncludeFilters() { * @see #extractStereotype(TypeFilter) */ private boolean indexSupportsIncludeFilter(TypeFilter filter) { - if (filter instanceof AnnotationTypeFilter) { - Class annotation = ((AnnotationTypeFilter) filter).getAnnotationType(); - return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation) || - annotation.getName().startsWith("javax.")); + if (filter instanceof AnnotationTypeFilter annotationTypeFilter) { + Class annotationType = annotationTypeFilter.getAnnotationType(); + return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotationType) || + annotationType.getName().startsWith("jakarta.")); } - if (filter instanceof AssignableTypeFilter) { - Class target = ((AssignableTypeFilter) filter).getTargetType(); + if (filter instanceof AssignableTypeFilter assignableTypeFilter) { + Class target = assignableTypeFilter.getTargetType(); return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target); } return false; @@ -361,11 +362,11 @@ private boolean indexSupportsIncludeFilter(TypeFilter filter) { */ @Nullable private String extractStereotype(TypeFilter filter) { - if (filter instanceof AnnotationTypeFilter) { - return ((AnnotationTypeFilter) filter).getAnnotationType().getName(); + if (filter instanceof AnnotationTypeFilter annotationTypeFilter) { + return annotationTypeFilter.getAnnotationType().getName(); } - if (filter instanceof AssignableTypeFilter) { - return ((AssignableTypeFilter) filter).getTargetType().getName(); + if (filter instanceof AssignableTypeFilter assignableTypeFilter) { + return assignableTypeFilter.getTargetType().getName(); } return null; } @@ -536,10 +537,10 @@ protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { * Clear the local metadata cache, if any, removing all cached class metadata. */ public void clearCache() { - if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { + if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory cmrf) { // Clear cache in externally provided MetadataReaderFactory; this is a no-op // for a shared cache since it'll be cleared by the ApplicationContext. - ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); + cmrf.clearCache(); } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index ddc7d8c9bd53..b80ca7f5cdd2 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -147,9 +147,8 @@ public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, this.problemReporter = problemReporter; this.environment = environment; this.resourceLoader = resourceLoader; - this.propertySourceRegistry = (this.environment instanceof ConfigurableEnvironment ce - ? new PropertySourceRegistry(new PropertySourceProcessor(ce, this.resourceLoader)) - : null); + this.propertySourceRegistry = (this.environment instanceof ConfigurableEnvironment ce ? + new PropertySourceRegistry(new PropertySourceProcessor(ce, this.resourceLoader)) : null); this.registry = registry; this.componentScanParser = new ComponentScanAnnotationParser( environment, resourceLoader, componentScanBeanNameGenerator, registry); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 6c06e366c2b2..94995830b97e 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -319,6 +319,7 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe } @Override + @Nullable public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { boolean hasPropertySourceDescriptors = !CollectionUtils.isEmpty(this.propertySourceDescriptors); boolean hasImportRegistry = beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index f16c04040467..b957179e5486 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -17,7 +17,6 @@ package org.springframework.context.annotation; import java.io.IOException; -import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -66,14 +65,12 @@ public abstract class ConfigurationClassUtils { private static final Log logger = LogFactory.getLog(ConfigurationClassUtils.class); - private static final Set candidateIndicators = new HashSet<>(8); + private static final Set candidateIndicators = Set.of( + Component.class.getName(), + ComponentScan.class.getName(), + Import.class.getName(), + ImportResource.class.getName()); - static { - candidateIndicators.add(Component.class.getName()); - candidateIndicators.add(ComponentScan.class.getName()); - candidateIndicators.add(Import.class.getName()); - candidateIndicators.add(ImportResource.class.getName()); - } /** * Initialize a configuration class proxy for the specified class. diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ImportRuntimeHints.java b/spring-context/src/main/java/org/springframework/context/annotation/ImportRuntimeHints.java index ea38ed7f3d09..bc3d4992c542 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ImportRuntimeHints.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ImportRuntimeHints.java @@ -29,7 +29,7 @@ * should be processed. * *

Unlike declaring {@link RuntimeHintsRegistrar} using - * {@code spring/aot.factories}, this annotation allows for more flexible + * {@code META-INF/spring/aot.factories}, this annotation allows for more flexible * registration where it is only processed if the annotated component or bean * method is actually registered in the bean factory. To illustrate this * behavior, consider the following example: @@ -47,21 +47,26 @@ * * } * - * If the configuration class above is processed, {@code MyHints} will be - * contributed only if {@code MyCondition} matches. If it does not, and - * therefore {@code MyService} is not defined as a bean, the hints will + *

If the configuration class above is processed, {@code MyHints} will be + * contributed only if {@code MyCondition} matches. If the condition does not + * match, {@code MyService} will not be defined as a bean and the hints will * not be processed either. * - *

If several components refer to the same {@link RuntimeHintsRegistrar} - * implementation, it is invoked only once for a given bean factory - * processing. + *

{@code @ImportRuntimeHints} can also be applied to any test class that uses + * the Spring TestContext Framework to load an {@code ApplicationContext}. + * + *

If several components or test classes refer to the same {@link RuntimeHintsRegistrar} + * implementation, the registrar will only be invoked once for the given bean factory + * processing or test suite. * * @author Brian Clozel * @author Stephane Nicoll * @since 6.0 * @see org.springframework.aot.hint.RuntimeHints + * @see org.springframework.aot.hint.annotation.Reflective + * @see org.springframework.aot.hint.annotation.RegisterReflectionForBinding */ -@Target({ ElementType.TYPE, ElementType.METHOD }) +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ImportRuntimeHints { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ParserStrategyUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ParserStrategyUtils.java index 0aa2965b749a..8decf18cfad3 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ParserStrategyUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ParserStrategyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -60,8 +60,8 @@ static T instantiateClass(Class clazz, Class assignableTo, Environment if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } - ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ? - ((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader()); + ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory cbf ? + cbf.getBeanClassLoader() : resourceLoader.getClassLoader()); T instance = (T) createInstance(clazz, environment, resourceLoader, registry, classLoader); ParserStrategyUtils.invokeAwareMethods(instance, environment, resourceLoader, registry, classLoader); return instance; @@ -122,17 +122,18 @@ private static void invokeAwareMethods(Object parserStrategyBean, Environment en ResourceLoader resourceLoader, BeanDefinitionRegistry registry, @Nullable ClassLoader classLoader) { if (parserStrategyBean instanceof Aware) { - if (parserStrategyBean instanceof BeanClassLoaderAware && classLoader != null) { - ((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader); + if (parserStrategyBean instanceof BeanClassLoaderAware beanClassLoaderAware && classLoader != null) { + beanClassLoaderAware.setBeanClassLoader(classLoader); } - if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) { - ((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry); + if (parserStrategyBean instanceof BeanFactoryAware beanFactoryAware && + registry instanceof BeanFactory beanFactory) { + beanFactoryAware.setBeanFactory(beanFactory); } - if (parserStrategyBean instanceof EnvironmentAware) { - ((EnvironmentAware) parserStrategyBean).setEnvironment(environment); + if (parserStrategyBean instanceof EnvironmentAware environmentAware) { + environmentAware.setEnvironment(environment); } - if (parserStrategyBean instanceof ResourceLoaderAware) { - ((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader); + if (parserStrategyBean instanceof ResourceLoaderAware resourceLoaderAware) { + resourceLoaderAware.setResourceLoader(resourceLoader); } } } diff --git a/spring-context/src/main/java/org/springframework/context/aot/CglibClassHandler.java b/spring-context/src/main/java/org/springframework/context/aot/CglibClassHandler.java index 4401342fd812..61b393f801fb 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/CglibClassHandler.java +++ b/spring-context/src/main/java/org/springframework/context/aot/CglibClassHandler.java @@ -16,7 +16,6 @@ package org.springframework.context.aot; -import java.util.function.BiConsumer; import java.util.function.Consumer; import org.springframework.aot.generate.GeneratedFiles; @@ -26,6 +25,7 @@ import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeHint.Builder; import org.springframework.aot.hint.TypeReference; +import org.springframework.aot.hint.support.ClassHintUtils; import org.springframework.cglib.core.ReflectUtils; import org.springframework.core.io.ByteArrayResource; @@ -34,8 +34,10 @@ * and register the necessary hints so that they can be instantiated. * * @author Stephane Nicoll - * @see ReflectUtils#setGeneratedClassHandler(BiConsumer) - * @see ReflectUtils#setLoadedClassHandler(Consumer) + * @since 6.0 + * @see ReflectUtils#setGeneratedClassHandler + * @see ReflectUtils#setLoadedClassHandler + * @see ClassHintUtils#registerProxyIfNecessary */ class CglibClassHandler { @@ -46,11 +48,13 @@ class CglibClassHandler { private final GeneratedFiles generatedFiles; + CglibClassHandler(GenerationContext generationContext) { this.runtimeHints = generationContext.getRuntimeHints(); this.generatedFiles = generationContext.getGeneratedFiles(); } + /** * Handle the specified generated CGLIB class. * @param cglibClassName the name of the generated class diff --git a/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java b/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java index 4d80cf146f84..ffb4c40d6fc0 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessor.java @@ -16,12 +16,8 @@ package org.springframework.context.aot; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -36,12 +32,8 @@ import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.ImportRuntimeHints; -import org.springframework.core.annotation.MergedAnnotation; -import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.log.LogMessage; import org.springframework.lang.Nullable; @@ -54,6 +46,8 @@ * * @author Brian Clozel * @author Sebastien Deleuze + * @author Juergen Hoeller + * @since 6.0 */ class RuntimeHintsBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { @@ -67,67 +61,26 @@ public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableL .collect(LinkedHashMap::new, (map, item) -> map.put(item.getClass(), item), Map::putAll); extractFromBeanFactory(beanFactory).forEach(registrarClass -> registrars.computeIfAbsent(registrarClass, BeanUtils::instantiateClass)); - return new RuntimeHintsRegistrarContribution(registrars.values(), - beanFactory.getBeanClassLoader()); + return new RuntimeHintsRegistrarContribution(registrars.values(), beanFactory.getBeanClassLoader()); } private Set> extractFromBeanFactory(ConfigurableListableBeanFactory beanFactory) { Set> registrarClasses = new LinkedHashSet<>(); - for (String beanName : beanFactory - .getBeanNamesForAnnotation(ImportRuntimeHints.class)) { - findAnnotationsOnBean(beanFactory, beanName, - ImportRuntimeHints.class).forEach(annotation -> - registrarClasses.addAll(extractFromBeanDefinition(beanName, annotation))); + for (String beanName : beanFactory.getBeanDefinitionNames()) { + beanFactory.findAllAnnotationsOnBean(beanName, ImportRuntimeHints.class, true) + .forEach(annotation -> registrarClasses.addAll(extractFromBeanDefinition(beanName, annotation))); } return registrarClasses; } - private List findAnnotationsOnBean(ConfigurableListableBeanFactory beanFactory, - String beanName, Class annotationType) { - - List annotations = new ArrayList<>(); - Class beanType = beanFactory.getType(beanName, true); - if (beanType != null) { - MergedAnnotations.from(beanType, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) - .stream(annotationType) - .filter(MergedAnnotation::isPresent) - .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); - } - if (beanFactory.containsBeanDefinition(beanName)) { - BeanDefinition bd = beanFactory.getBeanDefinition(beanName); - if (bd instanceof RootBeanDefinition rbd) { - // Check raw bean class, e.g. in case of a proxy. - if (rbd.hasBeanClass() && rbd.getFactoryMethodName() == null) { - Class beanClass = rbd.getBeanClass(); - if (beanClass != beanType) { - MergedAnnotations.from(beanClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) - .stream(annotationType) - .filter(MergedAnnotation::isPresent) - .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); - } - } - // Check annotations declared on factory method, if any. - Method factoryMethod = rbd.getResolvedFactoryMethod(); - if (factoryMethod != null) { - MergedAnnotations.from(factoryMethod, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) - .stream(annotationType) - .filter(MergedAnnotation::isPresent) - .forEach(mergedAnnotation -> annotations.add(mergedAnnotation.synthesize())); - } - } - } - return annotations; - } - private Set> extractFromBeanDefinition(String beanName, ImportRuntimeHints annotation) { Set> registrars = new LinkedHashSet<>(); for (Class registrarClass : annotation.value()) { if (logger.isTraceEnabled()) { - logger.trace( - LogMessage.format("Loaded [%s] registrar from annotated bean [%s]", - registrarClass.getCanonicalName(), beanName)); + logger.trace(LogMessage.format("Loaded [%s] registrar from annotated bean [%s]", + registrarClass.getCanonicalName(), beanName)); } registrars.add(registrarClass); } @@ -135,26 +88,24 @@ private Set> extractFromBeanDefinition(St } - static class RuntimeHintsRegistrarContribution - implements BeanFactoryInitializationAotContribution { - + static class RuntimeHintsRegistrarContribution implements BeanFactoryInitializationAotContribution { private final Iterable registrars; @Nullable private final ClassLoader beanClassLoader; - RuntimeHintsRegistrarContribution(Iterable registrars, @Nullable ClassLoader beanClassLoader) { + this.registrars = registrars; this.beanClassLoader = beanClassLoader; } - @Override public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { + RuntimeHints hints = generationContext.getRuntimeHints(); this.registrars.forEach(registrar -> { if (logger.isTraceEnabled()) { @@ -165,7 +116,6 @@ public void applyTo(GenerationContext generationContext, registrar.registerHints(hints, this.beanClassLoader); }); } - } } diff --git a/spring-context/src/main/java/org/springframework/context/expression/BeanExpressionContextAccessor.java b/spring-context/src/main/java/org/springframework/context/expression/BeanExpressionContextAccessor.java index 8672542ba186..451d81b271c3 100644 --- a/spring-context/src/main/java/org/springframework/context/expression/BeanExpressionContextAccessor.java +++ b/spring-context/src/main/java/org/springframework/context/expression/BeanExpressionContextAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -36,7 +36,7 @@ public class BeanExpressionContextAccessor implements PropertyAccessor { @Override public boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException { - return (target instanceof BeanExpressionContext && ((BeanExpressionContext) target).containsObject(name)); + return (target instanceof BeanExpressionContext bec && bec.containsObject(name)); } @Override diff --git a/spring-context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java b/spring-context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java index 58bf711fbadc..b8e90c56a7f1 100644 --- a/spring-context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java +++ b/spring-context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -41,7 +41,7 @@ public Class[] getSpecificTargetClasses() { @Override public boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException { - return (target instanceof BeanFactory && ((BeanFactory) target).containsBean(name)); + return (target instanceof BeanFactory beanFactory && beanFactory.containsBean(name)); } @Override diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index be2a0c34150d..3fa935249cde 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -395,8 +395,8 @@ protected void publishEvent(Object event, @Nullable ResolvableType eventType) { // Decorate event as an ApplicationEvent if necessary ApplicationEvent applicationEvent; - if (event instanceof ApplicationEvent) { - applicationEvent = (ApplicationEvent) event; + if (event instanceof ApplicationEvent applEvent) { + applicationEvent = applEvent; } else { applicationEvent = new PayloadApplicationEvent<>(this, event, eventType); @@ -415,8 +415,8 @@ protected void publishEvent(Object event, @Nullable ResolvableType eventType) { // Publish event via parent context as well... if (this.parent != null) { - if (this.parent instanceof AbstractApplicationContext) { - ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); + if (this.parent instanceof AbstractApplicationContext abstractApplicationContext) { + abstractApplicationContext.publishEvent(event, eventType); } else { this.parent.publishEvent(event); @@ -439,7 +439,7 @@ ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalState @Override public void setApplicationStartup(ApplicationStartup applicationStartup) { - Assert.notNull(applicationStartup, "applicationStartup should not be null"); + Assert.notNull(applicationStartup, "applicationStartup must not be null"); this.applicationStartup = applicationStartup; } @@ -497,8 +497,8 @@ public void setParent(@Nullable ApplicationContext parent) { this.parent = parent; if (parent != null) { Environment parentEnvironment = parent.getEnvironment(); - if (parentEnvironment instanceof ConfigurableEnvironment) { - getEnvironment().merge((ConfigurableEnvironment) parentEnvironment); + if (parentEnvironment instanceof ConfigurableEnvironment configurableEnvironment) { + getEnvironment().merge(configurableEnvironment); } } } @@ -770,12 +770,11 @@ protected void initMessageSource() { if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) { this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class); // Make MessageSource aware of parent MessageSource. - if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource hms) { - if (hms.getParentMessageSource() == null) { - // Only set parent context as parent MessageSource if no parent MessageSource - // registered already. - hms.setParentMessageSource(getInternalParentMessageSource()); - } + if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource hms && + hms.getParentMessageSource() == null) { + // Only set parent context as parent MessageSource if no parent MessageSource + // registered already. + hms.setParentMessageSource(getInternalParentMessageSource()); } if (logger.isTraceEnabled()) { logger.trace("Using MessageSource [" + this.messageSource + "]"); @@ -1318,6 +1317,15 @@ public A findAnnotationOnBean( return getBeanFactory().findAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit); } + @Override + public Set findAllAnnotationsOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + assertBeanFactoryActive(); + return getBeanFactory().findAllAnnotationsOnBean(beanName, annotationType, allowFactoryBeanInit); + } + //--------------------------------------------------------------------- // Implementation of HierarchicalBeanFactory interface @@ -1341,8 +1349,8 @@ public boolean containsLocalBean(String name) { */ @Nullable protected BeanFactory getInternalParentBeanFactory() { - return (getParent() instanceof ConfigurableApplicationContext ? - ((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent()); + return (getParent() instanceof ConfigurableApplicationContext cac ? + cac.getBeanFactory() : getParent()); } @@ -1384,8 +1392,8 @@ private MessageSource getMessageSource() throws IllegalStateException { */ @Nullable protected MessageSource getInternalParentMessageSource() { - return (getParent() instanceof AbstractApplicationContext ? - ((AbstractApplicationContext) getParent()).messageSource : getParent()); + return (getParent() instanceof AbstractApplicationContext abstractApplicationContext ? + abstractApplicationContext.messageSource : getParent()); } diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/AbstractMessageSource.java index df4dbbf96b7d..d58ceadb5132 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractMessageSource.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractMessageSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -255,10 +255,10 @@ protected String getMessageInternal(@Nullable String code, @Nullable Object[] ar protected String getMessageFromParent(String code, @Nullable Object[] args, Locale locale) { MessageSource parent = getParentMessageSource(); if (parent != null) { - if (parent instanceof AbstractMessageSource) { + if (parent instanceof AbstractMessageSource abstractMessageSource) { // Call internal method to avoid getting the default code back // in case of "useCodeAsDefaultMessage" being activated. - return ((AbstractMessageSource) parent).getMessageInternal(code, args, locale); + return abstractMessageSource.getMessageInternal(code, args, locale); } else { // Check parent MessageSource, returning null if not found there. @@ -287,8 +287,8 @@ protected String getDefaultMessage(MessageSourceResolvable resolvable, Locale lo String defaultMessage = resolvable.getDefaultMessage(); String[] codes = resolvable.getCodes(); if (defaultMessage != null) { - if (resolvable instanceof DefaultMessageSourceResolvable && - !((DefaultMessageSourceResolvable) resolvable).shouldRenderDefaultMessage()) { + if (resolvable instanceof DefaultMessageSourceResolvable defaultMessageSourceResolvable && + !defaultMessageSourceResolvable.shouldRenderDefaultMessage()) { // Given default message does not contain any argument placeholders // (and isn't escaped for alwaysUseMessageFormat either) -> return as-is. return defaultMessage; @@ -336,8 +336,8 @@ protected Object[] resolveArguments(@Nullable Object[] args, Locale locale) { } List resolvedArgs = new ArrayList<>(args.length); for (Object arg : args) { - if (arg instanceof MessageSourceResolvable) { - resolvedArgs.add(getMessage((MessageSourceResolvable) arg, locale)); + if (arg instanceof MessageSourceResolvable messageSourceResolvable) { + resolvedArgs.add(getMessage(messageSourceResolvable, locale)); } else { resolvedArgs.add(arg); diff --git a/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java b/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java index 60b1c3b58af1..df003f083fd5 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -17,6 +17,7 @@ package org.springframework.context.support; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.Aware; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.EmbeddedValueResolver; import org.springframework.context.ApplicationContextAware; @@ -47,6 +48,7 @@ * @author Juergen Hoeller * @author Costin Leau * @author Chris Beams + * @author Sam Brannen * @since 10.10.2003 * @see org.springframework.context.EnvironmentAware * @see org.springframework.context.EmbeddedValueResolverAware @@ -87,26 +89,28 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro } private void invokeAwareInterfaces(Object bean) { - if (bean instanceof EnvironmentAware) { - ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); - } - if (bean instanceof EmbeddedValueResolverAware) { - ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); - } - if (bean instanceof ResourceLoaderAware) { - ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); - } - if (bean instanceof ApplicationEventPublisherAware) { - ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); - } - if (bean instanceof MessageSourceAware) { - ((MessageSourceAware) bean).setMessageSource(this.applicationContext); - } - if (bean instanceof ApplicationStartupAware) { - ((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup()); - } - if (bean instanceof ApplicationContextAware) { - ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); + if (bean instanceof Aware) { + if (bean instanceof EnvironmentAware environmentAware) { + environmentAware.setEnvironment(this.applicationContext.getEnvironment()); + } + if (bean instanceof EmbeddedValueResolverAware embeddedValueResolverAware) { + embeddedValueResolverAware.setEmbeddedValueResolver(this.embeddedValueResolver); + } + if (bean instanceof ResourceLoaderAware resourceLoaderAware) { + resourceLoaderAware.setResourceLoader(this.applicationContext); + } + if (bean instanceof ApplicationEventPublisherAware applicationEventPublisherAware) { + applicationEventPublisherAware.setApplicationEventPublisher(this.applicationContext); + } + if (bean instanceof MessageSourceAware messageSourceAware) { + messageSourceAware.setMessageSource(this.applicationContext); + } + if (bean instanceof ApplicationStartupAware applicationStartupAware) { + applicationStartupAware.setApplicationStartup(this.applicationContext.getApplicationStartup()); + } + if (bean instanceof ApplicationContextAware applicationContextAware) { + applicationContextAware.setApplicationContext(this.applicationContext); + } } } diff --git a/spring-context/src/main/java/org/springframework/context/support/ApplicationListenerDetector.java b/spring-context/src/main/java/org/springframework/context/support/ApplicationListenerDetector.java index d519b98db4ff..b2b5cec60415 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ApplicationListenerDetector.java +++ b/spring-context/src/main/java/org/springframework/context/support/ApplicationListenerDetector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -71,12 +71,12 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) { @Override public Object postProcessAfterInitialization(Object bean, String beanName) { - if (bean instanceof ApplicationListener) { + if (bean instanceof ApplicationListener applicationListener) { // potentially not detected as a listener by getBeanNamesForType retrieval Boolean flag = this.singletonNames.get(beanName); if (Boolean.TRUE.equals(flag)) { // singleton bean (top-level or inner): register on the fly - this.applicationContext.addApplicationListener((ApplicationListener) bean); + this.applicationContext.addApplicationListener(applicationListener); } else if (Boolean.FALSE.equals(flag)) { if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) { @@ -94,10 +94,10 @@ else if (Boolean.FALSE.equals(flag)) { @Override public void postProcessBeforeDestruction(Object bean, String beanName) { - if (bean instanceof ApplicationListener) { + if (bean instanceof ApplicationListener applicationListener) { try { ApplicationEventMulticaster multicaster = this.applicationContext.getApplicationEventMulticaster(); - multicaster.removeApplicationListener((ApplicationListener) bean); + multicaster.removeApplicationListener(applicationListener); multicaster.removeApplicationListenerBean(beanName); } catch (IllegalStateException ex) { @@ -114,8 +114,9 @@ public boolean requiresDestruction(Object bean) { @Override public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof ApplicationListenerDetector && - this.applicationContext == ((ApplicationListenerDetector) other).applicationContext)); + return (this == other || + (other instanceof ApplicationListenerDetector applicationListenerDectector && + this.applicationContext == applicationListenerDectector.applicationContext)); } @Override diff --git a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java index a269d5a81b15..5c57b1c86518 100644 --- a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -74,11 +74,11 @@ public void setTimeoutPerShutdownPhase(long timeoutPerShutdownPhase) { @Override public void setBeanFactory(BeanFactory beanFactory) { - if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { + if (!(beanFactory instanceof ConfigurableListableBeanFactory clbf)) { throw new IllegalArgumentException( "DefaultLifecycleProcessor requires a ConfigurableListableBeanFactory: " + beanFactory); } - this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + this.beanFactory = clbf; } private ConfigurableListableBeanFactory getBeanFactory() { @@ -143,7 +143,7 @@ private void startBeans(boolean autoStartupOnly) { Map phases = new TreeMap<>(); lifecycleBeans.forEach((beanName, bean) -> { - if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) { + if (!autoStartupOnly || (bean instanceof SmartLifecycle smartLifecycle && smartLifecycle.isAutoStartup())) { int phase = getPhase(bean); phases.computeIfAbsent( phase, @@ -170,7 +170,7 @@ private void doStart(Map lifecycleBeans, String bea doStart(lifecycleBeans, dependency, autoStartupOnly); } if (!bean.isRunning() && - (!autoStartupOnly || !(bean instanceof SmartLifecycle) || ((SmartLifecycle) bean).isAutoStartup())) { + (!autoStartupOnly || !(bean instanceof SmartLifecycle smartLifecycle) || smartLifecycle.isAutoStartup())) { if (logger.isTraceEnabled()) { logger.trace("Starting bean '" + beanName + "' of type [" + bean.getClass().getName() + "]"); } @@ -225,13 +225,13 @@ private void doStop(Map lifecycleBeans, final Strin } try { if (bean.isRunning()) { - if (bean instanceof SmartLifecycle) { + if (bean instanceof SmartLifecycle smartLifecycle) { if (logger.isTraceEnabled()) { logger.trace("Asking bean '" + beanName + "' of type [" + bean.getClass().getName() + "] to stop"); } countDownBeanNames.add(beanName); - ((SmartLifecycle) bean).stop(() -> { + smartLifecycle.stop(() -> { latch.countDown(); countDownBeanNames.remove(beanName); if (logger.isDebugEnabled()) { @@ -283,8 +283,8 @@ protected Map getLifecycleBeans() { (!isFactoryBean || matchesBeanType(Lifecycle.class, beanNameToCheck, beanFactory))) || matchesBeanType(SmartLifecycle.class, beanNameToCheck, beanFactory)) { Object bean = beanFactory.getBean(beanNameToCheck); - if (bean != this && bean instanceof Lifecycle) { - beans.put(beanNameToRegister, (Lifecycle) bean); + if (bean != this && bean instanceof Lifecycle lifecycle) { + beans.put(beanNameToRegister, lifecycle); } } } @@ -306,7 +306,7 @@ private boolean matchesBeanType(Class targetType, String beanName, BeanFactor * @see SmartLifecycle */ protected int getPhase(Lifecycle bean) { - return (bean instanceof Phased ? ((Phased) bean).getPhase() : 0); + return (bean instanceof Phased phased ? phased.getPhase() : 0); } diff --git a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java index 8f49d459fd98..c172acc02d87 100644 --- a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java @@ -18,15 +18,12 @@ import java.io.IOException; import java.lang.reflect.Constructor; -import java.lang.reflect.Proxy; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; import java.util.function.Supplier; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.TypeHint.Builder; +import org.springframework.aot.hint.support.ClassHintUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanDefinitionStoreException; @@ -49,7 +46,6 @@ import org.springframework.core.metrics.ApplicationStartup; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; /** * Generic ApplicationContext implementation that holds a single internal @@ -72,7 +68,7 @@ * definitions on it. {@link #refresh()} may only be called once. * *

This ApplicationContext implementation is suitable for Ahead of Time - * processing, using {@link #refreshForAotProcessing()} as an alternative to the + * processing, using {@link #refreshForAotProcessing} as an alternative to the * regular {@link #refresh()}. * *

Usage example: @@ -88,16 +84,13 @@ * MyBean myBean = (MyBean) ctx.getBean("myBean"); * ... * - * For the typical case of XML bean definitions, simply use + * For the typical case of XML bean definitions, you may also use * {@link ClassPathXmlApplicationContext} or {@link FileSystemXmlApplicationContext}, * which are easier to set up - but less flexible, since you can just use standard * resource locations for XML bean definitions, rather than mixing arbitrary bean - * definition formats. The equivalent in a web environment is - * {@link org.springframework.web.context.support.XmlWebApplicationContext}. - * - *

For custom application context implementations that are supposed to read - * special bean definition formats in a refreshable manner, consider deriving - * from the {@link AbstractRefreshableApplicationContext} base class. + * definition formats. For a custom application context implementation supposed to + * read a specific bean definition format in a refreshable manner, consider + * deriving from the {@link AbstractRefreshableApplicationContext} base class. * * @author Juergen Hoeller * @author Chris Beams @@ -111,15 +104,6 @@ */ public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { - private static final Consumer asClassBasedProxy = hint -> - hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.DECLARED_FIELDS); - - private static final Consumer asProxiedUserClass = hint -> - hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS); - - private final DefaultListableBeanFactory beanFactory; @Nullable @@ -272,8 +256,8 @@ public Resource getResource(String location) { */ @Override public Resource[] getResources(String locationPattern) throws IOException { - if (this.resourceLoader instanceof ResourcePatternResolver) { - return ((ResourcePatternResolver) this.resourceLoader).getResources(locationPattern); + if (this.resourceLoader instanceof ResourcePatternResolver resourcePatternResolver) { + return resourcePatternResolver.getResources(locationPattern); } return super.getResources(locationPattern); } @@ -442,11 +426,11 @@ private void preDetermineBeanTypes(RuntimeHints runtimeHints) { for (String beanName : this.beanFactory.getBeanDefinitionNames()) { Class beanType = this.beanFactory.getType(beanName); if (beanType != null) { - registerProxyHintIfNecessary(beanType, runtimeHints); + ClassHintUtils.registerProxyIfNecessary(beanType, runtimeHints); for (SmartInstantiationAwareBeanPostProcessor bpp : bpps) { Class newBeanType = bpp.determineBeanType(beanType, beanName); if (newBeanType != beanType) { - registerProxyHintIfNecessary(newBeanType, runtimeHints); + ClassHintUtils.registerProxyIfNecessary(newBeanType, runtimeHints); beanType = newBeanType; } } @@ -454,22 +438,6 @@ private void preDetermineBeanTypes(RuntimeHints runtimeHints) { } } - private void registerProxyHintIfNecessary(Class beanType, RuntimeHints runtimeHints) { - if (Proxy.isProxyClass(beanType)) { - // A JDK proxy class needs an explicit hint - runtimeHints.proxies().registerJdkProxy(beanType.getInterfaces()); - } - else { - // Potentially a CGLIB-generated subclass with reflection hints - Class userClass = ClassUtils.getUserClass(beanType); - if (userClass != beanType) { - runtimeHints.reflection() - .registerType(beanType, asClassBasedProxy) - .registerType(userClass, asProxiedUserClass); - } - } - } - //--------------------------------------------------------------------- // Convenient methods for registering individual beans diff --git a/spring-context/src/main/java/org/springframework/context/support/GenericGroovyApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/GenericGroovyApplicationContext.java index 4f4c07017347..ac1569e319da 100644 --- a/spring-context/src/main/java/org/springframework/context/support/GenericGroovyApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/GenericGroovyApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -242,8 +242,8 @@ public Object invokeMethod(String name, Object args) { @Override public void setProperty(String property, Object newValue) { - if (newValue instanceof BeanDefinition) { - registerBeanDefinition(property, (BeanDefinition) newValue); + if (newValue instanceof BeanDefinition beanDefinition) { + registerBeanDefinition(property, beanDefinition); } else { this.metaClass.setProperty(this, property, newValue); diff --git a/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java b/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java index 4f5f0aabbe39..d5b788487682 100644 --- a/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java +++ b/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java @@ -323,8 +323,8 @@ private static void sortPostProcessors(List postProcessors, ConfigurableLista return; } Comparator comparatorToUse = null; - if (beanFactory instanceof DefaultListableBeanFactory) { - comparatorToUse = ((DefaultListableBeanFactory) beanFactory).getDependencyComparator(); + if (beanFactory instanceof DefaultListableBeanFactory dlbf) { + comparatorToUse = dlbf.getDependencyComparator(); } if (comparatorToUse == null) { comparatorToUse = OrderComparator.INSTANCE; @@ -366,9 +366,9 @@ private static void invokeBeanFactoryPostProcessors( private static void registerBeanPostProcessors( ConfigurableListableBeanFactory beanFactory, List postProcessors) { - if (beanFactory instanceof AbstractBeanFactory) { + if (beanFactory instanceof AbstractBeanFactory abstractBeanFactory) { // Bulk addition is more efficient against our CopyOnWriteArrayList there - ((AbstractBeanFactory) beanFactory).addBeanPostProcessors(postProcessors); + abstractBeanFactory.addBeanPostProcessors(postProcessors); } else { for (BeanPostProcessor postProcessor : postProcessors) { diff --git a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java index 8c809648a063..6731999c0b83 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java +++ b/spring-context/src/main/java/org/springframework/context/support/ReloadableResourceBundleMessageSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -534,8 +534,8 @@ public void clearCache() { */ public void clearCacheIncludingAncestors() { clearCache(); - if (getParentMessageSource() instanceof ReloadableResourceBundleMessageSource) { - ((ReloadableResourceBundleMessageSource) getParentMessageSource()).clearCacheIncludingAncestors(); + if (getParentMessageSource() instanceof ReloadableResourceBundleMessageSource reloadableMsgSrc) { + reloadableMsgSrc.clearCacheIncludingAncestors(); } } diff --git a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java index 5235ca74324c..84bd7f9161c5 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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,9 +19,7 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.Collections; import java.util.Date; -import java.util.EnumMap; import java.util.Locale; import java.util.Map; import java.util.TimeZone; @@ -49,15 +47,10 @@ public class DateFormatter implements Formatter { private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); - private static final Map ISO_PATTERNS; - - static { - Map formats = new EnumMap<>(ISO.class); - formats.put(ISO.DATE, "yyyy-MM-dd"); - formats.put(ISO.TIME, "HH:mm:ss.SSSXXX"); - formats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); - ISO_PATTERNS = Collections.unmodifiableMap(formats); - } + private static final Map ISO_PATTERNS = Map.of( + ISO.DATE, "yyyy-MM-dd", + ISO.TIME, "HH:mm:ss.SSSXXX", + ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); @Nullable diff --git a/spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java b/spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java index 9f502e1f9e42..b4e8ad7f7294 100644 --- a/spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java +++ b/spring-context/src/main/java/org/springframework/format/support/FormattingConversionService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -99,8 +99,9 @@ public void addFormatterForFieldType(Class fieldType, Printer printer, Par @Override public void addFormatterForFieldAnnotation(AnnotationFormatterFactory annotationFormatterFactory) { Class annotationType = getAnnotationType(annotationFormatterFactory); - if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) { - ((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver); + if (this.embeddedValueResolver != null && + annotationFormatterFactory instanceof EmbeddedValueResolverAware embeddedValueResolverAware) { + embeddedValueResolverAware.setEmbeddedValueResolver(this.embeddedValueResolver); } Set> fieldTypes = annotationFormatterFactory.getFieldTypes(); for (Class fieldType : fieldTypes) { diff --git a/spring-context/src/main/java/org/springframework/scheduling/Trigger.java b/spring-context/src/main/java/org/springframework/scheduling/Trigger.java index 27bbd387cdea..5120c2a3205d 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/Trigger.java +++ b/spring-context/src/main/java/org/springframework/scheduling/Trigger.java @@ -34,6 +34,7 @@ public interface Trigger { /** * Determine the next execution time according to the given trigger context. + *

The default implementation delegates to {@link #nextExecution(TriggerContext)}. * @param triggerContext context object encapsulating last execution times * and last completion time * @return the next execution time as defined by the trigger, diff --git a/spring-context/src/main/java/org/springframework/scheduling/TriggerContext.java b/spring-context/src/main/java/org/springframework/scheduling/TriggerContext.java index 87d9a072ec3a..cb8ccc2f3997 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/TriggerContext.java +++ b/spring-context/src/main/java/org/springframework/scheduling/TriggerContext.java @@ -33,9 +33,9 @@ public interface TriggerContext { /** * Return the clock to use for trigger calculation. + *

Defaults to {@link Clock#systemDefaultZone()}. * @since 5.3 * @see TaskScheduler#getClock() - * @see Clock#systemDefaultZone() */ default Clock getClock() { return Clock.systemDefaultZone(); @@ -44,6 +44,7 @@ default Clock getClock() { /** * Return the last scheduled execution time of the task, * or {@code null} if not scheduled before. + *

The default implementation delegates to {@link #lastScheduledExecution()}. * @deprecated as of 6.0, in favor on {@link #lastScheduledExecution()} */ @Nullable @@ -64,10 +65,11 @@ default Date lastScheduledExecutionTime() { /** * Return the last actual execution time of the task, * or {@code null} if not scheduled before. + *

The default implementation delegates to {@link #lastActualExecution()}. * @deprecated as of 6.0, in favor on {@link #lastActualExecution()} */ - @Deprecated(since = "6.0") @Nullable + @Deprecated(since = "6.0") default Date lastActualExecutionTime() { Instant instant = lastActualExecution(); return instant != null ? Date.from(instant) : null; @@ -83,6 +85,7 @@ default Date lastActualExecutionTime() { /** * Return the last completion time of the task, * or {@code null} if not scheduled before. + *

The default implementation delegates to {@link #lastCompletion()}. * @deprecated as of 6.0, in favor on {@link #lastCompletion()} */ @Deprecated(since = "6.0") diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java index 72687ee2d337..35e08bec19e7 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java index 4b3cd9415c6b..f20aba5585d4 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java index e3a5b310e575..fcea2643acda 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ProxyAsyncConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -44,7 +44,7 @@ public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration { @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AsyncAnnotationBeanPostProcessor asyncAdvisor() { - Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected"); + Assert.state(this.enableAsync != null, "@EnableAsync annotation metadata was not injected"); AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor(); bpp.configure(this.executor, this.exceptionHandler); Class customAsyncAnnotation = this.enableAsync.getClass("annotation"); diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java index facf5323d035..5b3426f6cfa6 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java @@ -159,7 +159,7 @@ public ScheduledAnnotationBeanPostProcessor() { * @since 5.1 */ public ScheduledAnnotationBeanPostProcessor(ScheduledTaskRegistrar registrar) { - Assert.notNull(registrar, "ScheduledTaskRegistrar is required"); + Assert.notNull(registrar, "ScheduledTaskRegistrar must not be null"); this.registrar = registrar; } diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java index efce947cb7f1..7d502e4459e9 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -55,7 +55,7 @@ * @see CronTrigger * @deprecated as of 5.3, in favor of {@link CronExpression} */ -@Deprecated +@Deprecated(since = "5.3", forRemoval = true) public class CronSequenceGenerator { private final String expression; @@ -84,7 +84,7 @@ public class CronSequenceGenerator { * @see java.util.TimeZone#getDefault() * @deprecated as of 5.3, in favor of {@link CronExpression#parse(String)} */ - @Deprecated + @Deprecated(since = "5.3", forRemoval = true) public CronSequenceGenerator(String expression) { this(expression, TimeZone.getDefault()); } diff --git a/spring-context/src/main/java/org/springframework/validation/BindingResult.java b/spring-context/src/main/java/org/springframework/validation/BindingResult.java index 9ccbc37f066b..987414db36ce 100644 --- a/spring-context/src/main/java/org/springframework/validation/BindingResult.java +++ b/spring-context/src/main/java/org/springframework/validation/BindingResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,7 +24,7 @@ /** * General interface that represents binding results. Extends the - * {@link Errors interface} for error registration capabilities, + * {@link Errors} interface for error registration capabilities, * allowing for a {@link Validator} to be applied, and adds * binding-specific analysis and model building. * diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java index 33d53fcb787e..827ee3fd893e 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java @@ -49,7 +49,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.MessageSource; -import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.KotlinDetector; +import org.springframework.core.KotlinReflectionParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.io.Resource; import org.springframework.lang.Nullable; @@ -100,7 +101,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter private ConstraintValidatorFactory constraintValidatorFactory; @Nullable - private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + private ParameterNameDiscoverer parameterNameDiscoverer; @Nullable private Resource[] mappingLocations; @@ -117,6 +118,13 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter private ValidatorFactory validatorFactory; + public LocalValidatorFactoryBean() { + if (KotlinDetector.isKotlinReflectPresent()) { + this.parameterNameDiscoverer = new KotlinReflectionParameterNameDiscoverer(); + } + } + + /** * Specify the desired provider class, if any. *

If not specified, JSR-303's default search mechanism will be used. @@ -188,7 +196,10 @@ public void setConstraintValidatorFactory(ConstraintValidatorFactory constraintV /** * Set the ParameterNameDiscoverer to use for resolving method and constructor * parameter names if needed for message interpolation. - *

Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}. + *

Default is Hibernate Validator's own internal use of standard Java reflection, + * with an additional {@link KotlinReflectionParameterNameDiscoverer} if Kotlin + * is present. This may be overridden with a custom subclass or a Spring-controlled + * {@link org.springframework.core.DefaultParameterNameDiscoverer} if necessary. */ public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { this.parameterNameDiscoverer = parameterNameDiscoverer; @@ -378,43 +389,43 @@ protected void postProcessConfiguration(Configuration configuration) { @Override public Validator getValidator() { - Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); + Assert.state(this.validatorFactory != null, "No target ValidatorFactory set"); return this.validatorFactory.getValidator(); } @Override public ValidatorContext usingContext() { - Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); + Assert.state(this.validatorFactory != null, "No target ValidatorFactory set"); return this.validatorFactory.usingContext(); } @Override public MessageInterpolator getMessageInterpolator() { - Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); + Assert.state(this.validatorFactory != null, "No target ValidatorFactory set"); return this.validatorFactory.getMessageInterpolator(); } @Override public TraversableResolver getTraversableResolver() { - Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); + Assert.state(this.validatorFactory != null, "No target ValidatorFactory set"); return this.validatorFactory.getTraversableResolver(); } @Override public ConstraintValidatorFactory getConstraintValidatorFactory() { - Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); + Assert.state(this.validatorFactory != null, "No target ValidatorFactory set"); return this.validatorFactory.getConstraintValidatorFactory(); } @Override public ParameterNameProvider getParameterNameProvider() { - Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); + Assert.state(this.validatorFactory != null, "No target ValidatorFactory set"); return this.validatorFactory.getParameterNameProvider(); } @Override public ClockProvider getClockProvider() { - Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); + Assert.state(this.validatorFactory != null, "No target ValidatorFactory set"); return this.validatorFactory.getClockProvider(); } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java index ef6d19b3afb8..7fae422343e2 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -18,7 +18,6 @@ import java.io.Serializable; import java.util.ArrayList; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -66,13 +65,8 @@ */ public class SpringValidatorAdapter implements SmartValidator, jakarta.validation.Validator { - private static final Set internalAnnotationAttributes = new HashSet<>(4); + private static final Set internalAnnotationAttributes = Set.of("message", "groups", "payload"); - static { - internalAnnotationAttributes.add("message"); - internalAnnotationAttributes.add("groups"); - internalAnnotationAttributes.add("payload"); - } @Nullable private jakarta.validation.Validator targetValidator; diff --git a/spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java b/spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java new file mode 100644 index 000000000000..ed640a7a73da --- /dev/null +++ b/spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2022 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 example.indexed; + +/** + * @author Sam Brannen + */ +@jakarta.annotation.ManagedBean +public class IndexedJakartaManagedBeanComponent { +} diff --git a/spring-context/src/test/java/example/indexed/IndexedJakartaNamedComponent.java b/spring-context/src/test/java/example/indexed/IndexedJakartaNamedComponent.java new file mode 100644 index 000000000000..a2b1ed2042e0 --- /dev/null +++ b/spring-context/src/test/java/example/indexed/IndexedJakartaNamedComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2022 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 example.indexed; + +/** + * @author Sam Brannen + */ +@jakarta.inject.Named("myIndexedJakartaNamedComponent") +public class IndexedJakartaNamedComponent { +} diff --git a/spring-context/src/test/java/example/scannable/DefaultNamedComponent.java b/spring-context/src/test/java/example/scannable/DefaultNamedComponent.java index 1f9f65ff7982..1047c9365874 100644 --- a/spring-context/src/test/java/example/scannable/DefaultNamedComponent.java +++ b/spring-context/src/test/java/example/scannable/DefaultNamedComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2022 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,7 +16,6 @@ package example.scannable; - /** * @author Juergen Hoeller */ diff --git a/spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java b/spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java new file mode 100644 index 000000000000..ffeb6c29e0a6 --- /dev/null +++ b/spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2022 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 example.scannable; + +/** + * @author Sam Brannen + */ +@jakarta.annotation.ManagedBean +public class JakartaManagedBeanComponent { +} diff --git a/spring-context/src/test/java/example/scannable/JakartaNamedComponent.java b/spring-context/src/test/java/example/scannable/JakartaNamedComponent.java new file mode 100644 index 000000000000..64165df69c34 --- /dev/null +++ b/spring-context/src/test/java/example/scannable/JakartaNamedComponent.java @@ -0,0 +1,24 @@ +/* + * Copyright 2002-2022 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 example.scannable; + +/** + * @author Sam Brannen + */ +@jakarta.inject.Named("myJakartaNamedComponent") +public class JakartaNamedComponent { +} diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/AroundAdviceBindingTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/AroundAdviceBindingTests.java index c3404c7662bb..267c82df47a8 100644 --- a/spring-context/src/test/java/org/springframework/aop/aspectj/AroundAdviceBindingTests.java +++ b/spring-context/src/test/java/org/springframework/aop/aspectj/AroundAdviceBindingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -109,7 +109,7 @@ public void oneIntArg(ProceedingJoinPoint pjp, int age) throws Throwable { public int oneObjectArg(ProceedingJoinPoint pjp, Object bean) throws Throwable { this.collaborator.oneObjectArg(bean); - return ((Integer) pjp.proceed()).intValue(); + return (Integer) pjp.proceed(); } public void oneIntAndOneObject(ProceedingJoinPoint pjp, int x , Object o) throws Throwable { @@ -119,7 +119,7 @@ public void oneIntAndOneObject(ProceedingJoinPoint pjp, int x , Object o) throws public int justJoinPoint(ProceedingJoinPoint pjp) throws Throwable { this.collaborator.justJoinPoint(pjp.getSignature().getName()); - return ((Integer) pjp.proceed()).intValue(); + return (Integer) pjp.proceed(); } /** diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/AspectAndAdvicePrecedenceTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/AspectAndAdvicePrecedenceTests.java index c978d8a0c76a..fddb34c3c688 100644 --- a/spring-context/src/test/java/org/springframework/aop/aspectj/AspectAndAdvicePrecedenceTests.java +++ b/spring-context/src/test/java/org/springframework/aop/aspectj/AspectAndAdvicePrecedenceTests.java @@ -189,7 +189,7 @@ public int aroundAdviceOne(ProceedingJoinPoint pjp) { int ret = -1; this.collaborator.aroundAdviceOne(this.name); try { - ret = ((Integer)pjp.proceed()).intValue(); + ret = (Integer) pjp.proceed(); } catch (Throwable t) { throw new RuntimeException(t); @@ -202,7 +202,7 @@ public int aroundAdviceTwo(ProceedingJoinPoint pjp) { int ret = -1; this.collaborator.aroundAdviceTwo(this.name); try { - ret = ((Integer)pjp.proceed()).intValue(); + ret = (Integer) pjp.proceed(); } catch (Throwable t) { throw new RuntimeException(t); diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/DeclarationOrderIndependenceTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/DeclarationOrderIndependenceTests.java index 757434be476f..1083de70fd0d 100644 --- a/spring-context/src/test/java/org/springframework/aop/aspectj/DeclarationOrderIndependenceTests.java +++ b/spring-context/src/test/java/org/springframework/aop/aspectj/DeclarationOrderIndependenceTests.java @@ -44,8 +44,8 @@ class DeclarationOrderIndependenceTests { @BeforeEach void setup() { this.ctx = new ClassPathXmlApplicationContext(getClass().getSimpleName() + ".xml", getClass()); - aspect = (TopsyTurvyAspect) ctx.getBean("topsyTurvyAspect"); - target = (TopsyTurvyTarget) ctx.getBean("topsyTurvyTarget"); + aspect = ctx.getBean(TopsyTurvyAspect.class); + target = ctx.getBean(TopsyTurvyTarget.class); } @AfterEach @@ -55,19 +55,17 @@ void tearDown() { @Test - void testTargetIsSerializable() { - boolean condition = this.target instanceof Serializable; - assertThat(condition).as("target bean is serializable").isTrue(); + void targetIsSerializable() { + assertThat(this.target).isInstanceOf(Serializable.class); } @Test - void testTargetIsBeanNameAware() { - boolean condition = this.target instanceof BeanNameAware; - assertThat(condition).as("target bean is bean name aware").isTrue(); + void targetIsBeanNameAware() { + assertThat(this.target).isInstanceOf(BeanNameAware.class); } @Test - void testBeforeAdviceFiringOk() { + void beforeAdviceFiringOk() { AspectCollaborator collab = new AspectCollaborator(); this.aspect.setCollaborator(collab); this.target.doSomething(); @@ -75,7 +73,7 @@ void testBeforeAdviceFiringOk() { } @Test - void testAroundAdviceFiringOk() { + void aroundAdviceFiringOk() { AspectCollaborator collab = new AspectCollaborator(); this.aspect.setCollaborator(collab); this.target.getX(); @@ -83,7 +81,7 @@ void testAroundAdviceFiringOk() { } @Test - void testAfterReturningFiringOk() { + void afterReturningFiringOk() { AspectCollaborator collab = new AspectCollaborator(); this.aspect.setCollaborator(collab); this.target.getX(); diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/ProceedTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/ProceedTests.java index 1a0e86bfa167..e2feef5047b5 100644 --- a/spring-context/src/test/java/org/springframework/aop/aspectj/ProceedTests.java +++ b/spring-context/src/test/java/org/springframework/aop/aspectj/ProceedTests.java @@ -167,14 +167,14 @@ public Object capitalize(ProceedingJoinPoint pjp, String value) throws Throwable } public Object doubleOrQuits(ProceedingJoinPoint pjp) throws Throwable { - int value = ((Integer) pjp.getArgs()[0]).intValue(); - pjp.getArgs()[0] = Integer.valueOf(value * 2); + int value = (Integer) pjp.getArgs()[0]; + pjp.getArgs()[0] = value * 2; return pjp.proceed(); } public Object addOne(ProceedingJoinPoint pjp, Float value) throws Throwable { - float fv = value.floatValue(); - return pjp.proceed(new Object[] {Float.valueOf(fv + 1.0F)}); + float fv = value; + return pjp.proceed(new Object[] {fv + 1.0F}); } public void captureStringArgument(JoinPoint tjp, String arg) { @@ -198,7 +198,7 @@ public Object captureStringArgumentInAround(ProceedingJoinPoint pjp, String arg) } public void captureFloatArgument(JoinPoint tjp, float arg) { - float tjpArg = ((Float) tjp.getArgs()[0]).floatValue(); + float tjpArg = (Float) tjp.getArgs()[0]; if (Math.abs(tjpArg - arg) > 0.000001) { throw new IllegalStateException( "argument is '" + arg + "', " + diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AtAspectJAnnotationBindingTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AtAspectJAnnotationBindingTests.java index 679a4ceff945..a3758b73b81e 100644 --- a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AtAspectJAnnotationBindingTests.java +++ b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AtAspectJAnnotationBindingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -51,7 +51,7 @@ public void setup() { public void testAnnotationBindingInAroundAdvice() { assertThat(testBean.doThis()).isEqualTo("this value doThis"); assertThat(testBean.doThat()).isEqualTo("that value doThat"); - assertThat(testBean.doArray().length).isEqualTo(2); + assertThat(testBean.doArray()).hasSize(2); } @Test diff --git a/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java b/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java index a979f70177ae..0ab185a12ef5 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java @@ -214,7 +214,7 @@ public void testSerializableTargetAndAdvice() throws Throwable { Advised a1 = (Advised) p; Advised a2 = (Advised) p2; // Check we can manipulate state of p2 - assertThat(a2.getAdvisors().length).isEqualTo(a1.getAdvisors().length); + assertThat(a2.getAdvisors()).hasSize(a1.getAdvisors().length); // This should work as SerializablePerson is equal assertThat(p2).as("Proxies should be equal, even after one was serialized").isEqualTo(p); @@ -748,7 +748,7 @@ public void testCannotAddInterceptorWhenFrozen() throws Throwable { .withMessageContaining("frozen"); // Check it still works: proxy factory state shouldn't have been corrupted assertThat(proxied.getAge()).isEqualTo(target.getAge()); - assertThat(((Advised) proxied).getAdvisors().length).isEqualTo(1); + assertThat(((Advised) proxied).getAdvisors()).hasSize(1); } /** @@ -771,7 +771,7 @@ public void testCannotAddAdvisorWhenFrozenUsingCast() throws Throwable { .withMessageContaining("frozen"); // Check it still works: proxy factory state shouldn't have been corrupted assertThat(proxied.getAge()).isEqualTo(target.getAge()); - assertThat(advised.getAdvisors().length).isEqualTo(1); + assertThat(advised.getAdvisors()).hasSize(1); } @Test @@ -790,13 +790,13 @@ public void testCannotRemoveAdvisorWhenFrozen() throws Throwable { .isThrownBy(() -> advised.removeAdvisor(0)) .withMessageContaining("frozen"); // Didn't get removed - assertThat(advised.getAdvisors().length).isEqualTo(1); + assertThat(advised.getAdvisors()).hasSize(1); pc.setFrozen(false); // Can now remove it advised.removeAdvisor(0); // Check it still works: proxy factory state shouldn't have been corrupted assertThat(proxied.getAge()).isEqualTo(target.getAge()); - assertThat(advised.getAdvisors().length).isEqualTo(0); + assertThat(advised.getAdvisors()).isEmpty(); } @Test @@ -1085,7 +1085,7 @@ public Object invoke(MethodInvocation mi) throws Throwable { it.setName(name2); // NameReverter saved it back assertThat(it.getName()).isEqualTo(name1); - assertThat(saver.names.size()).isEqualTo(2); + assertThat(saver.names).hasSize(2); assertThat(saver.names.get(0)).isEqualTo(name2); assertThat(saver.names.get(1)).isEqualTo(name1); } @@ -1173,7 +1173,7 @@ public void testEquals() { IOther proxyA = (IOther) createProxy(pfa); IOther proxyB = (IOther) createProxy(pfb); - assertThat(pfb.getAdvisors().length).isEqualTo(pfa.getAdvisors().length); + assertThat(pfb.getAdvisors()).hasSize(pfa.getAdvisors().length); assertThat(b).isEqualTo(a); assertThat(i2).isEqualTo(i1); assertThat(proxyB).isEqualTo(proxyA); @@ -1353,7 +1353,7 @@ class SummingAfterAdvice implements AfterReturningAdvice { public int sum; @Override public void afterReturning(@Nullable Object returnValue, Method m, Object[] args, @Nullable Object target) throws Throwable { - sum += ((Integer) returnValue).intValue(); + sum += (Integer) returnValue; } } SummingAfterAdvice aa = new SummingAfterAdvice(); diff --git a/spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java b/spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java index b165abefaee1..051f9bdae57a 100644 --- a/spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java +++ b/spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java @@ -190,7 +190,7 @@ void innerBeans() throws IOException { assertThat(hasInnerBeans.getFriends()).isNotNull(); Object[] friends = hasInnerBeans.getFriends().toArray(); - assertThat(friends.length).isEqualTo(3); + assertThat(friends).hasSize(3); DerivedTestBean inner2 = (DerivedTestBean) friends[0]; assertThat(inner2.getName()).isEqualTo("inner2"); assertThat(inner2.getBeanName().startsWith(DerivedTestBean.class.getName())).isTrue(); @@ -203,7 +203,7 @@ void innerBeans() throws IOException { assertThat(inner5.getBeanName()).isEqualTo("innerBean#2"); assertThat(hasInnerBeans.getSomeMap()).isNotNull(); - assertThat(hasInnerBeans.getSomeMap().size()).isEqualTo(2); + assertThat(hasInnerBeans.getSomeMap()).hasSize(2); TestBean inner3 = (TestBean) hasInnerBeans.getSomeMap().get("someKey"); assertThat(inner3.getName()).isEqualTo("Jenny"); assertThat(inner3.getAge()).isEqualTo(30); @@ -261,7 +261,7 @@ void innerBeansWithoutDestroy() { assertThat(hasInnerBeans.getFriends()).isNotNull(); Object[] friends = hasInnerBeans.getFriends().toArray(); - assertThat(friends.length).isEqualTo(3); + assertThat(friends).hasSize(3); DerivedTestBean inner2 = (DerivedTestBean) friends[0]; assertThat(inner2.getName()).isEqualTo("inner2"); assertThat(inner2.getBeanName().startsWith(DerivedTestBean.class.getName())).isTrue(); @@ -410,7 +410,7 @@ void abstractParentBeans() { // abstract beans should not match Map tbs = parent.getBeansOfType(TestBean.class); - assertThat(tbs.size()).isEqualTo(2); + assertThat(tbs).hasSize(2); assertThat(tbs.containsKey("inheritedTestBeanPrototype")).isTrue(); assertThat(tbs.containsKey("inheritedTestBeanSingleton")).isTrue(); @@ -427,13 +427,13 @@ void dependenciesMaterializeThis() { DefaultListableBeanFactory xbf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(DEP_MATERIALIZE_CONTEXT); - assertThat(xbf.getBeansOfType(DummyBo.class, true, false).size()).isEqualTo(2); - assertThat(xbf.getBeansOfType(DummyBo.class, true, true).size()).isEqualTo(3); - assertThat(xbf.getBeansOfType(DummyBo.class, true, false).size()).isEqualTo(3); - assertThat(xbf.getBeansOfType(DummyBo.class).size()).isEqualTo(3); - assertThat(xbf.getBeansOfType(DummyBoImpl.class, true, true).size()).isEqualTo(2); - assertThat(xbf.getBeansOfType(DummyBoImpl.class, false, true).size()).isEqualTo(1); - assertThat(xbf.getBeansOfType(DummyBoImpl.class).size()).isEqualTo(2); + assertThat(xbf.getBeansOfType(DummyBo.class, true, false)).hasSize(2); + assertThat(xbf.getBeansOfType(DummyBo.class, true, true)).hasSize(3); + assertThat(xbf.getBeansOfType(DummyBo.class, true, false)).hasSize(3); + assertThat(xbf.getBeansOfType(DummyBo.class)).hasSize(3); + assertThat(xbf.getBeansOfType(DummyBoImpl.class, true, true)).hasSize(2); + assertThat(xbf.getBeansOfType(DummyBoImpl.class, false, true)).hasSize(1); + assertThat(xbf.getBeansOfType(DummyBoImpl.class)).hasSize(2); DummyBoImpl bos = (DummyBoImpl) xbf.getBean("boSingleton"); DummyBoImpl bop = (DummyBoImpl) xbf.getBean("boPrototype"); @@ -1389,12 +1389,12 @@ void innerBeanInheritsScopeFromConcreteChildDefinition() { reader.loadBeanDefinitions(OVERRIDES_CONTEXT); TestBean jenny1 = (TestBean) xbf.getBean("jennyChild"); - assertThat(jenny1.getFriends().size()).isEqualTo(1); + assertThat(jenny1.getFriends()).hasSize(1); Object friend1 = jenny1.getFriends().iterator().next(); assertThat(friend1 instanceof TestBean).isTrue(); TestBean jenny2 = (TestBean) xbf.getBean("jennyChild"); - assertThat(jenny2.getFriends().size()).isEqualTo(1); + assertThat(jenny2.getFriends()).hasSize(1); Object friend2 = jenny2.getFriends().iterator().next(); assertThat(friend2 instanceof TestBean).isTrue(); @@ -1510,7 +1510,7 @@ void primitiveConstructorArray() { new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(CONSTRUCTOR_ARG_CONTEXT); ConstructorArrayTestBean bean = (ConstructorArrayTestBean) xbf.getBean("constructorArray"); assertThat(bean.array instanceof int[]).isTrue(); - assertThat(((int[]) bean.array).length).isEqualTo(1); + assertThat(((int[]) bean.array)).hasSize(1); assertThat(((int[]) bean.array)[0]).isEqualTo(1); } @@ -1520,7 +1520,7 @@ void indexedPrimitiveConstructorArray() { new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(CONSTRUCTOR_ARG_CONTEXT); ConstructorArrayTestBean bean = (ConstructorArrayTestBean) xbf.getBean("indexedConstructorArray"); assertThat(bean.array instanceof int[]).isTrue(); - assertThat(((int[]) bean.array).length).isEqualTo(1); + assertThat(((int[]) bean.array)).hasSize(1); assertThat(((int[]) bean.array)[0]).isEqualTo(1); } @@ -1530,7 +1530,7 @@ void stringConstructorArrayNoType() { new XmlBeanDefinitionReader(xbf).loadBeanDefinitions(CONSTRUCTOR_ARG_CONTEXT); ConstructorArrayTestBean bean = (ConstructorArrayTestBean) xbf.getBean("constructorArrayNoType"); assertThat(bean.array instanceof String[]).isTrue(); - assertThat(((String[]) bean.array).length).isEqualTo(0); + assertThat(((String[]) bean.array)).isEmpty(); } @Test @@ -1541,7 +1541,7 @@ void stringConstructorArrayNoTypeNonLenient() { bd.setLenientConstructorResolution(false); ConstructorArrayTestBean bean = (ConstructorArrayTestBean) xbf.getBean("constructorArrayNoType"); assertThat(bean.array instanceof String[]).isTrue(); - assertThat(((String[]) bean.array).length).isEqualTo(0); + assertThat(((String[]) bean.array)).isEmpty(); } @Test @@ -1596,7 +1596,7 @@ static class DoSomethingReplacer implements MethodReplacer { @Override public Object reimplement(Object obj, Method method, Object[] args) throws Throwable { - assertThat(args.length).isEqualTo(1); + assertThat(args).hasSize(1); assertThat(method.getName()).isEqualTo("doSomething"); lastArg = args[0]; return null; diff --git a/spring-context/src/test/java/org/springframework/beans/factory/xml/support/CustomNamespaceHandlerTests.java b/spring-context/src/test/java/org/springframework/beans/factory/xml/support/CustomNamespaceHandlerTests.java index 7b03b3706fdb..a560c423240a 100644 --- a/spring-context/src/test/java/org/springframework/beans/factory/xml/support/CustomNamespaceHandlerTests.java +++ b/spring-context/src/test/java/org/springframework/beans/factory/xml/support/CustomNamespaceHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -151,21 +151,21 @@ public void testDecorationViaAttribute() throws Exception { public void testCustomElementNestedWithinUtilList() throws Exception { List things = (List) this.beanFactory.getBean("list.of.things"); assertThat(things).isNotNull(); - assertThat(things.size()).isEqualTo(2); + assertThat(things).hasSize(2); } @Test // SPR-2728 public void testCustomElementNestedWithinUtilSet() throws Exception { Set things = (Set) this.beanFactory.getBean("set.of.things"); assertThat(things).isNotNull(); - assertThat(things.size()).isEqualTo(2); + assertThat(things).hasSize(2); } @Test // SPR-2728 public void testCustomElementNestedWithinUtilMap() throws Exception { Map things = (Map) this.beanFactory.getBean("map.of.things"); assertThat(things).isNotNull(); - assertThat(things.size()).isEqualTo(2); + assertThat(things).hasSize(2); } diff --git a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheTests.java b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheTests.java index 4e8a9d3fbcaf..ac7d52b59eb0 100644 --- a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheTests.java +++ b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -88,7 +88,7 @@ public void testSerializer() { serializeCache.put(key, content); content.remove(0); List entry = (List) serializeCache.get(key).get(); - assertThat(entry.size()).isEqualTo(3); + assertThat(entry).hasSize(3); assertThat(entry.get(0)).isEqualTo("one"); } diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java index 495c977b9c6e..3be1c03a02cf 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/ExpressionEvaluatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -61,7 +61,7 @@ private Collection getOps(String name) { @Test public void testMultipleCachingSource() { Collection ops = getOps("multipleCaching"); - assertThat(ops.size()).isEqualTo(2); + assertThat(ops).hasSize(2); Iterator it = ops.iterator(); CacheOperation next = it.next(); assertThat(next instanceof CacheableOperation).isTrue(); diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java index d5ba2a49b8a8..bb107dd7bd16 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java @@ -372,8 +372,8 @@ void individualBeanWithFactoryBeanSupplier() { context.registerBean("fb", NonInstantiatedFactoryBean.class, NonInstantiatedFactoryBean::new, bd -> bd.setLazyInit(true)); context.refresh(); - assertThat(context.getType("fb")).isEqualTo(String.class); assertThat(context.getType("&fb")).isEqualTo(NonInstantiatedFactoryBean.class); + assertThat(context.getType("fb")).isEqualTo(String.class); assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(1); assertThat(context.getBeanNamesForType(NonInstantiatedFactoryBean.class)).hasSize(1); } @@ -388,25 +388,77 @@ void individualBeanWithFactoryBeanSupplierAndTargetType() { context.registerBeanDefinition("fb", bd); context.refresh(); - assertThat(context.getType("fb")).isEqualTo(String.class); assertThat(context.getType("&fb")).isEqualTo(FactoryBean.class); + assertThat(context.getType("fb")).isEqualTo(String.class); assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(1); assertThat(context.getBeanNamesForType(NonInstantiatedFactoryBean.class)).isEmpty(); } + @Test + void individualBeanWithFactoryBeanTypeAsTargetType() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + RootBeanDefinition bd1 = new RootBeanDefinition(); + bd1.setBeanClass(GenericHolderFactoryBean.class); + bd1.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class, ResolvableType.forClassWithGenerics(GenericHolder.class, String.class))); + bd1.setLazyInit(true); + context.registerBeanDefinition("fb1", bd1); + RootBeanDefinition bd2 = new RootBeanDefinition(); + bd2.setBeanClass(UntypedFactoryBean.class); + bd2.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class, ResolvableType.forClassWithGenerics(GenericHolder.class, Integer.class))); + bd2.setLazyInit(true); + context.registerBeanDefinition("fb2", bd2); + context.registerBeanDefinition("ip", new RootBeanDefinition(FactoryBeanInjectionPoints.class)); + context.refresh(); + + assertThat(context.getType("&fb1")).isEqualTo(GenericHolderFactoryBean.class); + assertThat(context.getType("fb1")).isEqualTo(GenericHolder.class); + assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(2); + assertThat(context.getBeanNamesForType(GenericHolderFactoryBean.class)).hasSize(1); + assertThat(context.getBean("ip", FactoryBeanInjectionPoints.class).factoryBean).isSameAs(context.getBean("&fb1")); + assertThat(context.getBean("ip", FactoryBeanInjectionPoints.class).factoryResult).isSameAs(context.getBean("fb1")); + } + + @Test + void individualBeanWithUnresolvedFactoryBeanTypeAsTargetType() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + RootBeanDefinition bd1 = new RootBeanDefinition(); + bd1.setBeanClass(GenericHolderFactoryBean.class); + bd1.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class, ResolvableType.forClassWithGenerics(GenericHolder.class, Object.class))); + bd1.setLazyInit(true); + context.registerBeanDefinition("fb1", bd1); + RootBeanDefinition bd2 = new RootBeanDefinition(); + bd2.setBeanClass(UntypedFactoryBean.class); + bd2.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class, ResolvableType.forClassWithGenerics(GenericHolder.class, Integer.class))); + bd2.setLazyInit(true); + context.registerBeanDefinition("fb2", bd2); + context.registerBeanDefinition("ip", new RootBeanDefinition(FactoryResultInjectionPoint.class)); + context.refresh(); + + assertThat(context.getType("&fb1")).isEqualTo(GenericHolderFactoryBean.class); + assertThat(context.getType("fb1")).isEqualTo(GenericHolder.class); + assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(2); + assertThat(context.getBean("ip", FactoryResultInjectionPoint.class).factoryResult).isSameAs(context.getBean("fb1")); + } + @Test void individualBeanWithFactoryBeanObjectTypeAsTargetType() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - RootBeanDefinition bd = new RootBeanDefinition(); - bd.setBeanClass(TypedFactoryBean.class); - bd.setTargetType(String.class); - context.registerBeanDefinition("fb", bd); + RootBeanDefinition bd1 = new RootBeanDefinition(); + bd1.setBeanClass(GenericHolderFactoryBean.class); + bd1.setTargetType(ResolvableType.forClassWithGenerics(GenericHolder.class, String.class)); + context.registerBeanDefinition("fb1", bd1); + RootBeanDefinition bd2 = new RootBeanDefinition(); + bd2.setBeanClass(UntypedFactoryBean.class); + bd2.setTargetType(ResolvableType.forClassWithGenerics(GenericHolder.class, Integer.class)); + context.registerBeanDefinition("fb2", bd2); + context.registerBeanDefinition("ip", new RootBeanDefinition(FactoryResultInjectionPoint.class)); context.refresh(); - assertThat(context.getType("&fb")).isEqualTo(TypedFactoryBean.class); - assertThat(context.getType("fb")).isEqualTo(String.class); - assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(1); - assertThat(context.getBeanNamesForType(TypedFactoryBean.class)).hasSize(1); + assertThat(context.getType("&fb1")).isEqualTo(GenericHolderFactoryBean.class); + assertThat(context.getType("fb1")).isEqualTo(GenericHolder.class); + assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(2); + assertThat(context.getBeanNamesForType(GenericHolderFactoryBean.class)).hasSize(1); + assertThat(context.getBean("ip", FactoryResultInjectionPoint.class).factoryResult).isSameAs(context.getBean("fb1")); } @Test @@ -630,6 +682,38 @@ public boolean isSingleton() { return false; } } + + static class GenericHolder {} + + static class GenericHolderFactoryBean implements FactoryBean> { + + @Override + public GenericHolder getObject() { + return new GenericHolder<>(); + } + + @Override + public Class getObjectType() { + return GenericHolder.class; + } + + @Override + public boolean isSingleton() { + return true; + } + } + + static class FactoryResultInjectionPoint { + + @Autowired + GenericHolder factoryResult; + } + + static class FactoryBeanInjectionPoints extends FactoryResultInjectionPoint { + + @Autowired + FactoryBean> factoryBean; + } } class TestBean { diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java index 99f604181d5e..d75c708ee654 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -465,7 +465,7 @@ public void testBeanAutowiredWithAnnotationConfigEnabled() { assertThat(fooService.foo(123)).isEqualTo("bar"); assertThat(fooService.lookupFoo(123)).isEqualTo("bar"); assertThat(fooService.beanFactory).isSameAs(context.getDefaultListableBeanFactory()); - assertThat(fooService.listableBeanFactory.size()).isEqualTo(2); + assertThat(fooService.listableBeanFactory).hasSize(2); assertThat(fooService.listableBeanFactory.get(0)).isSameAs(context.getDefaultListableBeanFactory()); assertThat(fooService.listableBeanFactory.get(1)).isSameAs(myBf); assertThat(fooService.resourceLoader).isSameAs(context); @@ -473,7 +473,7 @@ public void testBeanAutowiredWithAnnotationConfigEnabled() { assertThat(fooService.eventPublisher).isSameAs(context); assertThat(fooService.messageSource).isSameAs(ms); assertThat(fooService.context).isSameAs(context); - assertThat(fooService.configurableContext.length).isEqualTo(1); + assertThat(fooService.configurableContext).hasSize(1); assertThat(fooService.configurableContext[0]).isSameAs(context); assertThat(fooService.genericContext).isSameAs(context); } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java index 82bff2f0f10c..a5e2d44ee210 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java @@ -18,10 +18,17 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.regex.Pattern; +import java.util.stream.Stream; import example.gh24375.AnnotatedComponent; +import example.indexed.IndexedJakartaManagedBeanComponent; +import example.indexed.IndexedJakartaNamedComponent; import example.profilescan.DevComponent; import example.profilescan.ProfileAnnotatedComponent; import example.profilescan.ProfileMetaAnnotatedComponent; @@ -31,6 +38,8 @@ import example.scannable.FooDao; import example.scannable.FooService; import example.scannable.FooServiceImpl; +import example.scannable.JakartaManagedBeanComponent; +import example.scannable.JakartaNamedComponent; import example.scannable.MessageBean; import example.scannable.NamedComponent; import example.scannable.NamedStubDao; @@ -58,10 +67,13 @@ import static org.assertj.core.api.Assertions.assertThat; /** + * Integration tests for {@link ClassPathScanningCandidateComponentProvider}. + * * @author Mark Fisher * @author Juergen Hoeller * @author Chris Beams * @author Stephane Nicoll + * @author Sam Brannen */ class ClassPathScanningCandidateComponentProviderTests { @@ -79,27 +91,53 @@ void defaultsWithScan() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); provider.setResourceLoader(new DefaultResourceLoader( CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader()))); - testDefault(provider); + testDefault(provider, true, false); } @Test void defaultsWithIndex() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); - testDefault(provider); + testDefault(provider, "example", true, true); } - private void testDefault(ClassPathScanningCandidateComponentProvider provider) { - Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(containsBeanClass(candidates, DefaultNamedComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue(); - assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue(); - assertThat(containsBeanClass(candidates, NamedStubDao.class)).isTrue(); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); - assertThat(candidates.size()).isEqualTo(7); - assertBeanDefinitionType(candidates); + private static final Set> springComponents = Set.of( + DefaultNamedComponent.class, + NamedComponent.class, + FooServiceImpl.class, + StubFooDao.class, + NamedStubDao.class, + ServiceInvocationCounter.class, + BarComponent.class + ); + + private static final Set> scannedJakartaComponents = Set.of( + JakartaNamedComponent.class, + JakartaManagedBeanComponent.class + ); + + private static final Set> indexedJakartaComponents = Set.of( + IndexedJakartaNamedComponent.class, + IndexedJakartaManagedBeanComponent.class + ); + + + private void testDefault(ClassPathScanningCandidateComponentProvider provider, boolean includeScannedJakartaComponents, boolean includeIndexedJakartaComponents) { + testDefault(provider, TEST_BASE_PACKAGE, includeScannedJakartaComponents, includeIndexedJakartaComponents); + } + + private void testDefault(ClassPathScanningCandidateComponentProvider provider, String basePackage, boolean includeScannedJakartaComponents, boolean includeIndexedJakartaComponents) { + Set> expectedTypes = new HashSet<>(springComponents); + if (includeScannedJakartaComponents) { + expectedTypes.addAll(scannedJakartaComponents); + } + if (includeIndexedJakartaComponents) { + expectedTypes.addAll(indexedJakartaComponents); + } + + Set candidates = provider.findCandidateComponents(basePackage); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, expectedTypes); } @Test @@ -119,9 +157,8 @@ void antStylePackageWithIndex() { private void testAntStyle(ClassPathScanningCandidateComponentProvider provider) { Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE + ".**.sub"); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); - assertThat(candidates.size()).isEqualTo(1); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, BarComponent.class); } @Test @@ -130,7 +167,7 @@ void bogusPackageWithScan() { provider.setResourceLoader(new DefaultResourceLoader( CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader()))); Set candidates = provider.findCandidateComponents("bogus"); - assertThat(candidates.size()).isEqualTo(0); + assertThat(candidates).isEmpty(); } @Test @@ -138,7 +175,7 @@ void bogusPackageWithIndex() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); Set candidates = provider.findCandidateComponents("bogus"); - assertThat(candidates.size()).isEqualTo(0); + assertThat(candidates).isEmpty(); } @Test @@ -148,7 +185,7 @@ void customFiltersFollowedByResetUseIndex() { provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); provider.resetFilters(true); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); } @Test @@ -168,7 +205,7 @@ void customAnnotationTypeIncludeFilterWithIndex() { private void testCustomAnnotationTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) { provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); - testDefault(provider); + testDefault(provider, false, false); } @Test @@ -189,12 +226,9 @@ void customAssignableTypeIncludeFilterWithIndex() { private void testCustomAssignableTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) { provider.addIncludeFilter(new AssignableTypeFilter(FooService.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); + assertScannedBeanDefinitions(candidates); // Interfaces/Abstract class are filtered out automatically. - assertThat(containsBeanClass(candidates, AutowiredQualifierFooService.class)).isTrue(); - assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue(); - assertThat(containsBeanClass(candidates, ScopedProxyTestBean.class)).isTrue(); - assertThat(candidates.size()).isEqualTo(3); - assertBeanDefinitionType(candidates); + assertBeanTypes(candidates, AutowiredQualifierFooService.class, FooServiceImpl.class, ScopedProxyTestBean.class); } @Test @@ -217,24 +251,20 @@ private void testCustomSupportedIncludeAndExcludeFilter(ClassPathScanningCandida provider.addExcludeFilter(new AnnotationTypeFilter(Service.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Repository.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); - assertThat(candidates.size()).isEqualTo(3); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class); } @Test void customSupportIncludeFilterWithNonIndexedTypeUseScan() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); - // This annotation type is not directly annotated with Indexed so we can use - // the index to find candidates + // This annotation type is not directly annotated with @Indexed so we can use + // the index to find candidates. provider.addIncludeFilter(new AnnotationTypeFilter(CustomStereotype.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(containsBeanClass(candidates, DefaultNamedComponent.class)).isTrue(); - assertThat(candidates.size()).isEqualTo(1); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, DefaultNamedComponent.class); } @Test @@ -243,9 +273,8 @@ void customNotSupportedIncludeFilterUseScan() { provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); provider.addIncludeFilter(new AssignableTypeFilter(FooDao.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue(); - assertThat(candidates.size()).isEqualTo(1); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, StubFooDao.class); } @Test @@ -267,19 +296,16 @@ void excludeFilterWithIndex() { private void testExclude(ClassPathScanningCandidateComponentProvider provider) { Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue(); - assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue(); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); - assertThat(candidates.size()).isEqualTo(4); - assertBeanDefinitionType(candidates); + assertScannedBeanDefinitions(candidates); + assertBeanTypes(candidates, FooServiceImpl.class, StubFooDao.class, ServiceInvocationCounter.class, + BarComponent.class, JakartaManagedBeanComponent.class); } @Test void withNoFilters() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates.size()).isEqualTo(0); + assertThat(candidates).isEmpty(); } @Test @@ -290,13 +316,7 @@ void withComponentAnnotationOnly() { provider.addExcludeFilter(new AnnotationTypeFilter(Service.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Controller.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates.size()).isEqualTo(3); - assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isFalse(); - assertThat(containsBeanClass(candidates, StubFooDao.class)).isFalse(); - assertThat(containsBeanClass(candidates, NamedStubDao.class)).isFalse(); + assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class); } @Test @@ -304,8 +324,7 @@ void withAspectAnnotationOnly() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.addIncludeFilter(new AnnotationTypeFilter(Aspect.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates.size()).isEqualTo(1); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); + assertBeanTypes(candidates, ServiceInvocationCounter.class); } @Test @@ -313,8 +332,7 @@ void withInterfaceType() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.addIncludeFilter(new AssignableTypeFilter(FooDao.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates.size()).isEqualTo(1); - assertThat(containsBeanClass(candidates, StubFooDao.class)).isTrue(); + assertBeanTypes(candidates, StubFooDao.class); } @Test @@ -322,8 +340,7 @@ void withClassType() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.addIncludeFilter(new AssignableTypeFilter(MessageBean.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates.size()).isEqualTo(1); - assertThat(containsBeanClass(candidates, MessageBean.class)).isTrue(); + assertBeanTypes(candidates, MessageBean.class); } @Test @@ -332,11 +349,8 @@ void withMultipleMatchingFilters() { provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates.size()).isEqualTo(7); - assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); - assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isTrue(); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); + assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, FooServiceImpl.class, + BarComponent.class, DefaultNamedComponent.class, NamedStubDao.class, StubFooDao.class); } @Test @@ -346,18 +360,15 @@ void excludeTakesPrecedence() { provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class)); provider.addExcludeFilter(new AssignableTypeFilter(FooService.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertThat(candidates.size()).isEqualTo(6); - assertThat(containsBeanClass(candidates, NamedComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, ServiceInvocationCounter.class)).isTrue(); - assertThat(containsBeanClass(candidates, BarComponent.class)).isTrue(); - assertThat(containsBeanClass(candidates, FooServiceImpl.class)).isFalse(); + assertBeanTypes(candidates, NamedComponent.class, ServiceInvocationCounter.class, BarComponent.class, + DefaultNamedComponent.class, NamedStubDao.class, StubFooDao.class); } @Test void withNullEnvironment() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); Set candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE); - assertThat(containsBeanClass(candidates, ProfileAnnotatedComponent.class)).isFalse(); + assertThat(candidates).isEmpty(); } @Test @@ -367,7 +378,7 @@ void withInactiveProfile() { env.setActiveProfiles("other"); provider.setEnvironment(env); Set candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE); - assertThat(containsBeanClass(candidates, ProfileAnnotatedComponent.class)).isFalse(); + assertThat(candidates).isEmpty(); } @Test @@ -377,7 +388,7 @@ void withActiveProfile() { env.setActiveProfiles(ProfileAnnotatedComponent.PROFILE_NAME); provider.setEnvironment(env); Set candidates = provider.findCandidateComponents(TEST_PROFILE_PACKAGE); - assertThat(containsBeanClass(candidates, ProfileAnnotatedComponent.class)).isTrue(); + assertBeanTypes(candidates, ProfileAnnotatedComponent.class); } @Test @@ -515,19 +526,22 @@ void componentScanningFindsComponentsAnnotatedWithAnnotationsContainingNestedAnn } - private boolean containsBeanClass(Set candidates, Class beanClass) { - for (BeanDefinition candidate : candidates) { - if (beanClass.getName().equals(candidate.getBeanClassName())) { - return true; - } - } - return false; + private static void assertBeanTypes(Set candidates, Class... expectedTypes) { + assertBeanTypes(candidates, Arrays.stream(expectedTypes)); } - private void assertBeanDefinitionType(Set candidates) { - candidates.forEach(c -> - assertThat(c).isInstanceOf(ScannedGenericBeanDefinition.class) - ); + private static void assertBeanTypes(Set candidates, Collection> expectedTypes) { + assertBeanTypes(candidates, expectedTypes.stream()); + } + + private static void assertBeanTypes(Set candidates, Stream> expectedTypes) { + List actualTypeNames = candidates.stream().map(BeanDefinition::getBeanClassName).distinct().sorted().toList(); + List expectedTypeNames = expectedTypes.map(Class::getName).distinct().sorted().toList(); + assertThat(actualTypeNames).containsExactlyElementsOf(expectedTypeNames); + } + + private static void assertScannedBeanDefinitions(Set candidates) { + candidates.forEach(type -> assertThat(type).isInstanceOf(ScannedGenericBeanDefinition.class)); } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java index 188696412329..d68fa893e253 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java @@ -232,7 +232,7 @@ public void testResourceInjectionWithResolvableDependencyType() { assertThat(tb).isNotSameAs(anotherBean.getTestBean6()); String[] depBeans = bf.getDependenciesForBean("annotatedBean"); - assertThat(depBeans.length).isEqualTo(1); + assertThat(depBeans).hasSize(1); assertThat(depBeans[0]).isEqualTo("testBean4"); } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassAndBeanMethodTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassAndBeanMethodTests.java index bb370049dbdb..468b090474fd 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassAndBeanMethodTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassAndBeanMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index 3ab1e7d015ae..c21783c9c817 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -647,11 +647,11 @@ void genericsBasedInjectionWithEarlyGenericsMatching() { assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); } @@ -665,11 +665,11 @@ void genericsBasedInjectionWithLateGenericsMatching() { assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); } @@ -682,10 +682,10 @@ void genericsBasedInjectionWithEarlyGenericsMatchingAndRawFactoryMethod() { assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(0); + assertThat(beanNames).isEmpty(); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(0); + assertThat(beanNames).isEmpty(); } @Test @@ -698,11 +698,11 @@ void genericsBasedInjectionWithLateGenericsMatchingAndRawFactoryMethod() { assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); } @@ -715,11 +715,11 @@ void genericsBasedInjectionWithEarlyGenericsMatchingAndRawInstance() { assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); } @@ -733,11 +733,11 @@ void genericsBasedInjectionWithLateGenericsMatchingAndRawInstance() { assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); } @@ -755,11 +755,11 @@ void genericsBasedInjectionWithEarlyGenericsMatchingOnCglibProxy() { assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); assertThat(AopUtils.isCglibProxy(beanFactory.getBean("stringRepo"))).isTrue(); @@ -780,11 +780,11 @@ void genericsBasedInjectionWithLateGenericsMatchingOnCglibProxy() { assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); assertThat(AopUtils.isCglibProxy(beanFactory.getBean("stringRepo"))).isTrue(); @@ -805,11 +805,11 @@ void genericsBasedInjectionWithLateGenericsMatchingOnCglibProxyAndRawFactoryMeth assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); assertThat(AopUtils.isCglibProxy(beanFactory.getBean("stringRepo"))).isTrue(); @@ -830,11 +830,11 @@ void genericsBasedInjectionWithLateGenericsMatchingOnCglibProxyAndRawInstance() assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); assertThat(AopUtils.isCglibProxy(beanFactory.getBean("stringRepo"))).isTrue(); @@ -853,11 +853,11 @@ void genericsBasedInjectionWithEarlyGenericsMatchingOnJdkProxy() { assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); assertThat(AopUtils.isJdkDynamicProxy(beanFactory.getBean("stringRepo"))).isTrue(); @@ -877,11 +877,11 @@ void genericsBasedInjectionWithLateGenericsMatchingOnJdkProxy() { assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); assertThat(AopUtils.isJdkDynamicProxy(beanFactory.getBean("stringRepo"))).isTrue(); @@ -901,11 +901,11 @@ void genericsBasedInjectionWithLateGenericsMatchingOnJdkProxyAndRawFactoryMethod assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); assertThat(AopUtils.isJdkDynamicProxy(beanFactory.getBean("stringRepo"))).isTrue(); @@ -925,11 +925,11 @@ void genericsBasedInjectionWithLateGenericsMatchingOnJdkProxyAndRawInstance() { assertThat(beanNames).contains("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(RepositoryInterface.class, String.class)); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("stringRepo"); assertThat(AopUtils.isJdkDynamicProxy(beanFactory.getBean("stringRepo"))).isTrue(); @@ -1030,7 +1030,7 @@ void testVarargOnBeanMethod() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class, TestBean.class); VarargConfiguration bean = ctx.getBean(VarargConfiguration.class); assertThat(bean.testBeans).isNotNull(); - assertThat(bean.testBeans.length).isEqualTo(1); + assertThat(bean.testBeans).hasSize(1); assertThat(bean.testBeans[0]).isSameAs(ctx.getBean(TestBean.class)); ctx.close(); } @@ -1040,7 +1040,7 @@ void testEmptyVarargOnBeanMethod() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class); VarargConfiguration bean = ctx.getBean(VarargConfiguration.class); assertThat(bean.testBeans).isNotNull(); - assertThat(bean.testBeans.length).isEqualTo(0); + assertThat(bean.testBeans).isEmpty(); ctx.close(); } @@ -1049,7 +1049,7 @@ void testCollectionArgumentOnBeanMethod() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class, TestBean.class); CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class); assertThat(bean.testBeans).isNotNull(); - assertThat(bean.testBeans.size()).isEqualTo(1); + assertThat(bean.testBeans).hasSize(1); assertThat(bean.testBeans.get(0)).isSameAs(ctx.getBean(TestBean.class)); ctx.close(); } @@ -1068,7 +1068,7 @@ void testMapArgumentOnBeanMethod() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class, DummyRunnable.class); MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class); assertThat(bean.testBeans).isNotNull(); - assertThat(bean.testBeans.size()).isEqualTo(1); + assertThat(bean.testBeans).hasSize(1); assertThat(bean.testBeans.values().iterator().next()).isSameAs(ctx.getBean(Runnable.class)); ctx.close(); } @@ -1087,7 +1087,7 @@ void testCollectionInjectionFromSameConfigurationClass() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionInjectionConfiguration.class); CollectionInjectionConfiguration bean = ctx.getBean(CollectionInjectionConfiguration.class); assertThat(bean.testBeans).isNotNull(); - assertThat(bean.testBeans.size()).isEqualTo(1); + assertThat(bean.testBeans).hasSize(1); assertThat(bean.testBeans.get(0)).isSameAs(ctx.getBean(TestBean.class)); ctx.close(); } @@ -1097,7 +1097,7 @@ void testMapInjectionFromSameConfigurationClass() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapInjectionConfiguration.class); MapInjectionConfiguration bean = ctx.getBean(MapInjectionConfiguration.class); assertThat(bean.testBeans).isNotNull(); - assertThat(bean.testBeans.size()).isEqualTo(1); + assertThat(bean.testBeans).hasSize(1); assertThat(bean.testBeans.get("testBean")).isSameAs(ctx.getBean(Runnable.class)); ctx.close(); } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java index e5593c5d8731..5ae586619dfe 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -138,14 +138,14 @@ public void importsNotCreated() throws Exception { @Test public void conditionOnOverriddenMethodHonored() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithBeanSkipped.class); - assertThat(context.getBeansOfType(ExampleBean.class).size()).isEqualTo(0); + assertThat(context.getBeansOfType(ExampleBean.class)).isEmpty(); } @Test public void noConditionOnOverriddenMethodHonored() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithBeanReactivated.class); Map beans = context.getBeansOfType(ExampleBean.class); - assertThat(beans.size()).isEqualTo(1); + assertThat(beans).hasSize(1); assertThat(beans.keySet().iterator().next()).isEqualTo("baz"); } @@ -153,7 +153,7 @@ public void noConditionOnOverriddenMethodHonored() { public void configWithAlternativeBeans() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithAlternativeBeans.class); Map beans = context.getBeansOfType(ExampleBean.class); - assertThat(beans.size()).isEqualTo(1); + assertThat(beans).hasSize(1); assertThat(beans.keySet().iterator().next()).isEqualTo("baz"); } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/Gh29105Tests.java b/spring-context/src/test/java/org/springframework/context/annotation/Gh29105Tests.java index f93d7a3e35e2..d8faf0f1587f 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/Gh29105Tests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/Gh29105Tests.java @@ -16,8 +16,7 @@ package org.springframework.context.annotation; -import java.util.List; -import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; @@ -30,25 +29,23 @@ * * @author Stephane Nicoll */ -public class Gh29105Tests { +class Gh29105Tests { @Test void beanProviderWithParentContextReuseOrder() { - AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); - parent.register(DefaultConfiguration.class); - parent.register(CustomConfiguration.class); - parent.refresh(); + AnnotationConfigApplicationContext parent = + new AnnotationConfigApplicationContext(DefaultConfiguration.class, CustomConfiguration.class); AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext(); child.setParent(parent); child.register(DefaultConfiguration.class); child.refresh(); - List> orderedTypes = child.getBeanProvider(MyService.class) - .orderedStream().map(Object::getClass).collect(Collectors.toList()); + Stream> orderedTypes = child.getBeanProvider(MyService.class).orderedStream().map(Object::getClass); assertThat(orderedTypes).containsExactly(CustomService.class, DefaultService.class); - parent.close(); + child.close(); + parent.close(); } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java index 1b9214753b66..e4f5168c8237 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -126,8 +126,8 @@ public void importSelectorsWithGroup() { ordered.verify(beanFactory).registerBeanDefinition(eq("c"), any()); ordered.verify(beanFactory).registerBeanDefinition(eq("d"), any()); assertThat(TestImportGroup.instancesCount.get()).isEqualTo(1); - assertThat(TestImportGroup.imports.size()).isEqualTo(1); - assertThat(TestImportGroup.imports.values().iterator().next().size()).isEqualTo(2); + assertThat(TestImportGroup.imports).hasSize(1); + assertThat(TestImportGroup.imports.values().iterator().next()).hasSize(2); } @Test @@ -141,7 +141,7 @@ public void importSelectorsSeparateWithGroup() { ordered.verify(beanFactory).registerBeanDefinition(eq("c"), any()); ordered.verify(beanFactory).registerBeanDefinition(eq("d"), any()); assertThat(TestImportGroup.instancesCount.get()).isEqualTo(1); - assertThat(TestImportGroup.imports.size()).isEqualTo(2); + assertThat(TestImportGroup.imports).hasSize(2); Iterator iterator = TestImportGroup.imports.keySet().iterator(); assertThat(iterator.next().getClassName()).isEqualTo(GroupedConfig2.class.getName()); assertThat(iterator.next().getClassName()).isEqualTo(GroupedConfig1.class.getName()); @@ -158,7 +158,7 @@ public void importSelectorsWithNestedGroup() { ordered.verify(beanFactory).registerBeanDefinition(eq("e"), any()); ordered.verify(beanFactory).registerBeanDefinition(eq("c"), any()); assertThat(TestImportGroup.instancesCount.get()).isEqualTo(2); - assertThat(TestImportGroup.imports.size()).isEqualTo(2); + assertThat(TestImportGroup.imports).hasSize(2); assertThat(TestImportGroup.allImports()) .containsOnlyKeys(ParentConfiguration1.class.getName(), ChildConfiguration1.class.getName()); assertThat(TestImportGroup.allImports().get(ParentConfiguration1.class.getName())) @@ -177,7 +177,7 @@ public void importSelectorsWithNestedGroupSameDeferredImport() { ordered.verify(beanFactory).registerBeanDefinition(eq("b"), any()); ordered.verify(beanFactory).registerBeanDefinition(eq("d"), any()); assertThat(TestImportGroup.instancesCount.get()).isEqualTo(2); - assertThat(TestImportGroup.allImports().size()).isEqualTo(2); + assertThat(TestImportGroup.allImports()).hasSize(2); assertThat(TestImportGroup.allImports()) .containsOnlyKeys(ParentConfiguration2.class.getName(), ChildConfiguration2.class.getName()); assertThat(TestImportGroup.allImports().get(ParentConfiguration2.class.getName())) diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java index b033fdb80875..9c715f1d25f3 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java @@ -172,15 +172,15 @@ void configWithFactoryBeanReturnType() { assertThat(condition).isTrue(); String[] beanNames = factory.getBeanNamesForType(FactoryBean.class); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("&factoryBean"); beanNames = factory.getBeanNamesForType(BeanClassLoaderAware.class); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("&factoryBean"); beanNames = factory.getBeanNamesForType(ListFactoryBean.class); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("&factoryBean"); beanNames = factory.getBeanNamesForType(List.class); 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 bdf218c04e88..bbd4a1effdab 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 @@ -54,8 +54,10 @@ import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver; import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.testfixture.context.annotation.AutowiredComponent; +import org.springframework.context.testfixture.context.annotation.AutowiredGenericTemplate; import org.springframework.context.testfixture.context.annotation.CglibConfiguration; 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.LazyAutowiredFieldComponent; import org.springframework.context.testfixture.context.annotation.LazyAutowiredMethodComponent; @@ -114,6 +116,18 @@ void processAheadOfTimeWhenHasAutowiring() { }); } + @Test + void processAheadOfTimeWhenHasAutowiringOnUnresolvedGeneric() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.registerBean(GenericTemplateConfiguration.class); + applicationContext.registerBean("autowiredComponent", AutowiredGenericTemplate.class); + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + AutowiredGenericTemplate bean = freshApplicationContext.getBean(AutowiredGenericTemplate.class); + assertThat(bean).hasFieldOrPropertyWithValue("genericTemplate", applicationContext.getBean("genericTemplate")); + }); + } + @Test void processAheadOfTimeWhenHasLazyAutowiringOnField() { testAutowiredComponent(LazyAutowiredFieldComponent.class, (bean, generationContext) -> { diff --git a/spring-context/src/test/java/org/springframework/context/conversionservice/ConversionServiceContextConfigTests.java b/spring-context/src/test/java/org/springframework/context/conversionservice/ConversionServiceContextConfigTests.java index c3b3629e2f91..a594b76a9026 100644 --- a/spring-context/src/test/java/org/springframework/context/conversionservice/ConversionServiceContextConfigTests.java +++ b/spring-context/src/test/java/org/springframework/context/conversionservice/ConversionServiceContextConfigTests.java @@ -31,7 +31,7 @@ class ConversionServiceContextConfigTests { void testConfigOk() { try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/context/conversionservice/conversionService.xml")) { TestClient client = context.getBean("testClient", TestClient.class); - assertThat(client.getBars().size()).isEqualTo(2); + assertThat(client.getBars()).hasSize(2); assertThat(client.getBars().get(0).getValue()).isEqualTo("value1"); assertThat(client.getBars().get(1).getValue()).isEqualTo("value2"); assertThat(client.isBool()).isTrue(); diff --git a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java index 35a4a3f8c23b..f56f419c4ecd 100644 --- a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java @@ -191,7 +191,7 @@ public void orderedListeners() { smc.multicastEvent(new MyEvent(this)); smc.multicastEvent(new MyOtherEvent(this)); - assertThat(listener1.seenEvents.size()).isEqualTo(2); + assertThat(listener1.seenEvents).hasSize(2); } @Test @@ -205,7 +205,7 @@ public void orderedListenersWithAnnotation() { smc.multicastEvent(new MyEvent(this)); smc.multicastEvent(new MyOtherEvent(this)); - assertThat(listener1.seenEvents.size()).isEqualTo(2); + assertThat(listener1.seenEvents).hasSize(2); } @Test @@ -222,7 +222,7 @@ public void proxiedListeners() { smc.multicastEvent(new MyEvent(this)); smc.multicastEvent(new MyOtherEvent(this)); - assertThat(listener1.seenEvents.size()).isEqualTo(2); + assertThat(listener1.seenEvents).hasSize(2); } @Test @@ -241,7 +241,7 @@ public void proxiedListenersMixedWithTargetListeners() { smc.multicastEvent(new MyEvent(this)); smc.multicastEvent(new MyOtherEvent(this)); - assertThat(listener1.seenEvents.size()).isEqualTo(2); + assertThat(listener1.seenEvents).hasSize(2); } @Test @@ -298,7 +298,7 @@ public void listenersInApplicationContext() { assertThat(listener1.seenEvents.contains(event4)).isTrue(); AbstractApplicationEventMulticaster multicaster = context.getBean(AbstractApplicationEventMulticaster.class); - assertThat(multicaster.retrieverCache.size()).isEqualTo(2); + assertThat(multicaster.retrieverCache).hasSize(2); context.close(); } @@ -320,7 +320,7 @@ public void listenersInApplicationContextWithPayloadEvents() { assertThat(listener.seenPayloads.contains("event4")).isTrue(); AbstractApplicationEventMulticaster multicaster = context.getBean(AbstractApplicationEventMulticaster.class); - assertThat(multicaster.retrieverCache.size()).isEqualTo(2); + assertThat(multicaster.retrieverCache).hasSize(2); context.close(); } @@ -384,7 +384,7 @@ public void nonSingletonListenerInApplicationContext() { MyNonSingletonListener.seenEvents.clear(); AbstractApplicationEventMulticaster multicaster = context.getBean(AbstractApplicationEventMulticaster.class); - assertThat(multicaster.retrieverCache.size()).isEqualTo(3); + assertThat(multicaster.retrieverCache).hasSize(3); context.close(); } diff --git a/spring-context/src/test/java/org/springframework/context/event/PayloadApplicationEventTests.java b/spring-context/src/test/java/org/springframework/context/event/PayloadApplicationEventTests.java index a1801fd8daae..2280dddb659e 100644 --- a/spring-context/src/test/java/org/springframework/context/event/PayloadApplicationEventTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/PayloadApplicationEventTests.java @@ -83,7 +83,7 @@ void testEventClassWithInterface() { void testProgrammaticEventListener() { List events = new ArrayList<>(); ApplicationListener> listener = events::add; - ApplicationListener> mismatch = (event -> event.getPayload().intValue()); + ApplicationListener> mismatch = (event -> event.getPayload()); ConfigurableApplicationContext ac = new GenericApplicationContext(); ac.addApplicationListener(listener); diff --git a/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java b/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java index 9228a27b1efb..e4bd7a4726ca 100644 --- a/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java +++ b/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context/src/test/java/org/springframework/context/support/ClassPathXmlApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/support/ClassPathXmlApplicationContextTests.java index d51548400b80..ec0c01d27886 100644 --- a/spring-context/src/test/java/org/springframework/context/support/ClassPathXmlApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/ClassPathXmlApplicationContextTests.java @@ -195,7 +195,7 @@ void multipleConfigLocationsWithClass() { void factoryBeanAndApplicationListener() { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_WILDCARD); ctx.getBeanFactory().registerSingleton("manualFBAAL", new FactoryBeanAndApplicationListener()); - assertThat(ctx.getBeansOfType(ApplicationListener.class).size()).isEqualTo(2); + assertThat(ctx.getBeansOfType(ApplicationListener.class)).hasSize(2); ctx.close(); } @@ -261,11 +261,11 @@ void aliasForParentContext() { assertThat(myMs).isSameAs(someMs); String[] aliases = child.getAliases("someMessageSource"); - assertThat(aliases.length).isEqualTo(2); + assertThat(aliases).hasSize(2); assertThat(aliases[0]).isEqualTo("myMessageSource"); assertThat(aliases[1]).isEqualTo("yourMessageSource"); aliases = child.getAliases("myMessageSource"); - assertThat(aliases.length).isEqualTo(2); + assertThat(aliases).hasSize(2); assertThat(aliases[0]).isEqualTo("someMessageSource"); assertThat(aliases[1]).isEqualTo("yourMessageSource"); @@ -299,29 +299,29 @@ void aliasThatOverridesEarlierBean() { private void assertOneMessageSourceOnly(ClassPathXmlApplicationContext ctx, Object myMessageSource) { String[] beanNamesForType = ctx.getBeanNamesForType(StaticMessageSource.class); - assertThat(beanNamesForType.length).isEqualTo(1); + assertThat(beanNamesForType).hasSize(1); assertThat(beanNamesForType[0]).isEqualTo("myMessageSource"); beanNamesForType = ctx.getBeanNamesForType(StaticMessageSource.class, true, true); - assertThat(beanNamesForType.length).isEqualTo(1); + assertThat(beanNamesForType).hasSize(1); assertThat(beanNamesForType[0]).isEqualTo("myMessageSource"); beanNamesForType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(ctx, StaticMessageSource.class); - assertThat(beanNamesForType.length).isEqualTo(1); + assertThat(beanNamesForType).hasSize(1); assertThat(beanNamesForType[0]).isEqualTo("myMessageSource"); beanNamesForType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(ctx, StaticMessageSource.class, true, true); - assertThat(beanNamesForType.length).isEqualTo(1); + assertThat(beanNamesForType).hasSize(1); assertThat(beanNamesForType[0]).isEqualTo("myMessageSource"); Map beansOfType = ctx.getBeansOfType(StaticMessageSource.class); - assertThat(beansOfType.size()).isEqualTo(1); + assertThat(beansOfType).hasSize(1); assertThat(beansOfType.values().iterator().next()).isSameAs(myMessageSource); beansOfType = ctx.getBeansOfType(StaticMessageSource.class, true, true); - assertThat(beansOfType.size()).isEqualTo(1); + assertThat(beansOfType).hasSize(1); assertThat(beansOfType.values().iterator().next()).isSameAs(myMessageSource); beansOfType = BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx, StaticMessageSource.class); - assertThat(beansOfType.size()).isEqualTo(1); + assertThat(beansOfType).hasSize(1); assertThat(beansOfType.values().iterator().next()).isSameAs(myMessageSource); beansOfType = BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx, StaticMessageSource.class, true, true); - assertThat(beansOfType.size()).isEqualTo(1); + assertThat(beansOfType).hasSize(1); assertThat(beansOfType.values().iterator().next()).isSameAs(myMessageSource); } diff --git a/spring-context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java b/spring-context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java index 63f7908c0c1a..9bcfa4e7a692 100644 --- a/spring-context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java @@ -74,7 +74,7 @@ void singleSmartLifecycleAutoStartup() throws Exception { assertThat(bean.isRunning()).isTrue(); context.stop(); assertThat(bean.isRunning()).isFalse(); - assertThat(startedBeans.size()).isEqualTo(1); + assertThat(startedBeans).hasSize(1); context.close(); } @@ -116,10 +116,10 @@ void singleSmartLifecycleWithoutAutoStartup() throws Exception { assertThat(bean.isRunning()).isFalse(); context.refresh(); assertThat(bean.isRunning()).isFalse(); - assertThat(startedBeans.size()).isEqualTo(0); + assertThat(startedBeans).isEmpty(); context.start(); assertThat(bean.isRunning()).isTrue(); - assertThat(startedBeans.size()).isEqualTo(1); + assertThat(startedBeans).hasSize(1); context.stop(); context.close(); } @@ -143,7 +143,7 @@ void singleSmartLifecycleAutoStartupWithNonAutoStartupDependency() throws Except context.stop(); assertThat(bean.isRunning()).isFalse(); assertThat(dependency.isRunning()).isFalse(); - assertThat(startedBeans.size()).isEqualTo(1); + assertThat(startedBeans).hasSize(1); context.close(); } @@ -173,7 +173,7 @@ void smartLifecycleGroupStartup() throws Exception { assertThat(bean3.isRunning()).isTrue(); assertThat(beanMax.isRunning()).isTrue(); context.stop(); - assertThat(startedBeans.size()).isEqualTo(5); + assertThat(startedBeans).hasSize(5); assertThat(getPhase(startedBeans.get(0))).isEqualTo(Integer.MIN_VALUE); assertThat(getPhase(startedBeans.get(1))).isEqualTo(1); assertThat(getPhase(startedBeans.get(2))).isEqualTo(2); @@ -203,7 +203,7 @@ void contextRefreshThenStartWithMixedBeans() throws Exception { assertThat(smartBean2.isRunning()).isTrue(); assertThat(simpleBean1.isRunning()).isFalse(); assertThat(simpleBean2.isRunning()).isFalse(); - assertThat(startedBeans.size()).isEqualTo(2); + assertThat(startedBeans).hasSize(2); assertThat(getPhase(startedBeans.get(0))).isEqualTo(-3); assertThat(getPhase(startedBeans.get(1))).isEqualTo(5); context.start(); @@ -211,7 +211,7 @@ void contextRefreshThenStartWithMixedBeans() throws Exception { assertThat(smartBean2.isRunning()).isTrue(); assertThat(simpleBean1.isRunning()).isTrue(); assertThat(simpleBean2.isRunning()).isTrue(); - assertThat(startedBeans.size()).isEqualTo(4); + assertThat(startedBeans).hasSize(4); assertThat(getPhase(startedBeans.get(2))).isEqualTo(0); assertThat(getPhase(startedBeans.get(3))).isEqualTo(0); context.close(); @@ -238,7 +238,7 @@ void contextRefreshThenStopAndRestartWithMixedBeans() throws Exception { assertThat(smartBean2.isRunning()).isTrue(); assertThat(simpleBean1.isRunning()).isFalse(); assertThat(simpleBean2.isRunning()).isFalse(); - assertThat(startedBeans.size()).isEqualTo(2); + assertThat(startedBeans).hasSize(2); assertThat(getPhase(startedBeans.get(0))).isEqualTo(-3); assertThat(getPhase(startedBeans.get(1))).isEqualTo(5); context.stop(); @@ -251,7 +251,7 @@ void contextRefreshThenStopAndRestartWithMixedBeans() throws Exception { assertThat(smartBean2.isRunning()).isTrue(); assertThat(simpleBean1.isRunning()).isTrue(); assertThat(simpleBean2.isRunning()).isTrue(); - assertThat(startedBeans.size()).isEqualTo(6); + assertThat(startedBeans).hasSize(6); assertThat(getPhase(startedBeans.get(2))).isEqualTo(-3); assertThat(getPhase(startedBeans.get(3))).isEqualTo(0); assertThat(getPhase(startedBeans.get(4))).isEqualTo(0); @@ -300,7 +300,7 @@ void singleSmartLifecycleShutdown() throws Exception { context.refresh(); assertThat(bean.isRunning()).isTrue(); context.stop(); - assertThat(stoppedBeans.size()).isEqualTo(1); + assertThat(stoppedBeans).hasSize(1); assertThat(bean.isRunning()).isFalse(); assertThat(stoppedBeans.get(0)).isEqualTo(bean); context.close(); @@ -317,7 +317,7 @@ void singleLifecycleShutdown() throws Exception { bean.start(); assertThat(bean.isRunning()).isTrue(); context.stop(); - assertThat(stoppedBeans.size()).isEqualTo(1); + assertThat(stoppedBeans).hasSize(1); assertThat(bean.isRunning()).isFalse(); assertThat(stoppedBeans.get(0)).isEqualTo(bean); context.close(); @@ -361,7 +361,7 @@ void mixedShutdown() throws Exception { assertThat(bean5.isRunning()).isFalse(); assertThat(bean6.isRunning()).isFalse(); assertThat(bean7.isRunning()).isFalse(); - assertThat(stoppedBeans.size()).isEqualTo(7); + assertThat(stoppedBeans).hasSize(7); assertThat(getPhase(stoppedBeans.get(0))).isEqualTo(Integer.MAX_VALUE); assertThat(getPhase(stoppedBeans.get(1))).isEqualTo(500); assertThat(getPhase(stoppedBeans.get(2))).isEqualTo(1); @@ -390,7 +390,7 @@ void dependencyStartedFirstEvenIfItsPhaseIsHigher() throws Exception { assertThat(bean2.isRunning()).isTrue(); assertThat(bean99.isRunning()).isTrue(); assertThat(beanMax.isRunning()).isTrue(); - assertThat(startedBeans.size()).isEqualTo(4); + assertThat(startedBeans).hasSize(4); assertThat(getPhase(startedBeans.get(0))).isEqualTo(Integer.MIN_VALUE); assertThat(getPhase(startedBeans.get(1))).isEqualTo(99); assertThat(startedBeans.get(1)).isEqualTo(bean99); @@ -433,7 +433,7 @@ void dependentShutdownFirstEvenIfItsPhaseIsLower() throws Exception { assertThat(bean7.isRunning()).isFalse(); assertThat(bean99.isRunning()).isFalse(); assertThat(beanMax.isRunning()).isFalse(); - assertThat(stoppedBeans.size()).isEqualTo(6); + assertThat(stoppedBeans).hasSize(6); assertThat(getPhase(stoppedBeans.get(0))).isEqualTo(Integer.MAX_VALUE); assertThat(getPhase(stoppedBeans.get(1))).isEqualTo(2); assertThat(stoppedBeans.get(1)).isEqualTo(bean2); @@ -467,7 +467,7 @@ void dependencyStartedFirstAndIsSmartLifecycle() throws Exception { assertThat(bean99.isRunning()).isTrue(); assertThat(bean7.isRunning()).isTrue(); assertThat(simpleBean.isRunning()).isTrue(); - assertThat(startedBeans.size()).isEqualTo(4); + assertThat(startedBeans).hasSize(4); assertThat(getPhase(startedBeans.get(0))).isEqualTo(-99); assertThat(getPhase(startedBeans.get(1))).isEqualTo(7); assertThat(getPhase(startedBeans.get(2))).isEqualTo(0); @@ -509,7 +509,7 @@ void dependentShutdownFirstAndIsSmartLifecycle() throws Exception { assertThat(bean2.isRunning()).isFalse(); assertThat(bean7.isRunning()).isFalse(); assertThat(simpleBean.isRunning()).isFalse(); - assertThat(stoppedBeans.size()).isEqualTo(6); + assertThat(stoppedBeans).hasSize(6); assertThat(getPhase(stoppedBeans.get(0))).isEqualTo(7); assertThat(getPhase(stoppedBeans.get(1))).isEqualTo(2); assertThat(getPhase(stoppedBeans.get(2))).isEqualTo(1); @@ -534,7 +534,7 @@ void dependencyStartedFirstButNotSmartLifecycle() throws Exception { assertThat(beanMin.isRunning()).isTrue(); assertThat(bean7.isRunning()).isTrue(); assertThat(simpleBean.isRunning()).isTrue(); - assertThat(startedBeans.size()).isEqualTo(3); + assertThat(startedBeans).hasSize(3); assertThat(getPhase(startedBeans.get(0))).isEqualTo(0); assertThat(getPhase(startedBeans.get(1))).isEqualTo(Integer.MIN_VALUE); assertThat(getPhase(startedBeans.get(2))).isEqualTo(7); @@ -572,7 +572,7 @@ void dependentShutdownFirstButNotSmartLifecycle() throws Exception { assertThat(bean2.isRunning()).isFalse(); assertThat(bean7.isRunning()).isFalse(); assertThat(simpleBean.isRunning()).isFalse(); - assertThat(stoppedBeans.size()).isEqualTo(5); + assertThat(stoppedBeans).hasSize(5); assertThat(getPhase(stoppedBeans.get(0))).isEqualTo(7); assertThat(getPhase(stoppedBeans.get(1))).isEqualTo(0); assertThat(getPhase(stoppedBeans.get(2))).isEqualTo(2); diff --git a/spring-context/src/test/java/org/springframework/context/support/ResourceBundleMessageSourceTests.java b/spring-context/src/test/java/org/springframework/context/support/ResourceBundleMessageSourceTests.java index a0c6fc1f18f4..d799515796d1 100644 --- a/spring-context/src/test/java/org/springframework/context/support/ResourceBundleMessageSourceTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/ResourceBundleMessageSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -389,32 +389,32 @@ void reloadableResourceBundleMessageSourceFileNameCalculation() { ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource(); List filenames = ms.calculateFilenamesForLocale("messages", Locale.ENGLISH); - assertThat(filenames.size()).isEqualTo(1); + assertThat(filenames).hasSize(1); assertThat(filenames.get(0)).isEqualTo("messages_en"); filenames = ms.calculateFilenamesForLocale("messages", Locale.UK); - assertThat(filenames.size()).isEqualTo(2); + assertThat(filenames).hasSize(2); assertThat(filenames.get(1)).isEqualTo("messages_en"); assertThat(filenames.get(0)).isEqualTo("messages_en_GB"); filenames = ms.calculateFilenamesForLocale("messages", new Locale("en", "GB", "POSIX")); - assertThat(filenames.size()).isEqualTo(3); + assertThat(filenames).hasSize(3); assertThat(filenames.get(2)).isEqualTo("messages_en"); assertThat(filenames.get(1)).isEqualTo("messages_en_GB"); assertThat(filenames.get(0)).isEqualTo("messages_en_GB_POSIX"); filenames = ms.calculateFilenamesForLocale("messages", new Locale("en", "", "POSIX")); - assertThat(filenames.size()).isEqualTo(2); + assertThat(filenames).hasSize(2); assertThat(filenames.get(1)).isEqualTo("messages_en"); assertThat(filenames.get(0)).isEqualTo("messages_en__POSIX"); filenames = ms.calculateFilenamesForLocale("messages", new Locale("", "UK", "POSIX")); - assertThat(filenames.size()).isEqualTo(2); + assertThat(filenames).hasSize(2); assertThat(filenames.get(1)).isEqualTo("messages__UK"); assertThat(filenames.get(0)).isEqualTo("messages__UK_POSIX"); filenames = ms.calculateFilenamesForLocale("messages", new Locale("", "", "POSIX")); - assertThat(filenames.size()).isEqualTo(0); + assertThat(filenames).isEmpty(); } @Test diff --git a/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java b/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java index b43ec1709e57..6078982e1381 100644 --- a/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/AbstractMBeanServerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java index 4ccfbadfc841..f774744dec8c 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/NotificationListenerTests.java @@ -455,7 +455,7 @@ public void handleNotification(Notification notification, Object handback) { Integer currentCount = (Integer) this.attributeCounts.get(attributeName); if (currentCount != null) { - int count = currentCount.intValue() + 1; + int count = currentCount + 1; this.attributeCounts.put(attributeName, count); } else { @@ -468,7 +468,7 @@ public void handleNotification(Notification notification, Object handback) { public int getCount(String attribute) { Integer count = (Integer) this.attributeCounts.get(attribute); - return (count == null) ? 0 : count.intValue(); + return (count == null) ? 0 : count; } public Object getLastHandback(String attributeName) { diff --git a/spring-context/src/test/java/org/springframework/jmx/support/JmxUtilsTests.java b/spring-context/src/test/java/org/springframework/jmx/support/JmxUtilsTests.java index eba01bd4fe6b..c929150af1c9 100644 --- a/spring-context/src/test/java/org/springframework/jmx/support/JmxUtilsTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/support/JmxUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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,6 +19,7 @@ import java.beans.PropertyDescriptor; import javax.management.DynamicMBean; +import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; @@ -30,6 +31,7 @@ import org.springframework.jmx.IJmxTestBean; import org.springframework.jmx.JmxTestBean; import org.springframework.jmx.export.TestDynamicMBean; +import org.springframework.util.MBeanTestUtils; import org.springframework.util.ObjectUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -39,6 +41,7 @@ * * @author Rob Harrop * @author Juergen Hoeller + * @author Sam Brannen */ class JmxUtilsTests { @@ -124,6 +127,20 @@ void appendIdentityToObjectName() throws MalformedObjectNameException { assertThat(uniqueName.getKeyProperty(JmxUtils.IDENTITY_OBJECT_NAME_KEY)).as("Identity key is incorrect").isEqualTo(ObjectUtils.getIdentityHexString(managedResource)); } + @Test + void locatePlatformMBeanServer() { + MBeanServer server = null; + try { + server = JmxUtils.locateMBeanServer(); + assertThat(server).isNotNull(); + } + finally { + if (server != null) { + MBeanTestUtils.releaseMBeanServer(server); + } + } + } + public static class AttributeTestBean { diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java index a67e98ee503b..b87abbe704c7 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java @@ -63,7 +63,7 @@ public void tearDown() { @EnabledForTestGroups(LONG_RUNNING) public void withFixedRateTask() throws InterruptedException { ctx = new AnnotationConfigApplicationContext(FixedRateTaskConfig.class); - assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks().size()).isEqualTo(2); + assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(2); Thread.sleep(100); assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10); @@ -73,7 +73,7 @@ public void withFixedRateTask() throws InterruptedException { @EnabledForTestGroups(LONG_RUNNING) public void withSubclass() throws InterruptedException { ctx = new AnnotationConfigApplicationContext(FixedRateTaskConfigSubclass.class); - assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks().size()).isEqualTo(2); + assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(2); Thread.sleep(100); assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10); @@ -83,7 +83,7 @@ public void withSubclass() throws InterruptedException { @EnabledForTestGroups(LONG_RUNNING) public void withExplicitScheduler() throws InterruptedException { ctx = new AnnotationConfigApplicationContext(ExplicitSchedulerConfig.class); - assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks().size()).isEqualTo(1); + assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(1); Thread.sleep(100); assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10); @@ -102,7 +102,7 @@ public void withExplicitSchedulerAmbiguity_andSchedulingEnabled() { @EnabledForTestGroups(LONG_RUNNING) public void withExplicitScheduledTaskRegistrar() throws InterruptedException { ctx = new AnnotationConfigApplicationContext(ExplicitScheduledTaskRegistrarConfig.class); - assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks().size()).isEqualTo(1); + assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(1); Thread.sleep(100); assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10); diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java index 81f6ec104907..a1cd409d205f 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java @@ -104,7 +104,7 @@ void fixedDelayTask(@NameToClass Class beanClass, long expectedInterval) { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -137,7 +137,7 @@ void fixedRateTask(@NameToClass Class beanClass, long expectedInterval) { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -145,7 +145,7 @@ void fixedRateTask(@NameToClass Class beanClass, long expectedInterval) { @SuppressWarnings("unchecked") List fixedRateTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); - assertThat(fixedRateTasks.size()).isEqualTo(1); + assertThat(fixedRateTasks).hasSize(1); IntervalTask task = fixedRateTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -172,7 +172,7 @@ void fixedRateTaskWithInitialDelay(@NameToClass Class beanClass, long expecte context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -180,7 +180,7 @@ void fixedRateTaskWithInitialDelay(@NameToClass Class beanClass, long expecte @SuppressWarnings("unchecked") List fixedRateTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); - assertThat(fixedRateTasks.size()).isEqualTo(1); + assertThat(fixedRateTasks).hasSize(1); IntervalTask task = fixedRateTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -237,7 +237,7 @@ private void severalFixedRates(StaticApplicationContext context, context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(2); + assertThat(postProcessor.getScheduledTasks()).hasSize(2); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -245,7 +245,7 @@ private void severalFixedRates(StaticApplicationContext context, @SuppressWarnings("unchecked") List fixedRateTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); - assertThat(fixedRateTasks.size()).isEqualTo(2); + assertThat(fixedRateTasks).hasSize(2); IntervalTask task1 = fixedRateTasks.get(0); ScheduledMethodRunnable runnable1 = (ScheduledMethodRunnable) task1.getRunnable(); Object targetObject = runnable1.getTarget(); @@ -273,7 +273,7 @@ void cronTask() { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -281,7 +281,7 @@ void cronTask() { @SuppressWarnings("unchecked") List cronTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); - assertThat(cronTasks.size()).isEqualTo(1); + assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -300,7 +300,7 @@ void cronTaskWithZone() { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -308,7 +308,7 @@ void cronTaskWithZone() { @SuppressWarnings("unchecked") List cronTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); - assertThat(cronTasks.size()).isEqualTo(1); + assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -367,14 +367,14 @@ void cronTaskWithScopedProxy() { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); @SuppressWarnings("unchecked") List cronTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); - assertThat(cronTasks.size()).isEqualTo(1); + assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -393,7 +393,7 @@ void metaAnnotationWithFixedRate() { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -401,7 +401,7 @@ void metaAnnotationWithFixedRate() { @SuppressWarnings("unchecked") List fixedRateTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); - assertThat(fixedRateTasks.size()).isEqualTo(1); + assertThat(fixedRateTasks).hasSize(1); IntervalTask task = fixedRateTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -420,7 +420,7 @@ void composedAnnotationWithInitialDelayAndFixedRate() { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -428,7 +428,7 @@ void composedAnnotationWithInitialDelayAndFixedRate() { @SuppressWarnings("unchecked") List fixedRateTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); - assertThat(fixedRateTasks.size()).isEqualTo(1); + assertThat(fixedRateTasks).hasSize(1); IntervalTask task = fixedRateTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -448,7 +448,7 @@ void metaAnnotationWithCronExpression() { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -456,7 +456,7 @@ void metaAnnotationWithCronExpression() { @SuppressWarnings("unchecked") List cronTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); - assertThat(cronTasks.size()).isEqualTo(1); + assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -481,7 +481,7 @@ void propertyPlaceholderWithCron() { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -489,7 +489,7 @@ void propertyPlaceholderWithCron() { @SuppressWarnings("unchecked") List cronTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); - assertThat(cronTasks.size()).isEqualTo(1); + assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -540,7 +540,7 @@ void propertyPlaceholderWithFixedDelay(@NameToClass Class beanClass, String f context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -548,7 +548,7 @@ void propertyPlaceholderWithFixedDelay(@NameToClass Class beanClass, String f @SuppressWarnings("unchecked") List fixedDelayTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks"); - assertThat(fixedDelayTasks.size()).isEqualTo(1); + assertThat(fixedDelayTasks).hasSize(1); IntervalTask task = fixedDelayTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -584,7 +584,7 @@ void propertyPlaceholderWithFixedRate(@NameToClass Class beanClass, String fi context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -592,7 +592,7 @@ void propertyPlaceholderWithFixedRate(@NameToClass Class beanClass, String fi @SuppressWarnings("unchecked") List fixedRateTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); - assertThat(fixedRateTasks.size()).isEqualTo(1); + assertThat(fixedRateTasks).hasSize(1); IntervalTask task = fixedRateTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -618,7 +618,7 @@ void expressionWithCron() { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -626,7 +626,7 @@ void expressionWithCron() { @SuppressWarnings("unchecked") List cronTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); - assertThat(cronTasks.size()).isEqualTo(1); + assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -651,7 +651,7 @@ void propertyPlaceholderForMetaAnnotation() { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -659,7 +659,7 @@ void propertyPlaceholderForMetaAnnotation() { @SuppressWarnings("unchecked") List cronTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); - assertThat(cronTasks.size()).isEqualTo(1); + assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); @@ -678,7 +678,7 @@ void nonVoidReturnType() { context.refresh(); ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class); - assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1); + assertThat(postProcessor.getScheduledTasks()).hasSize(1); Object target = context.getBean("target"); ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) @@ -686,7 +686,7 @@ void nonVoidReturnType() { @SuppressWarnings("unchecked") List cronTasks = (List) new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); - assertThat(cronTasks.size()).isEqualTo(1); + assertThat(cronTasks).hasSize(1); CronTask task = cronTasks.get(0); ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); Object targetObject = runnable.getTarget(); diff --git a/spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java b/spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java index fff8b6eeb134..73895c8619d0 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/support/CronSequenceGeneratorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -27,7 +27,7 @@ * @author Juergen Hoeller * @author Ruslan Sibgatullin */ -@SuppressWarnings("deprecation") +@SuppressWarnings({ "removal", "deprecation" }) public class CronSequenceGeneratorTests { @Test diff --git a/spring-context/src/test/java/org/springframework/scripting/config/ScriptingDefaultsTests.java b/spring-context/src/test/java/org/springframework/scripting/config/ScriptingDefaultsTests.java index 07f9892b5ff0..770758e0e75b 100644 --- a/spring-context/src/test/java/org/springframework/scripting/config/ScriptingDefaultsTests.java +++ b/spring-context/src/test/java/org/springframework/scripting/config/ScriptingDefaultsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -50,7 +50,7 @@ public void defaultRefreshCheckDelay() throws Exception { ((AbstractRefreshableTargetSource) advised.getTargetSource()); Field field = AbstractRefreshableTargetSource.class.getDeclaredField("refreshCheckDelay"); field.setAccessible(true); - long delay = ((Long) field.get(targetSource)).longValue(); + long delay = (Long) field.get(targetSource); assertThat(delay).isEqualTo(5000L); } diff --git a/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java b/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java index c53053ff0b2b..fe3dd0baba33 100644 --- a/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java +++ b/spring-context/src/test/java/org/springframework/scripting/groovy/GroovyScriptFactoryTests.java @@ -463,7 +463,7 @@ public void testProxyTargetClassNotAllowedIfNotGroovy() throws Exception { public void testAnonymousScriptDetected() throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("groovy-with-xsd.xml", getClass()); Map beans = ctx.getBeansOfType(Messenger.class); - assertThat(beans.size()).isEqualTo(4); + assertThat(beans).hasSize(4); assertThat(ctx.getBean(MyBytecodeProcessor.class).processed.contains( "org.springframework.scripting.groovy.GroovyMessenger2")).isTrue(); } diff --git a/spring-context/src/test/java/org/springframework/scripting/support/ResourceScriptSourceTests.java b/spring-context/src/test/java/org/springframework/scripting/support/ResourceScriptSourceTests.java index 9795271cb6d9..a06e14364492 100644 --- a/spring-context/src/test/java/org/springframework/scripting/support/ResourceScriptSourceTests.java +++ b/spring-context/src/test/java/org/springframework/scripting/support/ResourceScriptSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java b/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java index 93669ec3b215..26381e7c8334 100644 --- a/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java +++ b/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java @@ -44,7 +44,7 @@ public class ModelMapTests { @Test public void testNoArgCtorYieldsEmptyModel() throws Exception { - assertThat(new ModelMap().size()).isEqualTo(0); + assertThat(new ModelMap()).isEmpty(); } /* @@ -71,7 +71,7 @@ public void testAddNullObjectViaCtorWithExplicitKey() throws Exception { @Test public void testNamedObjectCtor() throws Exception { ModelMap model = new ModelMap("foo", "bing"); - assertThat(model.size()).isEqualTo(1); + assertThat(model).hasSize(1); String bing = (String) model.get("foo"); assertThat(bing).isNotNull(); assertThat(bing).isEqualTo("bing"); @@ -80,7 +80,7 @@ public void testNamedObjectCtor() throws Exception { @Test public void testUnnamedCtorScalar() throws Exception { ModelMap model = new ModelMap("foo", "bing"); - assertThat(model.size()).isEqualTo(1); + assertThat(model).hasSize(1); String bing = (String) model.get("foo"); assertThat(bing).isNotNull(); assertThat(bing).isEqualTo("bing"); @@ -89,7 +89,7 @@ public void testUnnamedCtorScalar() throws Exception { @Test public void testOneArgCtorWithScalar() throws Exception { ModelMap model = new ModelMap("bing"); - assertThat(model.size()).isEqualTo(1); + assertThat(model).hasSize(1); String string = (String) model.get("string"); assertThat(string).isNotNull(); assertThat(string).isEqualTo("bing"); @@ -105,10 +105,10 @@ public void testOneArgCtorWithNull() { @Test public void testOneArgCtorWithCollection() throws Exception { ModelMap model = new ModelMap(new String[]{"foo", "boing"}); - assertThat(model.size()).isEqualTo(1); + assertThat(model).hasSize(1); String[] strings = (String[]) model.get("stringList"); assertThat(strings).isNotNull(); - assertThat(strings.length).isEqualTo(2); + assertThat(strings).hasSize(2); assertThat(strings[0]).isEqualTo("foo"); assertThat(strings[1]).isEqualTo("boing"); } @@ -117,7 +117,7 @@ public void testOneArgCtorWithCollection() throws Exception { public void testOneArgCtorWithEmptyCollection() throws Exception { ModelMap model = new ModelMap(new HashSet<>()); // must not add if collection is empty... - assertThat(model.size()).isEqualTo(0); + assertThat(model).isEmpty(); } @Test @@ -131,24 +131,24 @@ public void testAddObjectWithNull() throws Exception { @Test public void testAddObjectWithEmptyArray() throws Exception { ModelMap model = new ModelMap(new int[]{}); - assertThat(model.size()).isEqualTo(1); + assertThat(model).hasSize(1); int[] ints = (int[]) model.get("intList"); assertThat(ints).isNotNull(); - assertThat(ints.length).isEqualTo(0); + assertThat(ints).isEmpty(); } @Test public void testAddAllObjectsWithNullMap() throws Exception { ModelMap model = new ModelMap(); model.addAllAttributes((Map) null); - assertThat(model.size()).isEqualTo(0); + assertThat(model).isEmpty(); } @Test public void testAddAllObjectsWithNullCollection() throws Exception { ModelMap model = new ModelMap(); model.addAllAttributes((Collection) null); - assertThat(model.size()).isEqualTo(0); + assertThat(model).isEmpty(); } @Test @@ -169,7 +169,7 @@ public void testAddMap() throws Exception { map.put("two", "two-value"); ModelMap model = new ModelMap(); model.addAttribute(map); - assertThat(model.size()).isEqualTo(1); + assertThat(model).hasSize(1); String key = StringUtils.uncapitalize(ClassUtils.getShortName(map.getClass())); assertThat(model.containsKey(key)).isTrue(); } @@ -179,7 +179,7 @@ public void testAddObjectNoKeyOfSameTypeOverrides() throws Exception { ModelMap model = new ModelMap(); model.addAttribute("foo"); model.addAttribute("bar"); - assertThat(model.size()).isEqualTo(1); + assertThat(model).hasSize(1); String bar = (String) model.get("string"); assertThat(bar).isEqualTo("bar"); } @@ -192,7 +192,7 @@ public void testAddListOfTheSameObjects() throws Exception { beans.add(new TestBean("three")); ModelMap model = new ModelMap(); model.addAllAttributes(beans); - assertThat(model.size()).isEqualTo(1); + assertThat(model).hasSize(1); } @Test @@ -204,7 +204,7 @@ public void testMergeMapWithOverriding() throws Exception { ModelMap model = new ModelMap(); model.put("one", new TestBean("oneOld")); model.mergeAttributes(beans); - assertThat(model.size()).isEqualTo(3); + assertThat(model).hasSize(3); assertThat(((TestBean) model.get("one")).getName()).isEqualTo("oneOld"); } diff --git a/spring-context/src/test/java/org/springframework/util/MBeanTestUtils.java b/spring-context/src/test/java/org/springframework/util/MBeanTestUtils.java index 6b214a7f3360..900184a75fa0 100644 --- a/spring-context/src/test/java/org/springframework/util/MBeanTestUtils.java +++ b/spring-context/src/test/java/org/springframework/util/MBeanTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -23,23 +23,34 @@ * Utilities for MBean tests. * * @author Phillip Webb + * @author Sam Brannen */ public class MBeanTestUtils { /** - * Resets MBeanServerFactory and ManagementFactory to a known consistent state. - *

This involves releasing all currently registered MBeanServers and resetting - * the platformMBeanServer to null. + * Reset the {@link MBeanServerFactory} to a known consistent state. This involves + * {@linkplain #releaseMBeanServer(MBeanServer) releasing} all currently registered + * MBeanServers. */ public static synchronized void resetMBeanServers() throws Exception { for (MBeanServer server : MBeanServerFactory.findMBeanServer(null)) { - try { - MBeanServerFactory.releaseMBeanServer(server); - } - catch (IllegalArgumentException ex) { - if (!ex.getMessage().contains("not in list")) { - throw ex; - } + releaseMBeanServer(server); + } + } + + /** + * Attempt to release the supplied {@link MBeanServer}. + *

Ignores any {@link IllegalArgumentException} thrown by + * {@link MBeanServerFactory#releaseMBeanServer(MBeanServer)} whose error + * message contains the text "not in list". + */ + public static void releaseMBeanServer(MBeanServer server) { + try { + MBeanServerFactory.releaseMBeanServer(server); + } + catch (IllegalArgumentException ex) { + if (!ex.getMessage().contains("not in list")) { + throw ex; } } } diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java index 546c599c01f7..05dd886da828 100644 --- a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java +++ b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java @@ -1724,7 +1724,7 @@ public void setAsText(String text) throws IllegalArgumentException { pvs.add("stringArray", new String[] {"a1", "b2"}); binder.bind(pvs); assertThat(binder.getBindingResult().hasErrors()).isFalse(); - assertThat(tb.getStringArray().length).isEqualTo(2); + assertThat(tb.getStringArray()).hasSize(2); assertThat(tb.getStringArray()[0]).isEqualTo("Xa1"); assertThat(tb.getStringArray()[1]).isEqualTo("Xb2"); } @@ -1926,7 +1926,7 @@ void nestedGrowingList() { List list = (List) form.getF().get("list"); assertThat(list.get(0)).isEqualTo("firstValue"); assertThat(list.get(1)).isEqualTo("secondValue"); - assertThat(list.size()).isEqualTo(2); + assertThat(list).hasSize(2); } @Test @@ -1959,7 +1959,7 @@ void setAutoGrowCollectionLimit() { pvs.add("integerList[256]", "1"); binder.bind(pvs); - assertThat(tb.getIntegerList().size()).isEqualTo(257); + assertThat(tb.getIntegerList()).hasSize(257); assertThat(tb.getIntegerList().get(256)).isEqualTo(Integer.valueOf(1)); assertThat(binder.getBindingResult().getFieldValue("integerList[256]")).isEqualTo(1); } diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java index ffcb29095418..bcbeda94f7c6 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java @@ -68,7 +68,7 @@ void simpleValidation() { ValidPerson person = new ValidPerson(); Set> result = validator.validate(person); - assertThat(result.size()).isEqualTo(2); + assertThat(result).hasSize(2); for (ConstraintViolation cv : result) { String path = cv.getPropertyPath().toString(); assertThat(path).matches(actual -> "name".equals(actual) || "address.street".equals(actual)); @@ -92,7 +92,7 @@ void simpleValidationWithCustomProvider() { ValidPerson person = new ValidPerson(); Set> result = validator.validate(person); - assertThat(result.size()).isEqualTo(2); + assertThat(result).hasSize(2); for (ConstraintViolation cv : result) { String path = cv.getPropertyPath().toString(); assertThat(path).matches(actual -> "name".equals(actual) || "address.street".equals(actual)); @@ -117,7 +117,7 @@ void simpleValidationWithClassLevel() { person.setName("Juergen"); person.getAddress().setStreet("Juergen's Street"); Set> result = validator.validate(person); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); Iterator> iterator = result.iterator(); ConstraintViolation cv = iterator.next(); assertThat(cv.getPropertyPath().toString()).isEqualTo(""); @@ -158,7 +158,7 @@ void springValidation() { FieldError fieldError = result.getFieldError("name"); assertThat(fieldError.getField()).isEqualTo("name"); List errorCodes = Arrays.asList(fieldError.getCodes()); - assertThat(errorCodes.size()).isEqualTo(4); + assertThat(errorCodes).hasSize(4); assertThat(errorCodes.contains("NotNull.person.name")).isTrue(); assertThat(errorCodes.contains("NotNull.name")).isTrue(); assertThat(errorCodes.contains("NotNull.java.lang.String")).isTrue(); @@ -166,7 +166,7 @@ void springValidation() { fieldError = result.getFieldError("address.street"); assertThat(fieldError.getField()).isEqualTo("address.street"); errorCodes = Arrays.asList(fieldError.getCodes()); - assertThat(errorCodes.size()).isEqualTo(5); + assertThat(errorCodes).hasSize(5); assertThat(errorCodes.contains("NotNull.person.address.street")).isTrue(); assertThat(errorCodes.contains("NotNull.address.street")).isTrue(); assertThat(errorCodes.contains("NotNull.street")).isTrue(); @@ -190,7 +190,7 @@ void springValidationWithClassLevel() { assertThat(result.getErrorCount()).isEqualTo(1); ObjectError globalError = result.getGlobalError(); List errorCodes = Arrays.asList(globalError.getCodes()); - assertThat(errorCodes.size()).isEqualTo(2); + assertThat(errorCodes).hasSize(2); assertThat(errorCodes.contains("NameAddressValid.person")).isTrue(); assertThat(errorCodes.contains("NameAddressValid")).isTrue(); @@ -212,7 +212,7 @@ void springValidationWithAutowiredValidator() { assertThat(result.getErrorCount()).isEqualTo(1); ObjectError globalError = result.getGlobalError(); List errorCodes = Arrays.asList(globalError.getCodes()); - assertThat(errorCodes.size()).isEqualTo(2); + assertThat(errorCodes).hasSize(2); assertThat(errorCodes.contains("NameAddressValid.person")).isTrue(); assertThat(errorCodes.contains("NameAddressValid")).isTrue(); diff --git a/spring-context/src/test/resources/example/scannable/spring.components b/spring-context/src/test/resources/example/scannable/spring.components index 9e7303d2517b..a859835ba72e 100644 --- a/spring-context/src/test/resources/example/scannable/spring.components +++ b/spring-context/src/test/resources/example/scannable/spring.components @@ -7,4 +7,8 @@ example.scannable.ScopedProxyTestBean=example.scannable.FooService example.scannable.StubFooDao=org.springframework.stereotype.Component example.scannable.NamedStubDao=org.springframework.stereotype.Component example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component -example.scannable.sub.BarComponent=org.springframework.stereotype.Component \ No newline at end of file +example.scannable.sub.BarComponent=org.springframework.stereotype.Component +example.scannable.JakartaManagedBeanComponent=jakarta.annotation.ManagedBean +example.scannable.JakartaNamedComponent=jakarta.inject.Named +example.indexed.IndexedJakartaManagedBeanComponent=jakarta.annotation.ManagedBean +example.indexed.IndexedJakartaNamedComponent=jakarta.inject.Named diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/cache/AbstractCacheTests.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/cache/AbstractCacheTests.java index e01074417a15..c919e95e1108 100644 --- a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/cache/AbstractCacheTests.java +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/cache/AbstractCacheTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -204,7 +204,7 @@ public void testCacheGetSynchronized() throws InterruptedException { } latch.await(); - assertThat(results.size()).isEqualTo(10); + assertThat(results).hasSize(10); results.forEach(r -> assertThat(r).isEqualTo(1)); // Only one method got invoked } diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredGenericTemplate.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredGenericTemplate.java new file mode 100644 index 000000000000..8659cdecb62f --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredGenericTemplate.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2022 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.annotation.Autowired; + +public class AutowiredGenericTemplate { + + @SuppressWarnings("unused") + private GenericTemplate genericTemplate; + + @Autowired + public void setGenericTemplate(GenericTemplate genericTemplate) { + this.genericTemplate = genericTemplate; + } + +} diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/GenericTemplate.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/GenericTemplate.java new file mode 100644 index 000000000000..d3fe98baf56d --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/GenericTemplate.java @@ -0,0 +1,23 @@ +/* + * Copyright 2002-2022 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; + +public interface GenericTemplate { + + void process(V item); + +} diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/GenericTemplateConfiguration.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/GenericTemplateConfiguration.java new file mode 100644 index 000000000000..938a5263275b --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/GenericTemplateConfiguration.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2022 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.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +public class GenericTemplateConfiguration { + + @Bean + public GenericTemplate genericTemplate() { + return v -> {}; + } + +} diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/index/CandidateComponentsTestClassLoader.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/index/CandidateComponentsTestClassLoader.java index 0b6d0bdd0229..122bb95c94d8 100644 --- a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/index/CandidateComponentsTestClassLoader.java +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/index/CandidateComponentsTestClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,6 +24,7 @@ import org.springframework.context.index.CandidateComponentsIndexLoader; import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; /** * A test {@link ClassLoader} that can be used in a testing context to control the @@ -35,7 +36,7 @@ public class CandidateComponentsTestClassLoader extends ClassLoader { /** - * Create a test {@link ClassLoader} that disable the use of the index, even + * Create a test {@link ClassLoader} that disables the use of the index, even * if resources are present at the standard location. * @param classLoader the classloader to use for all other operations * @return a test {@link ClassLoader} that has no index @@ -48,8 +49,9 @@ public static ClassLoader disableIndex(ClassLoader classLoader) { /** * Create a test {@link ClassLoader} that creates an index with the - * specified {@link Resource} instances + * specified {@link Resource} instances. * @param classLoader the classloader to use for all other operations + * @param resources the resources for index files * @return a test {@link ClassLoader} with an index built based on the * specified resources. */ @@ -66,8 +68,10 @@ public static ClassLoader index(ClassLoader classLoader, Resource... resources) } + @Nullable private final Enumeration resourceUrls; + @Nullable private final IOException cause; public CandidateComponentsTestClassLoader(ClassLoader classLoader, Enumeration resourceUrls) { diff --git a/spring-core-test/src/main/java/org/springframework/aot/agent/InvocationsRecorderClassTransformer.java b/spring-core-test/src/main/java/org/springframework/aot/agent/InvocationsRecorderClassTransformer.java index 8e9b4a2a241f..f9cc6abd17f3 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/agent/InvocationsRecorderClassTransformer.java +++ b/spring-core-test/src/main/java/org/springframework/aot/agent/InvocationsRecorderClassTransformer.java @@ -44,8 +44,8 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer { private final String[] ignoredPackages; public InvocationsRecorderClassTransformer(String[] instrumentedPackages, String[] ignoredPackages) { - Assert.notNull(instrumentedPackages, "instrumentedPackages should not be null"); - Assert.notNull(ignoredPackages, "ignoredPackages should not be null"); + Assert.notNull(instrumentedPackages, "instrumentedPackages must not be null"); + Assert.notNull(ignoredPackages, "ignoredPackages must not be null"); this.instrumentedPackages = rewriteToAsmFormat(instrumentedPackages); this.ignoredPackages = rewriteToAsmFormat(ignoredPackages); } diff --git a/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java b/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java index c5ce25bd2b4c..c80a82eac105 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java +++ b/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java @@ -98,7 +98,7 @@ public Stream getStackFrames() { */ @SuppressWarnings("unchecked") public T getInstance() { - Assert.notNull(this.instance, "Cannot resolve 'this' for static invocations"); + Assert.state(this.instance != null, "Cannot resolve 'this' for static invocations"); return (T) this.instance; } @@ -108,7 +108,7 @@ public T getInstance() { * @throws IllegalStateException in case of static invocations (there is no {@code this}) */ public TypeReference getInstanceTypeReference() { - Assert.notNull(this.instance, "Cannot resolve 'this' for static invocations"); + Assert.state(this.instance != null, "Cannot resolve 'this' for static invocations"); return TypeReference.of(this.instance.getClass()); } diff --git a/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsInvocationsAssert.java b/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsInvocationsAssert.java index 0d0b52ecedea..34eea845d5f1 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsInvocationsAssert.java +++ b/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsInvocationsAssert.java @@ -74,7 +74,7 @@ private void configureRuntimeHints(RuntimeHints hints) { * @throws AssertionError if any of the recorded invocations has no match in the provided hints */ public void match(RuntimeHints runtimeHints) { - Assert.notNull(runtimeHints, "RuntimeHints should not be null"); + Assert.notNull(runtimeHints, "RuntimeHints must not be null"); configureRuntimeHints(runtimeHints); List noMatchInvocations = this.actual.recordedInvocations().filter(invocation -> !invocation.matches(runtimeHints)).toList(); @@ -84,7 +84,7 @@ public void match(RuntimeHints runtimeHints) { } public ListAssert notMatching(RuntimeHints runtimeHints) { - Assert.notNull(runtimeHints, "RuntimeHints should not be null"); + Assert.notNull(runtimeHints, "RuntimeHints must not be null"); configureRuntimeHints(runtimeHints); return ListAssert.assertThatStream(this.actual.recordedInvocations() .filter(invocation -> !invocation.matches(runtimeHints))); diff --git a/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsRecorder.java b/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsRecorder.java index abad3a855289..31b20241b2a0 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsRecorder.java +++ b/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsRecorder.java @@ -48,8 +48,8 @@ private RuntimeHintsRecorder() { * @return the recorded invocations */ public synchronized static RuntimeHintsInvocations record(Runnable action) { - Assert.notNull(action, "Runnable action should not be null"); - Assert.isTrue(RuntimeHintsAgent.isLoaded(), "RuntimeHintsAgent should be loaded in the current JVM"); + Assert.notNull(action, "Runnable action must not be null"); + Assert.state(RuntimeHintsAgent.isLoaded(), "RuntimeHintsAgent must be loaded in the current JVM"); RuntimeHintsRecorder recorder = new RuntimeHintsRecorder(); RecordedInvocationsPublisher.addListener(recorder.listener); try { diff --git a/spring-core-test/src/main/java/org/springframework/aot/test/generate/CompilerFiles.java b/spring-core-test/src/main/java/org/springframework/aot/test/generate/CompilerFiles.java index 304241f1b527..8b0b18bd80ce 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/test/generate/CompilerFiles.java +++ b/spring-core-test/src/main/java/org/springframework/aot/test/generate/CompilerFiles.java @@ -56,8 +56,7 @@ public TestCompiler apply(TestCompiler testCompiler) { return testCompiler .withSources(adapt(Kind.SOURCE, (path, inputStreamSource) -> SourceFile.of(inputStreamSource))) - .withResources(adapt(Kind.RESOURCE, (path, inputStreamSource) -> - ResourceFile.of(path, inputStreamSource))) + .withResources(adapt(Kind.RESOURCE, ResourceFile::of)) .withClasses(adapt(Kind.CLASS, (path, inputStreamSource) -> ClassFile.of(ClassFile.toClassName(path), inputStreamSource))); } diff --git a/spring-core-test/src/main/java/org/springframework/core/test/tools/DynamicClassLoader.java b/spring-core-test/src/main/java/org/springframework/core/test/tools/DynamicClassLoader.java index e5ff9a9411e8..6412cd0b060d 100644 --- a/spring-core-test/src/main/java/org/springframework/core/test/tools/DynamicClassLoader.java +++ b/spring-core-test/src/main/java/org/springframework/core/test/tools/DynamicClassLoader.java @@ -155,7 +155,7 @@ private URL createResourceUrl(String name, Supplier bytesSupplier) { private static Method lookupMethod(Class target, String name, Class... parameterTypes) { Method method = ReflectionUtils.findMethod(target, name, parameterTypes); - Assert.notNull(method, "Expected method '" + name + "' on '" + target.getName()); + Assert.notNull(method, () -> "Could not find method '%s' on '%s'".formatted(name, target.getName())); return method; } diff --git a/spring-core-test/src/main/java/org/springframework/core/test/tools/SourceFile.java b/spring-core-test/src/main/java/org/springframework/core/test/tools/SourceFile.java index 91f7da6f8b45..bf0ae02076d5 100644 --- a/spring-core-test/src/main/java/org/springframework/core/test/tools/SourceFile.java +++ b/spring-core-test/src/main/java/org/springframework/core/test/tools/SourceFile.java @@ -180,9 +180,8 @@ private static String getClassName(String content) { } Assert.state(javaSource.getClasses().size() == 1, "Source must define a single class"); JavaClass javaClass = javaSource.getClasses().get(0); - return (javaSource.getPackage() != null) - ? javaSource.getPackageName() + "." + javaClass.getName() - : javaClass.getName(); + return (javaSource.getPackage() != null) ? + (javaSource.getPackageName() + "." + javaClass.getName()) : javaClass.getName(); } catch (Exception ex) { throw new IllegalStateException( diff --git a/spring-core-test/src/test/java/org/springframework/aot/agent/RecordedInvocationTests.java b/spring-core-test/src/test/java/org/springframework/aot/agent/RecordedInvocationTests.java index bf46e804e86c..35b633dd810f 100644 --- a/spring-core-test/src/test/java/org/springframework/aot/agent/RecordedInvocationTests.java +++ b/spring-core-test/src/test/java/org/springframework/aot/agent/RecordedInvocationTests.java @@ -63,8 +63,8 @@ void buildValidStaticInvocation() { @Test void staticInvocationShouldThrowWhenGetInstance() { - assertThatThrownBy(staticInvocation::getInstance).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(staticInvocation::getInstanceTypeReference).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(staticInvocation::getInstance).isInstanceOf(IllegalStateException.class); + assertThatThrownBy(staticInvocation::getInstanceTypeReference).isInstanceOf(IllegalStateException.class); } @Test diff --git a/spring-core-test/src/test/java/org/springframework/core/test/tools/DynamicJavaFileManagerTests.java b/spring-core-test/src/test/java/org/springframework/core/test/tools/DynamicJavaFileManagerTests.java index 5f6e2442586f..474c506bf702 100644 --- a/spring-core-test/src/test/java/org/springframework/core/test/tools/DynamicJavaFileManagerTests.java +++ b/spring-core-test/src/test/java/org/springframework/core/test/tools/DynamicJavaFileManagerTests.java @@ -136,7 +136,7 @@ void listWithRecurseReturnsClassesInRequestedPackageAndSubpackages() throws IOEx void listWithoutClassKindDoesNotReturnClasses() throws IOException { Iterable listed = this.fileManager.list( this.location, "com.example", EnumSet.of(Kind.SOURCE), true); - assertThat(listed).hasSize(0); + assertThat(listed).isEmpty(); } @Test diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index 9c9f4a04a87f..1948a728f6a7 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -84,6 +84,8 @@ dependencies { testImplementation("io.projectreactor.tools:blockhound") testImplementation("org.skyscreamer:jsonassert") testImplementation("com.squareup.okhttp3:mockwebserver") + testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json") + testImplementation("com.fasterxml.jackson.core:jackson-annotations") testFixturesImplementation("com.google.code.findbugs:jsr305") testFixturesImplementation("org.junit.platform:junit-platform-launcher") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api") @@ -91,7 +93,6 @@ dependencies { testFixturesImplementation("org.assertj:assertj-core") testFixturesImplementation("org.xmlunit:xmlunit-assertj") testFixturesImplementation("io.projectreactor:reactor-test") - testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json") } jar { diff --git a/spring-core/src/jmh/java/org/springframework/core/convert/support/GenericConversionServiceBenchmark.java b/spring-core/src/jmh/java/org/springframework/core/convert/support/GenericConversionServiceBenchmark.java index 24c4a80db107..8748e9d144d4 100644 --- a/spring-core/src/jmh/java/org/springframework/core/convert/support/GenericConversionServiceBenchmark.java +++ b/spring-core/src/jmh/java/org/springframework/core/convert/support/GenericConversionServiceBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/main/java/org/springframework/aot/generate/DefaultMethodReference.java b/spring-core/src/main/java/org/springframework/aot/generate/DefaultMethodReference.java index b3a3ab117d8e..ca512fb27d0b 100644 --- a/spring-core/src/main/java/org/springframework/aot/generate/DefaultMethodReference.java +++ b/spring-core/src/main/java/org/springframework/aot/generate/DefaultMethodReference.java @@ -42,16 +42,18 @@ public class DefaultMethodReference implements MethodReference { @Nullable private final ClassName declaringClass; + public DefaultMethodReference(MethodSpec method, @Nullable ClassName declaringClass) { this.method = method; this.declaringClass = declaringClass; } + @Override public CodeBlock toCodeBlock() { String methodName = this.method.name; if (isStatic()) { - Assert.notNull(this.declaringClass, "static method reference must define a declaring class"); + Assert.state(this.declaringClass != null, "static method reference must define a declaring class"); return CodeBlock.of("$T::$L", this.declaringClass, methodName); } else { @@ -59,12 +61,13 @@ public CodeBlock toCodeBlock() { } } + @Override public CodeBlock toInvokeCodeBlock(ArgumentCodeGenerator argumentCodeGenerator, @Nullable ClassName targetClassName) { String methodName = this.method.name; CodeBlock.Builder code = CodeBlock.builder(); if (isStatic()) { - Assert.notNull(this.declaringClass, "static method reference must define a declaring class"); + Assert.state(this.declaringClass != null, "static method reference must define a declaring class"); if (isSameDeclaringClass(targetClassName)) { code.add("$L", methodName); } @@ -125,9 +128,8 @@ public String toString() { return this.declaringClass + "::" + methodName; } else { - return ((this.declaringClass != null) - ? "<" + this.declaringClass + ">" : "") - + "::" + methodName; + return ((this.declaringClass != null) ? + "<" + this.declaringClass + ">" : "") + "::" + methodName; } } diff --git a/spring-core/src/main/java/org/springframework/aot/generate/MethodReference.java b/spring-core/src/main/java/org/springframework/aot/generate/MethodReference.java index f6dda9710077..a33573d74fd3 100644 --- a/spring-core/src/main/java/org/springframework/aot/generate/MethodReference.java +++ b/spring-core/src/main/java/org/springframework/aot/generate/MethodReference.java @@ -94,8 +94,8 @@ static ArgumentCodeGenerator none() { * @return a new {@link ArgumentCodeGenerator} instance */ static ArgumentCodeGenerator of(Class argumentType, String argumentCode) { - return from(candidateType -> (candidateType.equals(ClassName.get(argumentType)) - ? CodeBlock.of(argumentCode) : null)); + return from(candidateType -> candidateType.equals(ClassName.get(argumentType)) ? + CodeBlock.of(argumentCode) : null); } /** diff --git a/spring-core/src/main/java/org/springframework/aot/hint/AbstractTypeReference.java b/spring-core/src/main/java/org/springframework/aot/hint/AbstractTypeReference.java index ddc26ebfc8c1..834753f61638 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/AbstractTypeReference.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/AbstractTypeReference.java @@ -47,9 +47,8 @@ protected AbstractTypeReference(String packageName, String simpleName, @Nullable public String getName() { TypeReference enclosingType = getEnclosingType(); String simpleName = getSimpleName(); - return (enclosingType != null - ? (enclosingType.getName() + '$' + simpleName) - : addPackageIfNecessary(simpleName)); + return (enclosingType != null ? (enclosingType.getName() + '$' + simpleName) : + addPackageIfNecessary(simpleName)); } @Override diff --git a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java index c162b0b4514d..4e3d0f4136ab 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java @@ -16,6 +16,7 @@ package org.springframework.aot.hint; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.RecordComponent; import java.lang.reflect.Type; @@ -28,8 +29,11 @@ import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; /** * Register the necessary reflection hints so that the specified type can be @@ -45,6 +49,11 @@ public class BindingReflectionHintsRegistrar { private static final String KOTLIN_COMPANION_SUFFIX = "$Companion"; + private static final String JACKSON_ANNOTATION = "com.fasterxml.jackson.annotation.JacksonAnnotation"; + + private static final boolean jacksonAnnotationPresent = ClassUtils.isPresent(JACKSON_ANNOTATION, + BindingReflectionHintsRegistrar.class.getClassLoader()); + /** * Register the necessary reflection hints to bind the specified types. * @param hints the hints instance to use @@ -62,7 +71,7 @@ private boolean shouldSkipType(Class type) { } private boolean shouldSkipMembers(Class type) { - return type.getCanonicalName().startsWith("java.") || type.isArray(); + return (type.getCanonicalName() != null && type.getCanonicalName().startsWith("java.")) || type.isArray(); } private void registerReflectionHints(ReflectionHints hints, Set seen, Type type) { @@ -82,25 +91,28 @@ private void registerReflectionHints(ReflectionHints hints, Set seen, Type registerRecordHints(hints, seen, recordComponent.getAccessor()); } } - else { - typeHint.withMembers( - MemberCategory.DECLARED_FIELDS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); - for (Method method : clazz.getMethods()) { - String methodName = method.getName(); - if (methodName.startsWith("set") && method.getParameterCount() == 1) { - registerPropertyHints(hints, seen, method, 0); - } - else if ((methodName.startsWith("get") && method.getParameterCount() == 0 && method.getReturnType() != Void.TYPE) || - (methodName.startsWith("is") && method.getParameterCount() == 0 && method.getReturnType() == boolean.class)) { - registerPropertyHints(hints, seen, method, -1); - } + typeHint.withMembers( + MemberCategory.DECLARED_FIELDS, + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + for (Method method : clazz.getMethods()) { + String methodName = method.getName(); + if (methodName.startsWith("set") && method.getParameterCount() == 1) { + registerPropertyHints(hints, seen, method, 0); + } + else if ((methodName.startsWith("get") && method.getParameterCount() == 0 && method.getReturnType() != Void.TYPE) || + (methodName.startsWith("is") && method.getParameterCount() == 0 && method.getReturnType() == boolean.class)) { + registerPropertyHints(hints, seen, method, -1); } } + if (jacksonAnnotationPresent) { + registerJacksonHints(hints, clazz); + } } if (KotlinDetector.isKotlinType(clazz)) { KotlinDelegate.registerComponentHints(hints, clazz); registerKotlinSerializationHints(hints, clazz); + // For Kotlin reflection + typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_METHODS); } }); } @@ -147,6 +159,31 @@ private void collectReferencedTypes(Set> types, ResolvableType resolvab } } + private void registerJacksonHints(ReflectionHints hints, Class clazz) { + ReflectionUtils.doWithFields(clazz, field -> + MergedAnnotations + .from(field, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(JACKSON_ANNOTATION) + .filter(MergedAnnotation::isMetaPresent) + .forEach(annotation -> { + Field sourceField = (Field) annotation.getSource(); + if (sourceField != null) { + hints.registerField(sourceField); + } + })); + ReflectionUtils.doWithMethods(clazz, method -> + MergedAnnotations + .from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(JACKSON_ANNOTATION) + .filter(MergedAnnotation::isMetaPresent) + .forEach(annotation -> { + Method sourceMethod = (Method) annotation.getSource(); + if (sourceMethod != null) { + hints.registerMethod(sourceMethod, ExecutableMode.INVOKE); + } + })); + } + /** * Inner class to avoid a hard dependency on Kotlin at runtime. */ @@ -157,7 +194,7 @@ public static void registerComponentHints(ReflectionHints hints, Class type) if (kClass.isData()) { for (Method method : type.getMethods()) { String methodName = method.getName(); - if (methodName.startsWith("component") || methodName.equals("copy")) { + if (methodName.startsWith("component") || methodName.equals("copy") || methodName.equals("copy$default")) { hints.registerMethod(method, ExecutableMode.INVOKE); } } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionTypeReference.java b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionTypeReference.java index 2da3520648c0..ea0a2788098f 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionTypeReference.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionTypeReference.java @@ -35,9 +35,8 @@ private ReflectionTypeReference(Class type) { @Nullable private static TypeReference getEnclosingClass(Class type) { - Class candidate = (type.isArray() - ? type.getComponentType().getEnclosingClass() - : type.getEnclosingClass()); + Class candidate = (type.isArray() ? type.getComponentType().getEnclosingClass() : + type.getEnclosingClass()); return (candidate != null ? new ReflectionTypeReference(candidate) : null); } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java index ef6219b3b55b..e12ceae9d490 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java @@ -29,17 +29,15 @@ * *

When present, either directly or as a meta-annotation, this annotation * triggers the configured {@linkplain ReflectiveProcessor processors} against - * the annotated element. By default, a reflection hint is added on the + * the annotated element. By default, a reflection hint is registered for the * annotated element so that it can be discovered and invoked if necessary. * - *

A reflection hint is also added if necessary on the annotation that - * directly uses this annotation. - * * @author Stephane Nicoll * @author Sam Brannen * @since 6.0 * @see SimpleReflectiveProcessor * @see ReflectiveRuntimeHintsRegistrar + * @see RegisterReflectionForBinding @RegisterReflectionForBinding */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD }) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveProcessor.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveProcessor.java index a753d385628f..74d1b1385f3a 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveProcessor.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveProcessor.java @@ -24,8 +24,12 @@ * Process an {@link AnnotatedElement} and register the necessary reflection * hints for it. * + *

{@code ReflectiveProcessor} implementations are registered via + * {@link Reflective#processors() @Reflective(processors = ...)}. + * * @author Stephane Nicoll * @since 6.0 + * @see Reflective @Reflective */ public interface ReflectiveProcessor { diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java index f860745a50a0..61e80a3fa2e2 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java @@ -96,12 +96,12 @@ private Entry createEntry(AnnotatedElement element) { .stream(Reflective.class) .map(annotation -> annotation.getClassArray("value")) .flatMap(Arrays::stream) - .map(type -> (Class) type) .distinct() + .map(type -> (Class) type) .map(processorClass -> this.processors.computeIfAbsent(processorClass, this::instantiateClass)) .toList(); - ReflectiveProcessor processorToUse = (processors.size() == 1 ? processors.get(0) - : new DelegatingReflectiveProcessor(processors)); + ReflectiveProcessor processorToUse = (processors.size() == 1 ? processors.get(0) : + new DelegatingReflectiveProcessor(processors)); return new Entry(element, processorToUse); } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBinding.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBinding.java index 900a9ee3d3a8..d0a5b9c98ba9 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBinding.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBinding.java @@ -25,24 +25,24 @@ import org.springframework.core.annotation.AliasFor; /** - * Indicate that the classes specified in the annotation attributes require some - * reflection hints for binding or reflection-based serialization purpose. For each + * Indicates that the classes specified in the annotation attributes require some + * reflection hints for binding or reflection-based serialization purposes. For each * class specified, hints on constructors, fields, properties, record components, * including types transitively used on properties and record components are registered. * At least one class must be specified in the {@code value} or {@code classes} annotation * attributes. * - *

Annotated element can be a configuration class, for example: + *

The annotated element can be a configuration class — for example: * *

  * @Configuration
  * @RegisterReflectionForBinding({ Foo.class, Bar.class })
  * public class MyConfig {
- *
  *     // ...
  * }
* - *

Annotated element can also be any Spring bean class, constructor, field or method, for example: + *

The annotated element can be any Spring bean class, constructor, field, + * or method — for example: * *

  * @Service
@@ -55,9 +55,13 @@
  *
  * }
* + *

The annotated element can also be any test class that uses the Spring + * TestContext Framework to load an {@code ApplicationContext}. + * * @author Sebastien Deleuze * @since 6.0 * @see org.springframework.aot.hint.BindingReflectionHintsRegistrar + * @see Reflective @Reflective */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @@ -66,16 +70,15 @@ public @interface RegisterReflectionForBinding { /** - * Classes for which reflection hints should be registered. At least one class must specified - * either in {@code value} or {@code classes}. - * @see #classes() + * Alias for {@link #classes()}. */ @AliasFor("classes") Class[] value() default {}; /** - * Classes for which reflection hints should be registered. At least one class must specified - * either in {@code value} or {@code classes}. + * Classes for which reflection hints should be registered. + *

At least one class must be specified either via {@link #value} or + * {@link #classes}. * @see #value() */ @AliasFor("value") diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessor.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessor.java index dee235580ec5..06e79ee53af4 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessor.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/RegisterReflectionForBindingProcessor.java @@ -30,14 +30,17 @@ * * @author Sebastien Deleuze * @since 6.0 + * @see RegisterReflectionForBinding @RegisterReflectionForBinding */ public class RegisterReflectionForBindingProcessor implements ReflectiveProcessor { private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + @Override public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) { - RegisterReflectionForBinding registerReflection = AnnotationUtils.getAnnotation(element, RegisterReflectionForBinding.class); + RegisterReflectionForBinding registerReflection = + AnnotationUtils.getAnnotation(element, RegisterReflectionForBinding.class); if (registerReflection != null) { Class[] classes = registerReflection.classes(); Assert.state(classes.length != 0, () -> "A least one class should be specified in " + diff --git a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java index 2de9a4118289..31c66348f8e5 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java @@ -51,6 +51,7 @@ public class ReflectionHintsPredicates { ReflectionHintsPredicates() { } + /** * Return a predicate that checks whether a reflection hint is registered for the given type. *

The returned type exposes additional methods that refine the predicate behavior. @@ -58,7 +59,7 @@ public class ReflectionHintsPredicates { * @return the {@link RuntimeHints} predicate */ public TypeHintPredicate onType(TypeReference typeReference) { - Assert.notNull(typeReference, "'typeReference' should not be null"); + Assert.notNull(typeReference, "'typeReference' must not be null"); return new TypeHintPredicate(typeReference); } @@ -69,7 +70,7 @@ public TypeHintPredicate onType(TypeReference typeReference) { * @return the {@link RuntimeHints} predicate */ public TypeHintPredicate onType(Class type) { - Assert.notNull(type, "'type' should not be null"); + Assert.notNull(type, "'type' must not be null"); return new TypeHintPredicate(TypeReference.of(type)); } @@ -81,7 +82,7 @@ public TypeHintPredicate onType(Class type) { * @return the {@link RuntimeHints} predicate */ public ConstructorHintPredicate onConstructor(Constructor constructor) { - Assert.notNull(constructor, "'constructor' should not be null"); + Assert.notNull(constructor, "'constructor' must not be null"); return new ConstructorHintPredicate(constructor); } @@ -93,7 +94,7 @@ public ConstructorHintPredicate onConstructor(Constructor constructor) { * @return the {@link RuntimeHints} predicate */ public MethodHintPredicate onMethod(Method method) { - Assert.notNull(method, "'method' should not be null"); + Assert.notNull(method, "'method' must not be null"); return new MethodHintPredicate(method); } @@ -108,8 +109,8 @@ public MethodHintPredicate onMethod(Method method) { * @throws IllegalArgumentException if the method cannot be found or if multiple methods are found with the same name. */ public MethodHintPredicate onMethod(Class type, String methodName) { - Assert.notNull(type, "'type' should not be null"); - Assert.hasText(methodName, "'methodName' should not be empty"); + Assert.notNull(type, "'type' must not be null"); + Assert.hasText(methodName, "'methodName' must not be empty"); return new MethodHintPredicate(getMethod(type, methodName)); } @@ -125,8 +126,8 @@ public MethodHintPredicate onMethod(Class type, String methodName) { * @throws IllegalArgumentException if the method cannot be found or if multiple methods are found with the same name. */ public MethodHintPredicate onMethod(String className, String methodName) throws ClassNotFoundException { - Assert.hasText(className, "'className' should not be empty"); - Assert.hasText(methodName, "'methodName' should not be empty"); + Assert.hasText(className, "'className' must not be empty"); + Assert.hasText(methodName, "'methodName' must not be empty"); return onMethod(Class.forName(className), methodName); } @@ -155,8 +156,8 @@ else if (methods.size() > 1) { * @throws IllegalArgumentException if a field cannot be found with the given name. */ public FieldHintPredicate onField(Class type, String fieldName) { - Assert.notNull(type, "'type' should not be null"); - Assert.hasText(fieldName, "'fieldName' should not be empty"); + Assert.notNull(type, "'type' must not be null"); + Assert.hasText(fieldName, "'fieldName' must not be empty"); Field field = ReflectionUtils.findField(type, fieldName); if (field == null) { throw new IllegalArgumentException("No field named '%s' on class %s".formatted(fieldName, type.getName())); @@ -176,8 +177,8 @@ public FieldHintPredicate onField(Class type, String fieldName) { * @throws IllegalArgumentException if a field cannot be found with the given name. */ public FieldHintPredicate onField(String className, String fieldName) throws ClassNotFoundException { - Assert.hasText(className, "'className' should not be empty"); - Assert.hasText(fieldName, "'fieldName' should not be empty"); + Assert.hasText(className, "'className' must not be empty"); + Assert.hasText(fieldName, "'fieldName' must not be empty"); return onField(Class.forName(className), fieldName); } @@ -189,7 +190,7 @@ public FieldHintPredicate onField(String className, String fieldName) throws Cla * @return the {@link RuntimeHints} predicate */ public FieldHintPredicate onField(Field field) { - Assert.notNull(field, "'field' should not be null"); + Assert.notNull(field, "'field' must not be null"); return new FieldHintPredicate(field); } @@ -218,7 +219,7 @@ public boolean test(RuntimeHints hints) { * @return the refined {@link RuntimeHints} predicate */ public Predicate withMemberCategory(MemberCategory memberCategory) { - Assert.notNull(memberCategory, "'memberCategory' should not be null"); + Assert.notNull(memberCategory, "'memberCategory' must not be null"); return this.and(hints -> getTypeHint(hints).getMemberCategories().contains(memberCategory)); } @@ -228,7 +229,7 @@ public Predicate withMemberCategory(MemberCategory memberCategory) * @return the refined {@link RuntimeHints} predicate */ public Predicate withMemberCategories(MemberCategory... memberCategories) { - Assert.notEmpty(memberCategories, "'memberCategories' should not be empty"); + Assert.notEmpty(memberCategories, "'memberCategories' must not be empty"); return this.and(hints -> getTypeHint(hints).getMemberCategories().containsAll(Arrays.asList(memberCategories))); } @@ -238,7 +239,7 @@ public Predicate withMemberCategories(MemberCategory... memberCate * @return the refined {@link RuntimeHints} predicate */ public Predicate withAnyMemberCategory(MemberCategory... memberCategories) { - Assert.notEmpty(memberCategories, "'memberCategories' should not be empty"); + Assert.notEmpty(memberCategories, "'memberCategories' must not be empty"); return this.and(hints -> Arrays.stream(memberCategories) .anyMatch(memberCategory -> getTypeHint(hints).getMemberCategories().contains(memberCategory))); } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/predicate/SerializationHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/predicate/SerializationHintsPredicates.java index 833937aed5ed..576131302710 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/predicate/SerializationHintsPredicates.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/predicate/SerializationHintsPredicates.java @@ -35,6 +35,7 @@ public class SerializationHintsPredicates { SerializationHintsPredicates() { } + /** * Return a predicate that checks whether a {@link SerializationHints * serialization hint} is registered for the given type. @@ -43,7 +44,7 @@ public class SerializationHintsPredicates { * @see java.lang.reflect.Proxy */ public Predicate onType(Class type) { - Assert.notNull(type, "'type' should not be null"); + Assert.notNull(type, "'type' must not be null"); return onType(TypeReference.of(type)); } @@ -55,7 +56,7 @@ public Predicate onType(Class type) { * @see java.lang.reflect.Proxy */ public Predicate onType(TypeReference typeReference) { - Assert.notNull(typeReference, "'typeReference' should not be null"); + Assert.notNull(typeReference, "'typeReference' must not be null"); return hints -> hints.serialization().javaSerializationHints().anyMatch( hint -> hint.getType().equals(typeReference)); } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/ClassHintUtils.java b/spring-core/src/main/java/org/springframework/aot/hint/support/ClassHintUtils.java new file mode 100644 index 000000000000..e38fdaaa970a --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/ClassHintUtils.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2022 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.aot.hint.support; + +import java.lang.reflect.Proxy; +import java.util.function.Consumer; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeHint; +import org.springframework.util.ClassUtils; + +/** + * Utilities for core hint inference on Spring-managed classes, + * specifically for proxy types such as interface-based JDK proxies + * and CGLIB-generated subclasses which need proxy/reflection hints. + * + *

Note that this class does not take specifics of Spring AOP or + * any other framework arrangement into account. It just operates + * on the JDK and CGLIB proxy facilities and their core conventions. + * + * @author Juergen Hoeller + * @since 6.0.3 + * @see org.springframework.aot.hint.ProxyHints + * @see org.springframework.aot.hint.ReflectionHints + */ +public abstract class ClassHintUtils { + + private static final Consumer asClassBasedProxy = hint -> + hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS, + MemberCategory.DECLARED_FIELDS); + + private static final Consumer asProxiedUserClass = hint -> + hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS); + + + /** + * Register a proxy hint for a JDK proxy or corresponding reflection hints + * for a CGLIB-generated subclass, if necessary. + * @param candidateClass the class to introspect + * @param runtimeHints the RuntimeHints instance to register the hints on + * @see Proxy#isProxyClass(Class) + * @see ClassUtils#getUserClass(Class) + */ + public static void registerProxyIfNecessary(Class candidateClass, RuntimeHints runtimeHints) { + if (Proxy.isProxyClass(candidateClass)) { + // A JDK proxy class needs an explicit hint + runtimeHints.proxies().registerJdkProxy(candidateClass.getInterfaces()); + } + else { + // Potentially a CGLIB-generated subclass with reflection hints + Class userClass = ClassUtils.getUserClass(candidateClass); + if (userClass != candidateClass) { + runtimeHints.reflection() + .registerType(candidateClass, asClassBasedProxy) + .registerType(userClass, asProxiedUserClass); + } + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHints.java b/spring-core/src/main/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHints.java new file mode 100644 index 000000000000..51d063c8b82f --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHints.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2022 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.aot.hint.support; + +import java.time.LocalDate; +import java.util.Collections; +import java.util.List; + +import org.springframework.aot.hint.ExecutableMode; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; +import org.springframework.lang.Nullable; + +/** + * {@link RuntimeHintsRegistrar} to register hints for popular conventions in + * {@code org.springframework.core.convert.support.ObjectToObjectConverter}. + * + * @author Sebastien Deleuze + * @since 6.0 + */ +class ObjectToObjectConverterRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { + TypeReference sqlDateTypeReference = TypeReference.of("java.sql.Date"); + hints.reflection().registerTypeIfPresent(classLoader, sqlDateTypeReference.getName(), hint -> hint + .withMethod("toLocalDate", Collections.emptyList(), ExecutableMode.INVOKE) + .onReachableType(sqlDateTypeReference) + .withMethod("valueOf", List.of(TypeReference.of(LocalDate.class)), ExecutableMode.INVOKE) + .onReachableType(sqlDateTypeReference)); + } + +} diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/feature/PreComputeFieldFeature.java b/spring-core/src/main/java/org/springframework/aot/nativex/feature/PreComputeFieldFeature.java index 9efa0343343b..4c8596b30e91 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/feature/PreComputeFieldFeature.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/feature/PreComputeFieldFeature.java @@ -34,9 +34,11 @@ class PreComputeFieldFeature implements Feature { private static Pattern[] patterns = { Pattern.compile(Pattern.quote("org.springframework.core.NativeDetector#imageCode")), + Pattern.compile(Pattern.quote("org.springframework.cglib.core.AbstractClassGenerator#imageCode")), Pattern.compile(Pattern.quote("org.springframework.") + ".*#.*Present"), Pattern.compile(Pattern.quote("org.springframework.") + ".*#.*PRESENT"), - Pattern.compile(Pattern.quote("reactor.") + ".*#.*Available") + Pattern.compile(Pattern.quote("reactor.") + ".*#.*Available"), + Pattern.compile(Pattern.quote("org.apache.commons.logging.LogAdapter") + "#.*Present") }; private final ThrowawayClassLoader throwawayClassLoader = new ThrowawayClassLoader(PreComputeFieldFeature.class.getClassLoader()); diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_ClassFinder.java b/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_ClassFinder.java index d3cc059e3a46..921a33b4c26b 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_ClassFinder.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_ClassFinder.java @@ -23,6 +23,8 @@ * Allow to reference {@code com.sun.beans.finder.ClassFinder} from * {@link Target_Introspector}. * + * TODO Remove once Spring Framework requires GraalVM 23.0+, see graal#5224. + * * @author Sebastien Deleuze * @since 6.0 */ diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_Introspector.java b/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_Introspector.java index ed98d969d03a..c88d6a9367fb 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_Introspector.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/substitution/Target_Introspector.java @@ -25,6 +25,8 @@ * {@link java.beans.Introspector} substitution with a refined {@code findCustomizerClass} implementation * designed to avoid thousands of AWT classes to be included in the native image. * + * TODO Remove once Spring Framework requires GraalVM 23.0+, see graal#5224. + * * @author Sebastien Deleuze * @since 6.0 */ diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/BeanCopier.java b/spring-core/src/main/java/org/springframework/cglib/beans/BeanCopier.java index 30a00e8d1d07..b5fbfa46de05 100644 --- a/spring-core/src/main/java/org/springframework/cglib/beans/BeanCopier.java +++ b/spring-core/src/main/java/org/springframework/cglib/beans/BeanCopier.java @@ -16,12 +16,25 @@ package org.springframework.cglib.beans; import java.beans.PropertyDescriptor; -import java.lang.reflect.*; +import java.lang.reflect.Modifier; import java.security.ProtectionDomain; -import org.springframework.cglib.core.*; +import java.util.HashMap; +import java.util.Map; + import org.springframework.asm.ClassVisitor; import org.springframework.asm.Type; -import java.util.*; +import org.springframework.cglib.core.AbstractClassGenerator; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.Converter; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.KeyFactory; +import org.springframework.cglib.core.Local; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.TypeUtils; /** * @author Chris Nokleberg @@ -39,7 +52,7 @@ abstract public class BeanCopier new Signature("copy", Type.VOID_TYPE, new Type[]{ Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER }); private static final Signature CONVERT = TypeUtils.parseSignature("Object convert(Object, Class, Object)"); - + interface BeanCopierKey { public Object newInstance(String source, String target, boolean useConverter); } @@ -65,14 +78,14 @@ public Generator() { } public void setSource(Class source) { - if(!Modifier.isPublic(source.getModifiers())){ + if(!Modifier.isPublic(source.getModifiers())){ setNamePrefix(source.getName()); } this.source = source; } - + public void setTarget(Class target) { - if(!Modifier.isPublic(target.getModifiers())){ + if(!Modifier.isPublic(target.getModifiers())){ setNamePrefix(target.getName()); } this.target = target; @@ -85,11 +98,13 @@ public void setUseConverter(boolean useConverter) { this.useConverter = useConverter; } - protected ClassLoader getDefaultClassLoader() { + @Override + protected ClassLoader getDefaultClassLoader() { return source.getClassLoader(); } - protected ProtectionDomain getProtectionDomain() { + @Override + protected ProtectionDomain getProtectionDomain() { return ReflectUtils.getProtectionDomain(source); } @@ -98,7 +113,8 @@ public BeanCopier create() { return (BeanCopier)super.create(key); } - public void generateClass(ClassVisitor v) { + @Override + public void generateClass(ClassVisitor v) { Type sourceType = Type.getType(source); Type targetType = Type.getType(target); ClassEmitter ce = new ClassEmitter(v); @@ -115,8 +131,8 @@ public void generateClass(ClassVisitor v) { PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target); Map names = new HashMap(); - for (int i = 0; i < getters.length; i++) { - names.put(getters[i].getName(), getters[i]); + for (PropertyDescriptor getter : getters) { + names.put(getter.getName(), getter); } Local targetLocal = e.make_local(); Local sourceLocal = e.make_local(); @@ -124,7 +140,7 @@ public void generateClass(ClassVisitor v) { e.load_arg(1); e.checkcast(targetType); e.store_local(targetLocal); - e.load_arg(0); + e.load_arg(0); e.checkcast(sourceType); e.store_local(sourceLocal); } else { @@ -133,8 +149,7 @@ public void generateClass(ClassVisitor v) { e.load_arg(0); e.checkcast(sourceType); } - for (int i = 0; i < setters.length; i++) { - PropertyDescriptor setter = setters[i]; + for (PropertyDescriptor setter : setters) { PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName()); if (getter != null) { MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod()); @@ -168,11 +183,13 @@ private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor return setter.getPropertyType().isAssignableFrom(getter.getPropertyType()); } - protected Object firstInstance(Class type) { + @Override + protected Object firstInstance(Class type) { return ReflectUtils.newInstance(type); } - protected Object nextInstance(Object instance) { + @Override + protected Object nextInstance(Object instance) { return instance; } } diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/BeanGenerator.java b/spring-core/src/main/java/org/springframework/cglib/beans/BeanGenerator.java index d4f5e7af9a0a..b6b4fb9e7c18 100644 --- a/spring-core/src/main/java/org/springframework/cglib/beans/BeanGenerator.java +++ b/spring-core/src/main/java/org/springframework/cglib/beans/BeanGenerator.java @@ -17,10 +17,18 @@ import java.beans.PropertyDescriptor; import java.security.ProtectionDomain; -import java.util.*; -import org.springframework.cglib.core.*; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + import org.springframework.asm.ClassVisitor; import org.springframework.asm.Type; +import org.springframework.cglib.core.AbstractClassGenerator; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.KeyFactory; +import org.springframework.cglib.core.ReflectUtils; /** * @author Juozas Baliuka, Chris Nokleberg @@ -31,7 +39,7 @@ public class BeanGenerator extends AbstractClassGenerator private static final Source SOURCE = new Source(BeanGenerator.class.getName()); private static final BeanGeneratorKey KEY_FACTORY = (BeanGeneratorKey)KeyFactory.create(BeanGeneratorKey.class); - + interface BeanGeneratorKey { public Object newInstance(String superclass, Map props); } @@ -67,7 +75,8 @@ public void addProperty(String name, Class type) { props.put(name, Type.getType(type)); } - protected ClassLoader getDefaultClassLoader() { + @Override + protected ClassLoader getDefaultClassLoader() { if (superclass != null) { return superclass.getClassLoader(); } else { @@ -75,7 +84,8 @@ protected ClassLoader getDefaultClassLoader() { } } - protected ProtectionDomain getProtectionDomain() { + @Override + protected ProtectionDomain getProtectionDomain() { return ReflectUtils.getProtectionDomain(superclass); } @@ -98,7 +108,8 @@ private Object createHelper() { return super.create(key); } - public void generateClass(ClassVisitor v) throws Exception { + @Override + public void generateClass(ClassVisitor v) throws Exception { int size = props.size(); String[] names = (String[])props.keySet().toArray(new String[size]); Type[] types = new Type[size]; @@ -117,7 +128,8 @@ public void generateClass(ClassVisitor v) throws Exception { ce.end_class(); } - protected Object firstInstance(Class type) { + @Override + protected Object firstInstance(Class type) { if (classOnly) { return type; } else { @@ -125,7 +137,8 @@ protected Object firstInstance(Class type) { } } - protected Object nextInstance(Object instance) { + @Override + protected Object nextInstance(Object instance) { Class protoclass = (instance instanceof Class) ? (Class)instance : instance.getClass(); if (classOnly) { return protoclass; @@ -146,8 +159,8 @@ public static void addProperties(BeanGenerator gen, Class type) { } public static void addProperties(BeanGenerator gen, PropertyDescriptor[] descriptors) { - for (int i = 0; i < descriptors.length; i++) { - gen.addProperty(descriptors[i].getName(), descriptors[i].getPropertyType()); + for (PropertyDescriptor descriptor : descriptors) { + gen.addProperty(descriptor.getName(), descriptor.getPropertyType()); } } } diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java b/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java index 12731c671788..77a73326e616 100644 --- a/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java +++ b/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java @@ -54,7 +54,7 @@ abstract public class BeanMap implements Map { * @see BeanMap.Generator#setRequire */ public static final int REQUIRE_SETTER = 2; - + /** * Helper method to create a new BeanMap. For finer * control over the generated instance, use a new instance of @@ -77,11 +77,11 @@ public static class Generator extends AbstractClassGenerator { interface BeanMapKey { public Object newInstance(Class type, int require); } - + private Object bean; private Class beanClass; private int require; - + public Generator() { super(SOURCE); } @@ -121,11 +121,13 @@ public void setRequire(int require) { this.require = require; } - protected ClassLoader getDefaultClassLoader() { + @Override + protected ClassLoader getDefaultClassLoader() { return beanClass.getClassLoader(); } - protected ProtectionDomain getProtectionDomain() { + @Override + protected ProtectionDomain getProtectionDomain() { return ReflectUtils.getProtectionDomain(beanClass); } @@ -134,21 +136,25 @@ protected ProtectionDomain getProtectionDomain() { * generated class will be reused if possible. */ public BeanMap create() { - if (beanClass == null) - throw new IllegalArgumentException("Class of bean unknown"); + if (beanClass == null) { + throw new IllegalArgumentException("Class of bean unknown"); + } setNamePrefix(beanClass.getName()); return (BeanMap)super.create(KEY_FACTORY.newInstance(beanClass, require)); } - public void generateClass(ClassVisitor v) throws Exception { + @Override + public void generateClass(ClassVisitor v) throws Exception { new BeanMapEmitter(v, getClassName(), beanClass, require); } - protected Object firstInstance(Class type) { + @Override + protected Object firstInstance(Class type) { return ((BeanMap)ReflectUtils.newInstance(type)).newInstance(bean); } - protected Object nextInstance(Object instance) { + @Override + protected Object nextInstance(Object instance) { return ((BeanMap)instance).newInstance(bean); } } @@ -177,11 +183,13 @@ protected BeanMap(Object bean) { setBean(bean); } - public Object get(Object key) { + @Override + public Object get(Object key) { return get(bean, key); } - public Object put(Object key, Object value) { + @Override + public Object put(Object key, Object value) { return put(bean, key, value); } @@ -223,52 +231,58 @@ public Object getBean() { return bean; } - public void clear() { + @Override + public void clear() { throw new UnsupportedOperationException(); } - public boolean containsKey(Object key) { + @Override + public boolean containsKey(Object key) { return keySet().contains(key); } - public boolean containsValue(Object value) { + @Override + public boolean containsValue(Object value) { for (Iterator it = keySet().iterator(); it.hasNext();) { Object v = get(it.next()); - if (((value == null) && (v == null)) || (value != null && value.equals(v))) - return true; + if (((value == null) && (v == null)) || (value != null && value.equals(v))) { + return true; + } } return false; } - public int size() { + @Override + public int size() { return keySet().size(); } - public boolean isEmpty() { + @Override + public boolean isEmpty() { return size() == 0; } - public Object remove(Object key) { + @Override + public Object remove(Object key) { throw new UnsupportedOperationException(); } - public void putAll(Map t) { - for (Iterator it = t.keySet().iterator(); it.hasNext();) { - Object key = it.next(); + @Override + public void putAll(Map t) { + for (Object key : t.keySet()) { put(key, t.get(key)); } } - public boolean equals(Object o) { - if (o == null || !(o instanceof Map)) { + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof Map other)) { return false; } - Map other = (Map)o; if (size() != other.size()) { return false; } - for (Iterator it = keySet().iterator(); it.hasNext();) { - Object key = it.next(); + for (Object key : keySet()) { if (!other.containsKey(key)) { return false; } @@ -281,10 +295,10 @@ public boolean equals(Object o) { return true; } - public int hashCode() { + @Override + public int hashCode() { int code = 0; - for (Iterator it = keySet().iterator(); it.hasNext();) { - Object key = it.next(); + for (Object key : keySet()) { Object value = get(key); code += ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode()); @@ -293,16 +307,17 @@ public int hashCode() { } // TODO: optimize - public Set entrySet() { + @Override + public Set entrySet() { HashMap copy = new HashMap(); - for (Iterator it = keySet().iterator(); it.hasNext();) { - Object key = it.next(); + for (Object key : keySet()) { copy.put(key, get(key)); } return Collections.unmodifiableMap(copy).entrySet(); } - public Collection values() { + @Override + public Collection values() { Set keys = keySet(); List values = new ArrayList(keys.size()); for (Iterator it = keys.iterator(); it.hasNext();) { @@ -314,7 +329,8 @@ public Collection values() { /* * @see java.util.AbstractMap#toString */ - public String toString() + @Override + public String toString() { StringBuilder sb = new StringBuilder(); sb.append('{'); diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/BeanMapEmitter.java b/spring-core/src/main/java/org/springframework/cglib/beans/BeanMapEmitter.java index 986aa02fd618..b1e3596f8ff5 100644 --- a/spring-core/src/main/java/org/springframework/cglib/beans/BeanMapEmitter.java +++ b/spring-core/src/main/java/org/springframework/cglib/beans/BeanMapEmitter.java @@ -15,12 +15,23 @@ */ package org.springframework.cglib.beans; -import java.beans.*; -import java.util.*; -import org.springframework.cglib.core.*; +import java.beans.PropertyDescriptor; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + import org.springframework.asm.ClassVisitor; import org.springframework.asm.Label; import org.springframework.asm.Type; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.ObjectSwitchCallback; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.TypeUtils; @SuppressWarnings({"rawtypes", "unchecked"}) class BeanMapEmitter extends ClassEmitter { @@ -50,7 +61,7 @@ public BeanMapEmitter(ClassVisitor v, String className, Class type, int require) EmitUtils.null_constructor(this); EmitUtils.factory_method(this, NEW_INSTANCE); generateConstructor(); - + Map getters = makePropertyMap(ReflectUtils.getBeanGetters(type)); Map setters = makePropertyMap(ReflectUtils.getBeanSetters(type)); Map allProps = new HashMap(); @@ -79,8 +90,8 @@ public BeanMapEmitter(ClassVisitor v, String className, Class type, int require) private Map makePropertyMap(PropertyDescriptor[] props) { Map names = new HashMap(); - for (int i = 0; i < props.length; i++) { - names.put(props[i].getName(), props[i]); + for (PropertyDescriptor prop : props) { + names.put(prop.getName(), prop); } return names; } @@ -97,7 +108,7 @@ private void generateConstructor() { e.return_value(); e.end_method(); } - + private void generateGet(Class type, final Map getters) { final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, BEAN_MAP_GET, null); e.load_arg(0); @@ -105,14 +116,16 @@ private void generateGet(Class type, final Map getters) { e.load_arg(1); e.checkcast(Constants.TYPE_STRING); EmitUtils.string_switch(e, getNames(getters), Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { - public void processCase(Object key, Label end) { + @Override + public void processCase(Object key, Label end) { PropertyDescriptor pd = (PropertyDescriptor)getters.get(key); MethodInfo method = ReflectUtils.getMethodInfo(pd.getReadMethod()); e.invoke(method); e.box(method.getSignature().getReturnType()); e.return_value(); } - public void processDefault() { + @Override + public void processDefault() { e.aconst_null(); e.return_value(); } @@ -127,7 +140,8 @@ private void generatePut(Class type, final Map setters) { e.load_arg(1); e.checkcast(Constants.TYPE_STRING); EmitUtils.string_switch(e, getNames(setters), Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { - public void processCase(Object key, Label end) { + @Override + public void processCase(Object key, Label end) { PropertyDescriptor pd = (PropertyDescriptor)setters.get(key); if (pd.getReadMethod() == null) { e.aconst_null(); @@ -144,7 +158,8 @@ public void processCase(Object key, Label end) { e.invoke(write); e.return_value(); } - public void processDefault() { + @Override + public void processDefault() { // fall-through } }); @@ -152,7 +167,7 @@ public void processDefault() { e.return_value(); e.end_method(); } - + private void generateKeySet(String[] allNames) { // static initializer declare_field(Constants.ACC_STATIC | Constants.ACC_PRIVATE, "keys", FIXED_KEY_SET, null); @@ -178,12 +193,14 @@ private void generateGetPropertyType(final Map allProps, String[] allNames) { final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, GET_PROPERTY_TYPE, null); e.load_arg(0); EmitUtils.string_switch(e, allNames, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { - public void processCase(Object key, Label end) { + @Override + public void processCase(Object key, Label end) { PropertyDescriptor pd = (PropertyDescriptor)allProps.get(key); EmitUtils.load_class(e, Type.getType(pd.getPropertyType())); e.return_value(); } - public void processDefault() { + @Override + public void processDefault() { e.aconst_null(); e.return_value(); } diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/BulkBean.java b/spring-core/src/main/java/org/springframework/cglib/beans/BulkBean.java index 7fdd702dbdce..582df3be5327 100644 --- a/spring-core/src/main/java/org/springframework/cglib/beans/BulkBean.java +++ b/spring-core/src/main/java/org/springframework/cglib/beans/BulkBean.java @@ -16,8 +16,11 @@ package org.springframework.cglib.beans; import java.security.ProtectionDomain; -import org.springframework.cglib.core.*; + import org.springframework.asm.ClassVisitor; +import org.springframework.cglib.core.AbstractClassGenerator; +import org.springframework.cglib.core.KeyFactory; +import org.springframework.cglib.core.ReflectUtils; /** * @author Juozas Baliuka @@ -27,17 +30,17 @@ abstract public class BulkBean { private static final BulkBeanKey KEY_FACTORY = (BulkBeanKey)KeyFactory.create(BulkBeanKey.class); - + interface BulkBeanKey { public Object newInstance(String target, String[] getters, String[] setters, String[] types); } - + protected Class target; protected String[] getters, setters; protected Class[] types; - + protected BulkBean() { } - + abstract public void getPropertyValues(Object bean, Object[] values); abstract public void setPropertyValues(Object bean, Object[] values); @@ -46,15 +49,15 @@ public Object[] getPropertyValues(Object bean) { getPropertyValues(bean, values); return values; } - + public Class[] getPropertyTypes() { return types.clone(); } - + public String[] getGetters() { return getters.clone(); } - + public String[] getSetters() { return setters.clone(); } @@ -98,11 +101,13 @@ public void setTypes(Class[] types) { this.types = types; } - protected ClassLoader getDefaultClassLoader() { + @Override + protected ClassLoader getDefaultClassLoader() { return target.getClassLoader(); } - protected ProtectionDomain getProtectionDomain() { + @Override + protected ProtectionDomain getProtectionDomain() { return ReflectUtils.getProtectionDomain(target); } @@ -114,28 +119,31 @@ public BulkBean create() { return (BulkBean)super.create(key); } - public void generateClass(ClassVisitor v) throws Exception { + @Override + public void generateClass(ClassVisitor v) throws Exception { new BulkBeanEmitter(v, getClassName(), target, getters, setters, types); } - protected Object firstInstance(Class type) { + @Override + protected Object firstInstance(Class type) { BulkBean instance = (BulkBean)ReflectUtils.newInstance(type); instance.target = target; - + int length = getters.length; instance.getters = new String[length]; System.arraycopy(getters, 0, instance.getters, 0, length); - + instance.setters = new String[length]; System.arraycopy(setters, 0, instance.setters, 0, length); - + instance.types = new Class[types.length]; System.arraycopy(types, 0, instance.types, 0, types.length); return instance; } - protected Object nextInstance(Object instance) { + @Override + protected Object nextInstance(Object instance) { return instance; } } diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/BulkBeanEmitter.java b/spring-core/src/main/java/org/springframework/cglib/beans/BulkBeanEmitter.java index 9502c3fa88a2..8a6198f4c519 100644 --- a/spring-core/src/main/java/org/springframework/cglib/beans/BulkBeanEmitter.java +++ b/spring-core/src/main/java/org/springframework/cglib/beans/BulkBeanEmitter.java @@ -17,9 +17,19 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import org.springframework.cglib.core.*; + import org.springframework.asm.ClassVisitor; import org.springframework.asm.Type; +import org.springframework.cglib.core.Block; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.Local; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.TypeUtils; @SuppressWarnings({"rawtypes", "unchecked"}) class BulkBeanEmitter extends ClassEmitter { @@ -33,7 +43,7 @@ class BulkBeanEmitter extends ClassEmitter { TypeUtils.parseType("org.springframework.cglib.beans.BulkBean"); private static final Type BULK_BEAN_EXCEPTION = TypeUtils.parseType("org.springframework.cglib.beans.BulkBeanException"); - + public BulkBeanEmitter(ClassVisitor v, String className, Class target, @@ -116,7 +126,7 @@ private void generateSet(final Class target, final Method[] setters) { } e.end_method(); } - + private static void validate(Class target, String[] getters, String[] setters, diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/BulkBeanException.java b/spring-core/src/main/java/org/springframework/cglib/beans/BulkBeanException.java index 20887f93832b..7325fdebee93 100644 --- a/spring-core/src/main/java/org/springframework/cglib/beans/BulkBeanException.java +++ b/spring-core/src/main/java/org/springframework/cglib/beans/BulkBeanException.java @@ -20,7 +20,7 @@ public class BulkBeanException extends RuntimeException { private int index; private Throwable cause; - + public BulkBeanException(String message, int index) { super(message); this.index = index; @@ -35,8 +35,9 @@ public BulkBeanException(Throwable cause, int index) { public int getIndex() { return index; } - - public Throwable getCause() { + + @Override + public Throwable getCause() { return cause; } } diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/FixedKeySet.java b/spring-core/src/main/java/org/springframework/cglib/beans/FixedKeySet.java index 399da30ee5b9..92b32efe8cb5 100644 --- a/spring-core/src/main/java/org/springframework/cglib/beans/FixedKeySet.java +++ b/spring-core/src/main/java/org/springframework/cglib/beans/FixedKeySet.java @@ -15,7 +15,12 @@ */ package org.springframework.cglib.beans; -import java.util.*; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; @SuppressWarnings({"rawtypes", "unchecked"}) public /* need it for class loading */ class FixedKeySet extends AbstractSet { @@ -27,11 +32,13 @@ public FixedKeySet(String[] keys) { set = Collections.unmodifiableSet(new HashSet(Arrays.asList(keys))); } - public Iterator iterator() { + @Override + public Iterator iterator() { return set.iterator(); } - public int size() { + @Override + public int size() { return size; } } diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/ImmutableBean.java b/spring-core/src/main/java/org/springframework/cglib/beans/ImmutableBean.java index dfc03594228a..aa62b7a43b29 100644 --- a/spring-core/src/main/java/org/springframework/cglib/beans/ImmutableBean.java +++ b/spring-core/src/main/java/org/springframework/cglib/beans/ImmutableBean.java @@ -18,9 +18,18 @@ import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.security.ProtectionDomain; -import org.springframework.cglib.core.*; + import org.springframework.asm.ClassVisitor; import org.springframework.asm.Type; +import org.springframework.cglib.core.AbstractClassGenerator; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.TypeUtils; /** * @author Chris Nokleberg */ @@ -60,11 +69,13 @@ public void setBean(Object bean) { // SPRING PATCH END } - protected ClassLoader getDefaultClassLoader() { + @Override + protected ClassLoader getDefaultClassLoader() { return target.getClassLoader(); } - protected ProtectionDomain getProtectionDomain() { + @Override + protected ProtectionDomain getProtectionDomain() { return ReflectUtils.getProtectionDomain(target); } @@ -74,7 +85,8 @@ public Object create() { return super.create(name); } - public void generateClass(ClassVisitor v) { + @Override + public void generateClass(ClassVisitor v) { Type targetType = Type.getType(target); ClassEmitter ce = new ClassEmitter(v); ce.begin_class(Constants.V1_8, @@ -100,8 +112,8 @@ public void generateClass(ClassVisitor v) { Method[] getters = ReflectUtils.getPropertyMethods(descriptors, true, false); Method[] setters = ReflectUtils.getPropertyMethods(descriptors, false, true); - for (int i = 0; i < getters.length; i++) { - MethodInfo getter = ReflectUtils.getMethodInfo(getters[i]); + for (Method getter2 : getters) { + MethodInfo getter = ReflectUtils.getMethodInfo(getter2); e = EmitUtils.begin_method(ce, getter, Constants.ACC_PUBLIC); e.load_this(); e.getfield(FIELD_NAME); @@ -110,8 +122,8 @@ public void generateClass(ClassVisitor v) { e.end_method(); } - for (int i = 0; i < setters.length; i++) { - MethodInfo setter = ReflectUtils.getMethodInfo(setters[i]); + for (Method setter2 : setters) { + MethodInfo setter = ReflectUtils.getMethodInfo(setter2); e = EmitUtils.begin_method(ce, setter, Constants.ACC_PUBLIC); e.throw_exception(ILLEGAL_STATE_EXCEPTION, "Bean is immutable"); e.end_method(); @@ -120,12 +132,14 @@ public void generateClass(ClassVisitor v) { ce.end_class(); } - protected Object firstInstance(Class type) { + @Override + protected Object firstInstance(Class type) { return ReflectUtils.newInstance(type, OBJECT_CLASSES, new Object[]{ bean }); } // TODO: optimize - protected Object nextInstance(Object instance) { + @Override + protected Object nextInstance(Object instance) { return firstInstance(instance.getClass()); } } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/AbstractClassGenerator.java b/spring-core/src/main/java/org/springframework/cglib/core/AbstractClassGenerator.java index 062027039730..9196cdf1b915 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/AbstractClassGenerator.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/AbstractClassGenerator.java @@ -38,11 +38,14 @@ abstract public class AbstractClassGenerator implements ClassGenerator { private static final ThreadLocal CURRENT = new ThreadLocal(); - private static volatile Map CACHE = new WeakHashMap(); + private static volatile Map CACHE = new WeakHashMap<>(); private static final boolean DEFAULT_USE_CACHE = Boolean.parseBoolean(System.getProperty("cglib.useCache", "true")); + // See https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java + private static final boolean imageCode = (System.getProperty("org.graalvm.nativeimage.imagecode") != null); + private GeneratorStrategy strategy = DefaultGeneratorStrategy.INSTANCE; @@ -67,7 +70,7 @@ abstract public class AbstractClassGenerator implements ClassGenerator { protected static class ClassLoaderData { - private final Set reservedClassNames = new HashSet(); + private final Set reservedClassNames = new HashSet<>(); /** * {@link AbstractClassGenerator} here holds "cache key" (e.g. {@link org.springframework.cglib.proxy.Enhancer} @@ -87,31 +90,20 @@ protected static class ClassLoaderData { */ private final WeakReference classLoader; - private final Predicate uniqueNamePredicate = new Predicate() { - public boolean evaluate(Object name) { - return reservedClassNames.contains(name); - } - }; + private final Predicate uniqueNamePredicate = this.reservedClassNames::contains; - private static final Function GET_KEY = new Function() { - public Object apply(AbstractClassGenerator gen) { - return gen.key; - } - }; + private static final Function GET_KEY = gen -> gen.key; public ClassLoaderData(ClassLoader classLoader) { if (classLoader == null) { throw new IllegalArgumentException("classLoader == null is not yet supported"); } - this.classLoader = new WeakReference(classLoader); - Function load = - new Function() { - public Object apply(AbstractClassGenerator gen) { - Class klass = gen.generate(ClassLoaderData.this); - return gen.wrapCachedClass(klass); - } - }; - generatedClasses = new LoadingCache(GET_KEY, load); + this.classLoader = new WeakReference<>(classLoader); + Function load = gen -> { + Class klass = gen.generate(ClassLoaderData.this); + return gen.wrapCachedClass(klass); + }; + generatedClasses = new LoadingCache<>(GET_KEY, load); } public ClassLoader getClassLoader() { @@ -202,8 +194,9 @@ public void setContextClass(Class contextClass) { * @see DefaultNamingPolicy */ public void setNamingPolicy(NamingPolicy namingPolicy) { - if (namingPolicy == null) + if (namingPolicy == null) { namingPolicy = DefaultNamingPolicy.INSTANCE; + } this.namingPolicy = namingPolicy; } @@ -247,8 +240,9 @@ public boolean getAttemptLoad() { * By default an instance of {@link DefaultGeneratorStrategy} is used. */ public void setStrategy(GeneratorStrategy strategy) { - if (strategy == null) + if (strategy == null) { strategy = DefaultGeneratorStrategy.INSTANCE; + } this.strategy = strategy; } @@ -308,7 +302,7 @@ protected Object create(Object key) { cache = CACHE; data = cache.get(loader); if (data == null) { - Map newCache = new WeakHashMap(cache); + Map newCache = new WeakHashMap<>(cache); data = new ClassLoaderData(loader); newCache.put(loader, data); CACHE = newCache; @@ -317,8 +311,8 @@ protected Object create(Object key) { } this.key = key; Object obj = data.get(this, getUseCache()); - if (obj instanceof Class) { - return firstInstance((Class) obj); + if (obj instanceof Class clazz) { + return firstInstance(clazz); } return nextInstance(obj); } @@ -359,6 +353,12 @@ protected Class generate(ClassLoaderData data) { // ignore } } + // SPRING PATCH BEGIN + if (imageCode) { + throw new UnsupportedOperationException("CGLIB runtime enhancement not supported on native image. " + + "Make sure to include a pre-generated class on the classpath instead: " + getClassName()); + } + // SPRING PATCH END byte[] b = strategy.generate(this); String className = ClassNameReader.getClassName(new ClassReader(b)); ProtectionDomain protectionDomain = getProtectionDomain(); diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Block.java b/spring-core/src/main/java/org/springframework/cglib/core/Block.java index f9837a12eb57..3e92ebe1db96 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/Block.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/Block.java @@ -38,7 +38,7 @@ public void end() { } end = e.mark(); } - + public Label getStart() { return start; } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ClassEmitter.java b/spring-core/src/main/java/org/springframework/cglib/core/ClassEmitter.java index dbe7f3dce4e1..6f91692e0405 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/ClassEmitter.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/ClassEmitter.java @@ -15,14 +15,14 @@ */ package org.springframework.cglib.core; +import java.util.HashMap; +import java.util.Map; + import org.springframework.asm.ClassVisitor; import org.springframework.asm.FieldVisitor; import org.springframework.asm.MethodVisitor; import org.springframework.asm.Type; -import java.util.HashMap; -import java.util.Map; - /** * @author Juozas Baliuka, Chris Nokleberg */ @@ -45,7 +45,8 @@ public ClassEmitter() { super(Constants.ASM_API); } - public void setTarget(ClassVisitor cv) { + @Override + public void setTarget(ClassVisitor cv) { this.cv = cv; fieldInfo = new HashMap(); @@ -65,16 +66,20 @@ public ClassInfo getClassInfo() { public void begin_class(int version, final int access, String className, final Type superType, final Type[] interfaces, String source) { final Type classType = Type.getType("L" + className.replace('.', '/') + ";"); classInfo = new ClassInfo() { - public Type getType() { + @Override + public Type getType() { return classType; } - public Type getSuperType() { + @Override + public Type getSuperType() { return (superType != null) ? superType : Constants.TYPE_OBJECT; } - public Type[] getInterfaces() { + @Override + public Type[] getInterfaces() { return interfaces; } - public int getModifiers() { + @Override + public int getModifiers() { return access; } }; @@ -84,8 +89,9 @@ public int getModifiers() { null, classInfo.getSuperType().getInternalName(), TypeUtils.toInternalNames(interfaces)); - if (source != null) - cv.visitSource(source, null); + if (source != null) { + cv.visitSource(source, null); + } init(); } @@ -137,8 +143,9 @@ public void end_class() { } public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { - if (classInfo == null) - throw new IllegalStateException("classInfo is null! " + this); + if (classInfo == null) { + throw new IllegalStateException("classInfo is null! " + this); + } MethodVisitor v = cv.visitMethod(access, sig.getName(), sig.getDescriptor(), @@ -147,10 +154,12 @@ public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { if (sig.equals(Constants.SIG_STATIC) && !TypeUtils.isInterface(getAccess())) { rawStaticInit = v; MethodVisitor wrapped = new MethodVisitor(Constants.ASM_API, v) { - public void visitMaxs(int maxStack, int maxLocals) { + @Override + public void visitMaxs(int maxStack, int maxLocals) { // ignore } - public void visitInsn(int insn) { + @Override + public void visitInsn(int insn) { if (insn != Constants.RETURN) { super.visitInsn(insn); } @@ -166,7 +175,8 @@ public void visitInsn(int insn) { return staticInit; } else if (sig.equals(staticHookSig)) { return new CodeEmitter(this, v, access, sig, exceptions) { - public boolean isStaticHook() { + @Override + public boolean isStaticHook() { return true; } }; @@ -204,13 +214,13 @@ FieldInfo getFieldInfo(String name) { } return field; } - + static class FieldInfo { int access; String name; Type type; Object value; - + public FieldInfo(int access, String name, Type type, Object value) { this.access = access; this.name = name; @@ -218,30 +228,36 @@ public FieldInfo(int access, String name, Type type, Object value) { this.value = value; } - public boolean equals(Object o) { - if (o == null) - return false; - if (!(o instanceof FieldInfo)) - return false; - FieldInfo other = (FieldInfo)o; + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (!(o instanceof FieldInfo other)) { + return false; + } if (access != other.access || !name.equals(other.name) || !type.equals(other.type)) { return false; } - if ((value == null) ^ (other.value == null)) - return false; - if (value != null && !value.equals(other.value)) - return false; + if ((value == null) ^ (other.value == null)) { + return false; + } + if (value != null && !value.equals(other.value)) { + return false; + } return true; } - public int hashCode() { + @Override + public int hashCode() { return access ^ name.hashCode() ^ type.hashCode() ^ ((value == null) ? 0 : value.hashCode()); } } - public void visit(int version, + @Override + public void visit(int version, int access, String name, String signature, @@ -254,12 +270,14 @@ public void visit(int version, TypeUtils.fromInternalNames(interfaces), null); // TODO } - - public void visitEnd() { + + @Override + public void visitEnd() { end_class(); } - - public FieldVisitor visitField(int access, + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, @@ -267,14 +285,15 @@ public FieldVisitor visitField(int access, declare_field(access, name, Type.getType(desc), value); return null; // TODO } - - public MethodVisitor visitMethod(int access, + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return begin_method(access, new Signature(name, desc), - TypeUtils.fromInternalNames(exceptions)); + TypeUtils.fromInternalNames(exceptions)); } } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ClassInfo.java b/spring-core/src/main/java/org/springframework/cglib/core/ClassInfo.java index afe16470ab14..856e025bc050 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/ClassInfo.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/ClassInfo.java @@ -28,14 +28,16 @@ protected ClassInfo() { abstract public Type[] getInterfaces(); abstract public int getModifiers(); - @Override - public boolean equals(Object o) { - if (o == null) - return false; - if (!(o instanceof ClassInfo)) - return false; - return getType().equals(((ClassInfo)o).getType()); - } + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (!(o instanceof ClassInfo classInfo)) { + return false; + } + return getType().equals(classInfo.getType()); + } @Override public int hashCode() { diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ClassNameReader.java b/spring-core/src/main/java/org/springframework/cglib/core/ClassNameReader.java index e930032cf394..4371fcdc3a4b 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/ClassNameReader.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/ClassNameReader.java @@ -15,51 +15,53 @@ */ package org.springframework.cglib.core; +import java.util.ArrayList; +import java.util.List; + import org.springframework.asm.ClassReader; import org.springframework.asm.ClassVisitor; -import java.util.*; - // TODO: optimize (ClassReader buffers entire class before accept) @SuppressWarnings({"rawtypes", "unchecked"}) public class ClassNameReader { - private ClassNameReader() { - } - private static final EarlyExitException EARLY_EXIT = new EarlyExitException(); + private ClassNameReader() { + } + + private static final EarlyExitException EARLY_EXIT = new EarlyExitException(); @SuppressWarnings("serial") - private static class EarlyExitException extends RuntimeException { } - - public static String getClassName(ClassReader r) { - - return getClassInfo(r)[0]; - - } - + private static class EarlyExitException extends RuntimeException { + } + + public static String getClassName(ClassReader r) { + return getClassInfo(r)[0]; + } + public static String[] getClassInfo(ClassReader r) { - final List array = new ArrayList(); + final List array = new ArrayList<>(); try { r.accept(new ClassVisitor(Constants.ASM_API, null) { + @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - array.add( name.replace('/', '.') ); - if(superName != null){ - array.add( superName.replace('/', '.') ); + array.add(name.replace('/', '.')); + if (superName != null) { + array.add(superName.replace('/', '.')); } - for(int i = 0; i < interfaces.length; i++ ){ - array.add( interfaces[i].replace('/', '.') ); + for (String element : interfaces) { + array.add(element.replace('/', '.')); } - + throw EARLY_EXIT; } }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); } catch (EarlyExitException e) { } - - return (String[])array.toArray( new String[]{} ); + + return array.toArray(new String[0]); } } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ClassesKey.java b/spring-core/src/main/java/org/springframework/cglib/core/ClassesKey.java index 64f7239ece27..338a2c1a6f27 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/ClassesKey.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/ClassesKey.java @@ -17,7 +17,7 @@ public class ClassesKey { private static final Key FACTORY = (Key)KeyFactory.create(Key.class); - + interface Key { Object newInstance(Object[] array); } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/CodeEmitter.java b/spring-core/src/main/java/org/springframework/cglib/core/CodeEmitter.java index 68c5336abe7d..a7080055a633 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/CodeEmitter.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/CodeEmitter.java @@ -613,8 +613,9 @@ public void process_switch(int[] keys, ProcessSwitchCallback callback) { } public void process_switch(int[] keys, ProcessSwitchCallback callback, boolean useTable) { - if (!isSorted(keys)) - throw new IllegalArgumentException("keys to switch must be sorted ascending"); + if (!isSorted(keys)) { + throw new IllegalArgumentException("keys to switch must be sorted ascending"); + } Label def = make_label(); Label end = make_label(); @@ -656,9 +657,7 @@ public void process_switch(int[] keys, ProcessSwitchCallback callback, boolean u callback.processDefault(); mark(end); - } catch (RuntimeException e) { - throw e; - } catch (Error e) { + } catch (RuntimeException | Error e) { throw e; } catch (Exception e) { throw new CodeGenerationException(e); @@ -667,8 +666,9 @@ public void process_switch(int[] keys, ProcessSwitchCallback callback, boolean u private static boolean isSorted(int[] keys) { for (int i = 1; i < keys.length; i++) { - if (keys[i] < keys[i - 1]) - return false; + if (keys[i] < keys[i - 1]) { + return false; + } } return true; } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/CodeGenerationException.java b/spring-core/src/main/java/org/springframework/cglib/core/CodeGenerationException.java index 48b3495a50ae..ccbd34fb83ab 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/CodeGenerationException.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/CodeGenerationException.java @@ -27,7 +27,8 @@ public CodeGenerationException(Throwable cause) { this.cause = cause; } - public Throwable getCause() { + @Override + public Throwable getCause() { return cause; } } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/CollectionUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/CollectionUtils.java index 49fe4f174c0e..1590cad07797 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/CollectionUtils.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/CollectionUtils.java @@ -34,8 +34,7 @@ private CollectionUtils() { } public static Map bucket(Collection c, Transformer t) { Map buckets = new HashMap(); - for (Iterator it = c.iterator(); it.hasNext();) { - Object value = it.next(); + for (Object value : c) { Object key = t.transform(value); List bucket = (List)buckets.get(key); if (bucket == null) { @@ -47,8 +46,7 @@ public static Map bucket(Collection c, Transformer t) { } public static void reverse(Map source, Map target) { - for (Iterator it = source.keySet().iterator(); it.hasNext();) { - Object key = it.next(); + for (Object key : source.keySet()) { target.put(source.get(key), key); } } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Constants.java b/spring-core/src/main/java/org/springframework/cglib/core/Constants.java index 1fa8e2e3ccd7..577387a118a2 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/Constants.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/Constants.java @@ -32,7 +32,7 @@ public interface Constants extends org.springframework.asm.Opcodes { public static final Signature SIG_STATIC = TypeUtils.parseSignature("void ()"); - + public static final Type TYPE_OBJECT_ARRAY = TypeUtils.parseType("Object[]"); public static final Type TYPE_CLASS_ARRAY = TypeUtils.parseType("Class[]"); public static final Type TYPE_STRING_ARRAY = TypeUtils.parseType("String[]"); diff --git a/spring-core/src/main/java/org/springframework/cglib/core/DefaultGeneratorStrategy.java b/spring-core/src/main/java/org/springframework/cglib/core/DefaultGeneratorStrategy.java index 27607cf74b2f..ad72b4450b3c 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/DefaultGeneratorStrategy.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/DefaultGeneratorStrategy.java @@ -19,8 +19,9 @@ public class DefaultGeneratorStrategy implements GeneratorStrategy { public static final DefaultGeneratorStrategy INSTANCE = new DefaultGeneratorStrategy(); - - public byte[] generate(ClassGenerator cg) throws Exception { + + @Override + public byte[] generate(ClassGenerator cg) throws Exception { DebuggingClassWriter cw = getClassVisitor(); transform(cg).generateClass(cw); return transform(cw.toByteArray()); @@ -36,7 +37,7 @@ protected final ClassWriter getClassWriter() { throw new UnsupportedOperationException("You are calling " + "getClassWriter, which no longer exists in this cglib version."); } - + protected byte[] transform(byte[] b) throws Exception { return b; } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/DefaultNamingPolicy.java b/spring-core/src/main/java/org/springframework/cglib/core/DefaultNamingPolicy.java index 0efb6201274b..9020e490abd6 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/DefaultNamingPolicy.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/DefaultNamingPolicy.java @@ -48,8 +48,9 @@ public String getClassName(String prefix, String source, Object key, Predicate n Integer.toHexString(STRESS_HASH_CODE ? 0 : key.hashCode()); String attempt = base; int index = 2; - while (names.evaluate(attempt)) - attempt = base + "_" + index++; + while (names.evaluate(attempt)) { + attempt = base + "_" + index++; + } return attempt; } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/DuplicatesPredicate.java b/spring-core/src/main/java/org/springframework/cglib/core/DuplicatesPredicate.java index a12cf1ce62f5..23d843411fed 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/DuplicatesPredicate.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/DuplicatesPredicate.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cglib.core; import java.io.IOException; @@ -24,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Set; + import org.springframework.asm.ClassReader; import org.springframework.asm.ClassVisitor; import org.springframework.asm.MethodVisitor; @@ -45,7 +47,7 @@ public DuplicatesPredicate() { /** * Constructs a DuplicatesPredicate that prefers using superclass non-bridge methods despite a - * subclass method with the same signtaure existing (if the subclass is a bridge method). + * subclass method with the same signature existing (if the subclass is a bridge method). */ public DuplicatesPredicate(List allMethods) { rejected = new HashSet(); @@ -70,7 +72,7 @@ public DuplicatesPredicate(List allMethods) { suspects.put(sig, existing); } } - + if (!suspects.isEmpty()) { Set classes = new HashSet(); UnnecessaryBridgeFinder finder = new UnnecessaryBridgeFinder(rejected); @@ -101,10 +103,11 @@ public DuplicatesPredicate(List allMethods) { } } + @Override public boolean evaluate(Object arg) { return !rejected.contains(arg) && unique.add(MethodWrapper.create((Method) arg)); } - + private static ClassLoader getClassLoader(Class c) { ClassLoader cl = c.getClassLoader(); if (cl == null) { @@ -131,6 +134,7 @@ void addSuspectMethod(Method m) { methods.put(ReflectUtils.getSignature(m), m); } + @Override public void visit( int version, int access, @@ -139,6 +143,7 @@ public void visit( String superName, String[] interfaces) {} + @Override public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { Signature sig = new Signature(name, desc); @@ -146,6 +151,7 @@ public MethodVisitor visitMethod( if (currentMethod != null) { currentMethodSig = sig; return new MethodVisitor(Constants.ASM_API) { + @Override public void visitMethodInsn( int opcode, String owner, String name, String desc, boolean itf) { if (opcode == Opcodes.INVOKESPECIAL && currentMethodSig != null) { diff --git a/spring-core/src/main/java/org/springframework/cglib/core/EmitUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/EmitUtils.java index 36522a160278..822f38d5e8ba 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/EmitUtils.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/EmitUtils.java @@ -187,9 +187,7 @@ public static void string_switch(CodeEmitter e, String[] strings, int switchStyl default: throw new IllegalArgumentException("unknown switch style " + switchStyle); } - } catch (RuntimeException ex) { - throw ex; - } catch (Error ex) { + } catch (RuntimeException | Error ex) { throw ex; } catch (Exception ex) { throw new CodeGenerationException(ex); @@ -201,12 +199,7 @@ private static void string_switch_trie(final CodeEmitter e, final ObjectSwitchCallback callback) throws Exception { final Label def = e.make_label(); final Label end = e.make_label(); - final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), new Transformer() { - @Override - public Object transform(Object value) { - return ((String)value).length(); - } - }); + final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), value -> ((String)value).length()); e.dup(); e.invoke_virtual(Constants.TYPE_STRING, STRING_LENGTH); e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() { @@ -233,12 +226,7 @@ private static void stringSwitchHelper(final CodeEmitter e, final Label end, final int index) throws Exception { final int len = ((String)strings.get(0)).length(); - final Map buckets = CollectionUtils.bucket(strings, new Transformer() { - @Override - public Object transform(Object value) { - return ((String)value).charAt(index); - } - }); + final Map buckets = CollectionUtils.bucket(strings, value -> ((String)value).charAt(index)); e.dup(); e.push(index); e.invoke_virtual(Constants.TYPE_STRING, STRING_CHAR_AT); @@ -274,12 +262,7 @@ private static void string_switch_hash(final CodeEmitter e, final String[] strings, final ObjectSwitchCallback callback, final boolean skipEquals) throws Exception { - final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), new Transformer() { - @Override - public Object transform(Object value) { - return value.hashCode(); - } - }); + final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), value -> value.hashCode()); final Label def = e.make_label(); final Label end = e.make_label(); e.dup(); @@ -290,8 +273,9 @@ public void processCase(int key, Label ignore_end) throws Exception { List bucket = (List)buckets.get(key); Label next = null; if (skipEquals && bucket.size() == 1) { - if (skipEquals) - e.pop(); + if (skipEquals) { + e.pop(); + } callback.processCase(bucket.get(0), end); } else { for (Iterator it = bucket.iterator(); it.hasNext();) { @@ -373,8 +357,9 @@ public static void push_array(CodeEmitter e, Object[] array) { } private static Class remapComponentType(Class componentType) { - if (componentType.equals(Type.class)) - return Class.class; + if (componentType.equals(Type.class)) { + return Class.class; + } return componentType; } @@ -437,12 +422,7 @@ private static void hash_array(final CodeEmitter e, Type type, final int multipl Label end = e.make_label(); e.dup(); e.ifnull(skip); - EmitUtils.process_array(e, type, new ProcessArrayCallback() { - @Override - public void processElement(Type type) { - hash_code(e, type, multiplier, registry); - } - }); + EmitUtils.process_array(e, type, type1 -> hash_code(e, type1, multiplier, registry)); e.goTo(end); e.mark(skip); e.pop(); @@ -752,26 +732,18 @@ private static void member_switch_helper(final CodeEmitter e, boolean useName) { try { final Map cache = new HashMap(); - final ParameterTyper cached = new ParameterTyper() { - @Override - public Type[] getParameterTypes(MethodInfo member) { - Type[] types = (Type[])cache.get(member); - if (types == null) { - cache.put(member, types = member.getSignature().getArgumentTypes()); - } - return types; - } - }; + final ParameterTyper cached = member -> { + Type[] types = (Type[]) cache.get(member); + if (types == null) { + cache.put(member, types = member.getSignature().getArgumentTypes()); + } + return types; + }; final Label def = e.make_label(); final Label end = e.make_label(); if (useName) { e.swap(); - final Map buckets = CollectionUtils.bucket(members, new Transformer() { - @Override - public Object transform(Object value) { - return ((MethodInfo)value).getSignature().getName(); - } - }); + final Map buckets = CollectionUtils.bucket(members, value -> ((MethodInfo)value).getSignature().getName()); String[] names = (String[])buckets.keySet().toArray(new String[buckets.size()]); EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { @Override @@ -790,9 +762,7 @@ public void processDefault() throws Exception { e.pop(); callback.processDefault(); e.mark(end); - } catch (RuntimeException ex) { - throw ex; - } catch (Error ex) { + } catch (RuntimeException | Error ex) { throw ex; } catch (Exception ex) { throw new CodeGenerationException(ex); @@ -805,12 +775,7 @@ private static void member_helper_size(final CodeEmitter e, final ParameterTyper typer, final Label def, final Label end) throws Exception { - final Map buckets = CollectionUtils.bucket(members, new Transformer() { - @Override - public Object transform(Object value) { - return typer.getParameterTypes((MethodInfo)value).length; - } - }); + final Map buckets = CollectionUtils.bucket(members, value -> typer.getParameterTypes((MethodInfo)value).length); e.dup(); e.arraylength(); e.process_switch(EmitUtils.getSwitchKeys(buckets), new ProcessSwitchCallback() { @@ -856,12 +821,8 @@ private static void member_helper_type(final CodeEmitter e, int index = -1; for (int i = 0; i < example.length; i++) { final int j = i; - Map test = CollectionUtils.bucket(members, new Transformer() { - @Override - public Object transform(Object value) { - return TypeUtils.emulateClassGetName(typer.getParameterTypes((MethodInfo)value)[j]); - } - }); + Map test = CollectionUtils.bucket(members, + value -> TypeUtils.emulateClassGetName(typer.getParameterTypes((MethodInfo)value)[j])); if (buckets == null || test.size() > buckets.size()) { buckets = test; index = i; @@ -951,8 +912,9 @@ public static void add_property(ClassEmitter ce, String name, Type type, String public static void wrap_undeclared_throwable(CodeEmitter e, Block handler, Type[] exceptions, Type wrapper) { Set set = (exceptions == null) ? Collections.EMPTY_SET : new HashSet(Arrays.asList(exceptions)); - if (set.contains(Constants.TYPE_THROWABLE)) - return; + if (set.contains(Constants.TYPE_THROWABLE)) { + return; + } boolean needThrow = exceptions != null; if (!set.contains(Constants.TYPE_RUNTIME_EXCEPTION)) { @@ -964,8 +926,8 @@ public static void wrap_undeclared_throwable(CodeEmitter e, Block handler, Type[ needThrow = true; } if (exceptions != null) { - for (int i = 0; i < exceptions.length; i++) { - e.catch_exception(handler, exceptions[i]); + for (Type exception : exceptions) { + e.catch_exception(handler, exception); } } if (needThrow) { diff --git a/spring-core/src/main/java/org/springframework/cglib/core/GeneratorStrategy.java b/spring-core/src/main/java/org/springframework/cglib/core/GeneratorStrategy.java index c345c4449daf..050757b7bc2a 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/GeneratorStrategy.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/GeneratorStrategy.java @@ -40,5 +40,6 @@ public interface GeneratorStrategy { * correctly implement equals and hashCode * to avoid generating too many classes. */ - boolean equals(Object o); + @Override + boolean equals(Object o); } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/KeyFactory.java b/spring-core/src/main/java/org/springframework/cglib/core/KeyFactory.java index e1503b85c13b..2287cb6144e9 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/KeyFactory.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/KeyFactory.java @@ -98,20 +98,20 @@ abstract public class KeyFactory { 938313161, 1288102441, 1768288259}; - public static final Customizer CLASS_BY_NAME = new Customizer() { - public void customize(CodeEmitter e, Type type) { - if (type.equals(Constants.TYPE_CLASS)) { - e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME); - } + public static final Customizer CLASS_BY_NAME = (e, type) -> { + if (type.equals(Constants.TYPE_CLASS)) { + e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME); } }; public static final FieldTypeCustomizer STORE_CLASS_AS_STRING = new FieldTypeCustomizer() { + @Override public void customize(CodeEmitter e, int index, Type type) { if (type.equals(Constants.TYPE_CLASS)) { e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME); } } + @Override public Type getOutType(int index, Type type) { if (type.equals(Constants.TYPE_CLASS)) { return Constants.TYPE_STRING; @@ -124,14 +124,12 @@ public Type getOutType(int index, Type type) { * {@link Type#hashCode()} is very expensive as it traverses full descriptor to calculate hash code. * This customizer uses {@link Type#getSort()} as a hash code. */ - public static final HashCodeCustomizer HASH_ASM_TYPE = new HashCodeCustomizer() { - public boolean customize(CodeEmitter e, Type type) { - if (Constants.TYPE_TYPE.equals(type)) { - e.invoke_virtual(type, GET_SORT); - return true; - } - return false; + public static final HashCodeCustomizer HASH_ASM_TYPE = (e, type) -> { + if (Constants.TYPE_TYPE.equals(type)) { + e.invoke_virtual(type, GET_SORT); + return true; } + return false; }; /** @@ -139,11 +137,7 @@ public boolean customize(CodeEmitter e, Type type) { * It is recommended to have pre-processing method that would strip Objects and represent Classes as Strings */ @Deprecated - public static final Customizer OBJECT_BY_CLASS = new Customizer() { - public void customize(CodeEmitter e, Type type) { - e.invoke_virtual(Constants.TYPE_OBJECT, GET_CLASS); - } - }; + public static final Customizer OBJECT_BY_CLASS = (e, type) -> e.invoke_virtual(Constants.TYPE_OBJECT, GET_CLASS); protected KeyFactory() { } @@ -204,10 +198,12 @@ public Generator() { super(SOURCE); } + @Override protected ClassLoader getDefaultClassLoader() { return keyInterface.getClassLoader(); } + @Override protected ProtectionDomain getProtectionDomain() { return ReflectUtils.getProtectionDomain(keyInterface); } @@ -245,14 +241,17 @@ public void setHashMultiplier(int multiplier) { this.multiplier = multiplier; } + @Override protected Object firstInstance(Class type) { return ReflectUtils.newInstance(type); } + @Override protected Object nextInstance(Object instance) { return instance; } + @Override public void generateClass(ClassVisitor v) { ClassEmitter ce = new ClassEmitter(v); diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Local.java b/spring-core/src/main/java/org/springframework/cglib/core/Local.java index 93c2d22f35f5..563888729a5b 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/Local.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/Local.java @@ -21,12 +21,12 @@ public class Local { private Type type; private int index; - + public Local(int index, Type type) { this.type = type; this.index = index; } - + public int getIndex() { return index; } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/LocalVariablesSorter.java b/spring-core/src/main/java/org/springframework/cglib/core/LocalVariablesSorter.java index d3cdd9d548b7..7b72028812a9 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/LocalVariablesSorter.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/LocalVariablesSorter.java @@ -38,7 +38,7 @@ * A {@link MethodVisitor} that renumbers local variables in their order of * appearance. This adapter allows one to easily add new local variables to a * method. - * + * * @author Chris Nokleberg * @author Eric Bruneton */ @@ -51,7 +51,7 @@ public class LocalVariablesSorter extends MethodVisitor { */ private static class State { - int[] mapping = new int[40]; + int[] mapping = new int[40]; int nextLocal; } @@ -67,8 +67,8 @@ public LocalVariablesSorter( state = new State(); Type[] args = Type.getArgumentTypes(desc); state.nextLocal = ((Opcodes.ACC_STATIC & access) != 0) ? 0 : 1; - for (int i = 0; i < args.length; i++) { - state.nextLocal += args[i].getSize(); + for (Type arg : args) { + state.nextLocal += arg.getSize(); } firstLocal = state.nextLocal; } @@ -79,30 +79,27 @@ public LocalVariablesSorter(LocalVariablesSorter lvs) { firstLocal = lvs.firstLocal; } - public void visitVarInsn(final int opcode, final int var) { - int size; - switch (opcode) { - case Opcodes.LLOAD: - case Opcodes.LSTORE: - case Opcodes.DLOAD: - case Opcodes.DSTORE: - size = 2; - break; - default: - size = 1; - } + @Override + public void visitVarInsn(final int opcode, final int var) { + int size = switch (opcode) { + case Opcodes.LLOAD, Opcodes.LSTORE, Opcodes.DLOAD, Opcodes.DSTORE -> 2; + default -> 1; + }; mv.visitVarInsn(opcode, remap(var, size)); } - public void visitIincInsn(final int var, final int increment) { + @Override + public void visitIincInsn(final int var, final int increment) { mv.visitIincInsn(remap(var, 1), increment); } - public void visitMaxs(final int maxStack, final int maxLocals) { + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { mv.visitMaxs(maxStack, state.nextLocal); } - public void visitLocalVariable( + @Override + public void visitLocalVariable( final String name, final String desc, final String signature, diff --git a/spring-core/src/main/java/org/springframework/cglib/core/MethodInfo.java b/spring-core/src/main/java/org/springframework/cglib/core/MethodInfo.java index 89b48bdad2fb..41afcd701cfb 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/MethodInfo.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/MethodInfo.java @@ -20,31 +20,34 @@ abstract public class MethodInfo { - protected MethodInfo() { - } - - abstract public ClassInfo getClassInfo(); - abstract public int getModifiers(); - abstract public Signature getSignature(); - abstract public Type[] getExceptionTypes(); - - @Override - public boolean equals(Object o) { - if (o == null) - return false; - if (!(o instanceof MethodInfo)) - return false; - return getSignature().equals(((MethodInfo)o).getSignature()); - } - - @Override - public int hashCode() { - return getSignature().hashCode(); - } - - @Override - public String toString() { - // TODO: include modifiers, exceptions - return getSignature().toString(); - } + protected MethodInfo() { + } + + abstract public ClassInfo getClassInfo(); + abstract public int getModifiers(); + abstract public Signature getSignature(); + abstract public Type[] getExceptionTypes(); + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (!(o instanceof MethodInfo other)) { + return false; + } + return getSignature().equals(other.getSignature()); + } + + @Override + public int hashCode() { + return getSignature().hashCode(); + } + + @Override + public String toString() { + // TODO: include modifiers, exceptions + return getSignature().toString(); + } + } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/MethodWrapper.java b/spring-core/src/main/java/org/springframework/cglib/core/MethodWrapper.java index 119ffc48c61e..fd90500abe93 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/MethodWrapper.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/MethodWrapper.java @@ -16,7 +16,12 @@ package org.springframework.cglib.core; import java.lang.reflect.Method; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; @SuppressWarnings({"rawtypes", "unchecked"}) public class MethodWrapper { diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Predicate.java b/spring-core/src/main/java/org/springframework/cglib/core/Predicate.java index dad9ae37b930..8d1605b13366 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/Predicate.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/Predicate.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cglib.core; +@FunctionalInterface public interface Predicate { - boolean evaluate(Object arg); + boolean evaluate(Object arg); } - diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java index 4f3f2bac8dd9..3efb7663c3b1 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java @@ -60,7 +60,7 @@ private ReflectUtils() { private static final ProtectionDomain PROTECTION_DOMAIN; - private static final List OBJECT_METHODS = new ArrayList(); + private static final List OBJECT_METHODS = new ArrayList<>(); private static BiConsumer generatedClassHandler; @@ -125,11 +125,11 @@ public static ProtectionDomain getProtectionDomain(final Class source) { } public static Type[] getExceptionTypes(Member member) { - if (member instanceof Method) { - return TypeUtils.getTypes(((Method) member).getExceptionTypes()); + if (member instanceof Method method) { + return TypeUtils.getTypes(method.getExceptionTypes()); } - else if (member instanceof Constructor) { - return TypeUtils.getTypes(((Constructor) member).getExceptionTypes()); + else if (member instanceof Constructor constructor) { + return TypeUtils.getTypes(constructor.getExceptionTypes()); } else { throw new IllegalArgumentException("Cannot get exception types of a field"); @@ -137,14 +137,13 @@ else if (member instanceof Constructor) { } public static Signature getSignature(Member member) { - if (member instanceof Method) { - return new Signature(member.getName(), Type.getMethodDescriptor((Method) member)); + if (member instanceof Method method) { + return new Signature(member.getName(), Type.getMethodDescriptor(method)); } - else if (member instanceof Constructor) { - Type[] types = TypeUtils.getTypes(((Constructor) member).getParameterTypes()); + else if (member instanceof Constructor constructor) { + Type[] types = TypeUtils.getTypes(constructor.getParameterTypes()); return new Signature(Constants.CONSTRUCTOR_NAME, Type.getMethodDescriptor(Type.VOID_TYPE, types)); - } else { throw new IllegalArgumentException("Cannot get signature of a field"); @@ -230,9 +229,9 @@ private static Class getClass(String className, ClassLoader loader, String[] pac } catch (ClassNotFoundException ignore) { } - for (int i = 0; i < packages.length; i++) { + for (String pkg : packages) { try { - return Class.forName(prefix + packages[i] + '.' + className + suffix, false, loader); + return Class.forName(prefix + pkg + '.' + className + suffix, false, loader); } catch (ClassNotFoundException ignore) { } @@ -274,10 +273,7 @@ public static Object newInstance(final Constructor cstruct, final Object[] args) Object result = cstruct.newInstance(args); return result; } - catch (InstantiationException e) { - throw new CodeGenerationException(e); - } - catch (IllegalAccessException e) { + catch (InstantiationException | IllegalAccessException e) { throw new CodeGenerationException(e); } catch (InvocationTargetException e) { @@ -302,8 +298,9 @@ public static Constructor getConstructor(Class type, Class[] parameterTypes) { } public static String[] getNames(Class[] classes) { - if (classes == null) + if (classes == null) { return null; + } String[] names = new String[classes.length]; for (int i = 0; i < names.length; i++) { names[i] = classes[i].getName(); @@ -329,8 +326,7 @@ public static Method findNewInstance(Class iface) { public static Method[] getPropertyMethods(PropertyDescriptor[] properties, boolean read, boolean write) { Set methods = new HashSet(); - for (int i = 0; i < properties.length; i++) { - PropertyDescriptor pd = properties[i]; + for (PropertyDescriptor pd : properties) { if (read) { methods.add(pd.getReadMethod()); } @@ -362,8 +358,7 @@ private static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read return all; } List properties = new ArrayList(all.length); - for (int i = 0; i < all.length; i++) { - PropertyDescriptor pd = all[i]; + for (PropertyDescriptor pd : all) { if ((read && pd.getReadMethod() != null) || (write && pd.getWriteMethod() != null)) { properties.add(pd); @@ -396,16 +391,17 @@ public static List addAllMethods(final Class type, final List list) { if (type == Object.class) { list.addAll(OBJECT_METHODS); } - else + else { list.addAll(java.util.Arrays.asList(type.getDeclaredMethods())); + } Class superclass = type.getSuperclass(); if (superclass != null) { addAllMethods(superclass, list); } Class[] interfaces = type.getInterfaces(); - for (int i = 0; i < interfaces.length; i++) { - addAllMethods(interfaces[i], list); + for (Class element : interfaces) { + addAllMethods(element, list); } return list; @@ -584,20 +580,25 @@ public static MethodInfo getMethodInfo(final Member member, final int modifiers) return new MethodInfo() { private ClassInfo ci; + @Override public ClassInfo getClassInfo() { - if (ci == null) + if (ci == null) { ci = ReflectUtils.getClassInfo(member.getDeclaringClass()); + } return ci; } + @Override public int getModifiers() { return modifiers; } + @Override public Signature getSignature() { return sig; } + @Override public Type[] getExceptionTypes() { return ReflectUtils.getExceptionTypes(member); } @@ -612,15 +613,19 @@ public static ClassInfo getClassInfo(final Class clazz) { final Type type = Type.getType(clazz); final Type sc = (clazz.getSuperclass() == null) ? null : Type.getType(clazz.getSuperclass()); return new ClassInfo() { + @Override public Type getType() { return type; } + @Override public Type getSuperType() { return sc; } + @Override public Type[] getInterfaces() { return TypeUtils.getTypes(clazz.getInterfaces()); } + @Override public int getModifiers() { return clazz.getModifiers(); } @@ -630,8 +635,7 @@ public int getModifiers() { // used by MethodInterceptorGenerated generated code public static Method[] findMethods(String[] namesAndDescriptors, Method[] methods) { Map map = new HashMap(); - for (int i = 0; i < methods.length; i++) { - Method method = methods[i]; + for (Method method : methods) { map.put(method.getName() + Type.getMethodDescriptor(method), method); } Method[] result = new Method[namesAndDescriptors.length / 2]; diff --git a/spring-core/src/main/java/org/springframework/cglib/core/RejectModifierPredicate.java b/spring-core/src/main/java/org/springframework/cglib/core/RejectModifierPredicate.java index 5bcdb3ae00b0..4e24453a83e6 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/RejectModifierPredicate.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/RejectModifierPredicate.java @@ -15,7 +15,7 @@ */ package org.springframework.cglib.core; -import java.lang.reflect.*; +import java.lang.reflect.Member; public class RejectModifierPredicate implements Predicate { private int rejectMask; @@ -24,7 +24,8 @@ public RejectModifierPredicate(int rejectMask) { this.rejectMask = rejectMask; } - public boolean evaluate(Object arg) { + @Override + public boolean evaluate(Object arg) { return (((Member)arg).getModifiers() & rejectMask) == 0; } } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Signature.java b/spring-core/src/main/java/org/springframework/cglib/core/Signature.java index ab040ad69c06..421f751d153f 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/Signature.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/Signature.java @@ -54,20 +54,24 @@ public Type[] getArgumentTypes() { return Type.getArgumentTypes(desc); } - public String toString() { + @Override + public String toString() { return name + desc; } - public boolean equals(Object o) { - if (o == null) - return false; - if (!(o instanceof Signature)) - return false; - Signature other = (Signature)o; + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (!(o instanceof Signature other)) { + return false; + } return name.equals(other.name) && desc.equals(other.desc); } - public int hashCode() { + @Override + public int hashCode() { return name.hashCode() ^ desc.hashCode(); } } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java b/spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java index ed91f3a2d9e7..7eb96dd70178 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java @@ -36,6 +36,7 @@ public final class SpringNamingPolicy implements NamingPolicy { private SpringNamingPolicy() { } + @Override public String getClassName(String prefix, String source, Object key, Predicate names) { if (prefix == null) { prefix = "org.springframework.cglib.empty.Object"; diff --git a/spring-core/src/main/java/org/springframework/cglib/core/TypeUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/TypeUtils.java index 5ff8a3bfecee..5d1694beda4f 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/TypeUtils.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/TypeUtils.java @@ -15,7 +15,13 @@ */ package org.springframework.cglib.core; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + import org.springframework.asm.Type; @SuppressWarnings({"rawtypes", "unchecked"}) @@ -63,7 +69,7 @@ public static boolean isPublic(int access) { public static boolean isAbstract(int access) { return (Constants.ACC_ABSTRACT & access) != 0; } - + public static boolean isInterface(int access) { return (Constants.ACC_INTERFACE & access) != 0; } @@ -71,15 +77,15 @@ public static boolean isInterface(int access) { public static boolean isPrivate(int access) { return (Constants.ACC_PRIVATE & access) != 0; } - + public static boolean isSynthetic(int access) { return (Constants.ACC_SYNTHETIC & access) != 0; } - + public static boolean isBridge(int access) { return (Constants.ACC_BRIDGE & access) != 0; } - + // getPackage returns null on JDK 1.2 public static String getPackageName(Type type) { return getPackageName(getClassName(type)); @@ -89,7 +95,7 @@ public static String getPackageName(String className) { int idx = className.lastIndexOf('.'); return (idx < 0) ? "" : className.substring(0, idx); } - + public static String upperFirst(String s) { if (s == null || s.length() == 0) { return s; @@ -148,8 +154,8 @@ public static Type[] fromInternalNames(String[] names) { public static int getStackSize(Type[] types) { int size = 0; - for (int i = 0; i < types.length; i++) { - size += types[i].getSize(); + for (Type type : types) { + size += type.getSize(); } return size; } @@ -197,8 +203,8 @@ public static Type[] parseTypes(String s) { public static Signature parseConstructor(Type[] types) { StringBuilder sb = new StringBuilder(); sb.append("("); - for (int i = 0; i < types.length; i++) { - sb.append(types[i].getDescriptor()); + for (Type type : types) { + sb.append(type.getDescriptor()); } sb.append(")"); sb.append("V"); diff --git a/spring-core/src/main/java/org/springframework/cglib/core/VisibilityPredicate.java b/spring-core/src/main/java/org/springframework/cglib/core/VisibilityPredicate.java index efd96c70e812..e2ac86175d10 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/VisibilityPredicate.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/VisibilityPredicate.java @@ -15,7 +15,9 @@ */ package org.springframework.cglib.core; -import java.lang.reflect.*; +import java.lang.reflect.Member; +import java.lang.reflect.Modifier; + import org.springframework.asm.Type; @SuppressWarnings({"rawtypes", "unchecked"}) @@ -26,13 +28,14 @@ public class VisibilityPredicate implements Predicate { public VisibilityPredicate(Class source, boolean protectedOk) { this.protectedOk = protectedOk; - // same package is not ok for the bootstrap loaded classes. In all other cases we are + // same package is not ok for the bootstrap loaded classes. In all other cases we are // generating classes in the same classloader this.samePackageOk = source.getClassLoader() != null; pkg = TypeUtils.getPackageName(Type.getType(source)); } - public boolean evaluate(Object arg) { + @Override + public boolean evaluate(Object arg) { Member member = (Member)arg; int mod = member.getModifiers(); if (Modifier.isPrivate(mod)) { @@ -43,9 +46,9 @@ public boolean evaluate(Object arg) { // protected is fine if 'protectedOk' is true (for subclasses) return true; } else { - // protected/package private if the member is in the same package as the source class + // protected/package private if the member is in the same package as the source class // and we are generating into the same classloader. - return samePackageOk + return samePackageOk && pkg.equals(TypeUtils.getPackageName(Type.getType(member.getDeclaringClass()))); } } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/internal/CustomizerRegistry.java b/spring-core/src/main/java/org/springframework/cglib/core/internal/CustomizerRegistry.java index 4e114b567999..b05afceab1dd 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/internal/CustomizerRegistry.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/internal/CustomizerRegistry.java @@ -12,7 +12,7 @@ @SuppressWarnings({"rawtypes", "unchecked"}) public class CustomizerRegistry { private final Class[] customizerTypes; - private Map> customizers = new HashMap>(); + private Map> customizers = new HashMap<>(); public CustomizerRegistry(Class[] customizerTypes) { this.customizerTypes = customizerTypes; @@ -24,7 +24,7 @@ public void add(KeyFactoryCustomizer customizer) { if (type.isAssignableFrom(klass)) { List list = customizers.get(type); if (list == null) { - customizers.put(type, list = new ArrayList()); + customizers.put(type, list = new ArrayList<>()); } list.add(customizer); } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/internal/LoadingCache.java b/spring-core/src/main/java/org/springframework/cglib/core/internal/LoadingCache.java index 4c63211683b0..2bbc10e8db7b 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/internal/LoadingCache.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/internal/LoadingCache.java @@ -1,6 +1,9 @@ package org.springframework.cglib.core.internal; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; @SuppressWarnings({"rawtypes", "unchecked"}) public class LoadingCache { @@ -8,16 +11,12 @@ public class LoadingCache { protected final Function loader; protected final Function keyMapper; - public static final Function IDENTITY = new Function() { - public Object apply(Object key) { - return key; - } - }; + public static final Function IDENTITY = key -> key; public LoadingCache(Function keyMapper, Function loader) { this.keyMapper = keyMapper; this.loader = loader; - this.map = new ConcurrentHashMap(); + this.map = new ConcurrentHashMap<>(); } @SuppressWarnings("unchecked") @@ -50,11 +49,7 @@ protected V createEntry(final K key, KK cacheKey, Object v) { // Another thread is already loading an instance task = (FutureTask) v; } else { - task = new FutureTask(new Callable() { - public V call() throws Exception { - return loader.apply(key); - } - }); + task = new FutureTask<>(() -> loader.apply(key)); Object prevTask = map.putIfAbsent(cacheKey, task); if (prevTask == null) { // creator does the load diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/BridgeMethodResolver.java b/spring-core/src/main/java/org/springframework/cglib/proxy/BridgeMethodResolver.java index 983379385cea..65fcf60600f8 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/BridgeMethodResolver.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/BridgeMethodResolver.java @@ -22,12 +22,13 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; -import org.springframework.cglib.core.Constants; -import org.springframework.cglib.core.Signature; + import org.springframework.asm.ClassReader; import org.springframework.asm.ClassVisitor; import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.Signature; /** * Uses bytecode reflection to figure out the targets of all bridge methods that use invokespecial @@ -80,7 +81,7 @@ public BridgeMethodResolver(Map declToBridge, ClassLoader classLoader) { private static class BridgedFinder extends ClassVisitor { private Map/**/ resolved; private Set/**/ eligibleMethods; - + private Signature currentMethod = null; BridgedFinder(Set eligibleMethods, Map resolved) { @@ -89,17 +90,20 @@ private static class BridgedFinder extends ClassVisitor { this.eligibleMethods = eligibleMethods; } - public void visit(int version, int access, String name, + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { } - public MethodVisitor visitMethod(int access, String name, String desc, + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { Signature sig = new Signature(name, desc); if (eligibleMethods.remove(sig)) { currentMethod = sig; return new MethodVisitor(Constants.ASM_API) { - public void visitMethodInsn( + @Override + public void visitMethodInsn( int opcode, String owner, String name, String desc, boolean itf) { if ((opcode == Opcodes.INVOKESPECIAL || (itf && opcode == Opcodes.INVOKEINTERFACE)) diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackFilter.java b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackFilter.java index 1fc8079c51d7..fbba99768b0f 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackFilter.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackFilter.java @@ -31,7 +31,7 @@ public interface CallbackFilter { /** * Map a method to a callback. * @param method the intercepted method - * @return the index into the array of callbacks (as specified by {@link Enhancer#setCallbacks}) to use for the method, + * @return the index into the array of callbacks (as specified by {@link Enhancer#setCallbacks}) to use for the method, */ int accept(Method method); @@ -42,5 +42,6 @@ public interface CallbackFilter { * hashCode for custom CallbackFilter * implementations in order to improve performance. */ - boolean equals(Object o); + @Override + boolean equals(Object o); } diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackGenerator.java index 168dab3b1183..22a3bfae86c7 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackGenerator.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackGenerator.java @@ -16,7 +16,11 @@ package org.springframework.cglib.proxy; import java.util.List; -import org.springframework.cglib.core.*; + +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.Signature; @SuppressWarnings({"rawtypes", "unchecked"}) interface CallbackGenerator diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackHelper.java b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackHelper.java index 9aa54f0728c2..4f8b3726f07b 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackHelper.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackHelper.java @@ -15,9 +15,13 @@ */ package org.springframework.cglib.proxy; -import org.springframework.cglib.core.ReflectUtils; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.cglib.core.ReflectUtils; /** * @version $Id: CallbackHelper.java,v 1.2 2004/06/24 21:15:20 herbyderby Exp $ @@ -28,22 +32,24 @@ abstract public class CallbackHelper { private Map methodMap = new HashMap(); private List callbacks = new ArrayList(); - - public CallbackHelper(Class superclass, Class[] interfaces) - { + + public CallbackHelper(Class superclass, Class[] interfaces) { List methods = new ArrayList(); Enhancer.getMethods(superclass, interfaces, methods); Map indexes = new HashMap(); for (int i = 0, size = methods.size(); i < size; i++) { Method method = (Method)methods.get(i); Object callback = getCallback(method); - if (callback == null) + if (callback == null) { throw new IllegalStateException("getCallback cannot return null"); + } boolean isCallback = callback instanceof Callback; - if (!(isCallback || (callback instanceof Class))) + if (!(isCallback || (callback instanceof Class))) { throw new IllegalStateException("getCallback must return a Callback or a Class"); - if (i > 0 && ((callbacks.get(i - 1) instanceof Callback) ^ isCallback)) + } + if (i > 0 && ((callbacks.get(i - 1) instanceof Callback) ^ isCallback)) { throw new IllegalStateException("getCallback must return a Callback or a Class consistently for every Method"); + } Integer index = (Integer)indexes.get(callback); if (index == null) { index = callbacks.size(); @@ -56,44 +62,49 @@ public CallbackHelper(Class superclass, Class[] interfaces) abstract protected Object getCallback(Method method); - public Callback[] getCallbacks() - { - if (callbacks.size() == 0) + public Callback[] getCallbacks() { + if (callbacks.size() == 0) { return new Callback[0]; + } if (callbacks.get(0) instanceof Callback) { return (Callback[])callbacks.toArray(new Callback[callbacks.size()]); - } else { + } + else { throw new IllegalStateException("getCallback returned classes, not callbacks; call getCallbackTypes instead"); } } - public Class[] getCallbackTypes() - { - if (callbacks.size() == 0) + public Class[] getCallbackTypes() { + if (callbacks.size() == 0) { return new Class[0]; + } if (callbacks.get(0) instanceof Callback) { return ReflectUtils.getClasses(getCallbacks()); - } else { + } + else { return (Class[])callbacks.toArray(new Class[callbacks.size()]); } } - public int accept(Method method) - { + @Override + public int accept(Method method) { return ((Integer)methodMap.get(method)).intValue(); } - public int hashCode() - { + @Override + public int hashCode() { return methodMap.hashCode(); } - - public boolean equals(Object o) - { - if (o == null) + + @Override + public boolean equals(Object o) { + if (o == null) { return false; - if (!(o instanceof CallbackHelper)) + } + if (!(o instanceof CallbackHelper other)) { return false; - return methodMap.equals(((CallbackHelper)o).methodMap); + } + return this.methodMap.equals(other.methodMap); } + } diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackInfo.java b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackInfo.java index e1a5cbc5bb62..5ec7db4c9180 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackInfo.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackInfo.java @@ -57,7 +57,7 @@ public static CallbackGenerator[] getGenerators(Type[] callbackTypes) { private Class cls; private CallbackGenerator generator; private Type type; - + private static final CallbackInfo[] CALLBACKS = { new CallbackInfo(NoOp.class, NoOpGenerator.INSTANCE), new CallbackInfo(MethodInterceptor.class, MethodInterceptorGenerator.INSTANCE), @@ -84,8 +84,7 @@ private static Type determineType(Callback callback, boolean checkAll) { private static Type determineType(Class callbackType, boolean checkAll) { Class cur = null; Type type = null; - for (int i = 0; i < CALLBACKS.length; i++) { - CallbackInfo info = CALLBACKS[i]; + for (CallbackInfo info : CALLBACKS) { if (info.cls.isAssignableFrom(callbackType)) { if (cur != null) { throw new IllegalStateException("Callback implements both " + cur + " and " + info.cls); @@ -104,8 +103,7 @@ private static Type determineType(Class callbackType, boolean checkAll) { } private static CallbackGenerator getGenerator(Type callbackType) { - for (int i = 0; i < CALLBACKS.length; i++) { - CallbackInfo info = CALLBACKS[i]; + for (CallbackInfo info : CALLBACKS) { if (info.type.equals(callbackType)) { return info.generator; } @@ -113,5 +111,5 @@ private static CallbackGenerator getGenerator(Type callbackType) { throw new IllegalStateException("Unknown callback type " + callbackType); } } - + diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/DispatcherGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/DispatcherGenerator.java index 154f7d7363c7..b13c38cf0619 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/DispatcherGenerator.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/DispatcherGenerator.java @@ -15,9 +15,15 @@ */ package org.springframework.cglib.proxy; -import java.util.*; -import org.springframework.cglib.core.*; +import java.util.Iterator; +import java.util.List; + import org.springframework.asm.Type; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.TypeUtils; @SuppressWarnings({"rawtypes", "unchecked"}) class DispatcherGenerator implements CallbackGenerator { @@ -41,7 +47,8 @@ private DispatcherGenerator(boolean proxyRef) { this.proxyRef = proxyRef; } - public void generate(ClassEmitter ce, Context context, List methods) { + @Override + public void generate(ClassEmitter ce, Context context, List methods) { for (Iterator it = methods.iterator(); it.hasNext();) { MethodInfo method = (MethodInfo)it.next(); if (!TypeUtils.isProtected(method.getModifiers())) { @@ -62,5 +69,6 @@ public void generate(ClassEmitter ce, Context context, List methods) { } } - public void generateStatic(CodeEmitter e, Context context, List methods) { } + @Override + public void generateStatic(CodeEmitter e, Context context, List methods) { } } diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java b/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java index 42de4db409cc..fc655f244ad2 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java @@ -51,7 +51,6 @@ import org.springframework.cglib.core.ReflectUtils; import org.springframework.cglib.core.RejectModifierPredicate; import org.springframework.cglib.core.Signature; -import org.springframework.cglib.core.Transformer; import org.springframework.cglib.core.TypeUtils; import org.springframework.cglib.core.VisibilityPredicate; import org.springframework.cglib.core.WeakCacheKey; @@ -91,11 +90,7 @@ @SuppressWarnings({"rawtypes", "unchecked"}) public class Enhancer extends AbstractClassGenerator { - private static final CallbackFilter ALL_ZERO = new CallbackFilter() { - public int accept(Method method) { - return 0; - } - }; + private static final CallbackFilter ALL_ZERO = method -> 0; private static final Source SOURCE = new Source(Enhancer.class.getName()); @@ -463,12 +458,12 @@ else if (callbacks != null) { callbackTypes = CallbackInfo.determineTypes(callbacks); } if (interfaces != null) { - for (int i = 0; i < interfaces.length; i++) { - if (interfaces[i] == null) { + for (Class element : interfaces) { + if (element == null) { throw new IllegalStateException("Interfaces cannot be null"); } - if (!interfaces[i].isInterface()) { - throw new IllegalStateException(interfaces[i] + " is not an interface"); + if (!element.isInterface()) { + throw new IllegalStateException(element + " is not an interface"); } } } @@ -557,7 +552,7 @@ private Object createHelper() { // SPRING PATCH BEGIN Object key = new EnhancerKey((superclass != null ? superclass.getName() : null), (interfaces != null ? Arrays.asList(ReflectUtils.getNames(interfaces)) : null), - (filter == ALL_ZERO ? null : new WeakCacheKey(filter)), + (filter == ALL_ZERO ? null : new WeakCacheKey<>(filter)), Arrays.asList(callbackTypes), useFactory, interceptDuringConstruction, @@ -580,6 +575,7 @@ else if (interfaces != null) { return super.generate(data); } + @Override protected ClassLoader getDefaultClassLoader() { if (superclass != null) { return superclass.getClassLoader(); @@ -592,6 +588,7 @@ else if (interfaces != null) { } } + @Override protected ProtectionDomain getProtectionDomain() { if (superclass != null) { return ReflectUtils.getProtectionDomain(superclass); @@ -630,9 +627,9 @@ private static void getMethods(Class superclass, Class[] interfaces, List method ReflectUtils.addAllMethods(superclass, methods); List target = (interfaceMethods != null) ? interfaceMethods : methods; if (interfaces != null) { - for (int i = 0; i < interfaces.length; i++) { - if (interfaces[i] != Factory.class) { - ReflectUtils.addAllMethods(interfaces[i], target); + for (Class element : interfaces) { + if (element != Factory.class) { + ReflectUtils.addAllMethods(element, target); } } } @@ -648,11 +645,13 @@ private static void getMethods(Class superclass, Class[] interfaces, List method CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_FINAL)); } + @Override public void generateClass(ClassVisitor v) throws Exception { Class sc = (superclass == null) ? Object.class : superclass; - if (TypeUtils.isFinal(sc.getModifiers())) + if (TypeUtils.isFinal(sc.getModifiers())) { throw new IllegalArgumentException("Cannot subclass final class " + sc.getName()); + } List constructors = new ArrayList(Arrays.asList(sc.getDeclaredConstructors())); filterConstructors(sc, constructors); @@ -664,19 +663,17 @@ public void generateClass(ClassVisitor v) throws Exception { final Set forcePublic = new HashSet(); getMethods(sc, interfaces, actualMethods, interfaceMethods, forcePublic); - List methods = CollectionUtils.transform(actualMethods, new Transformer() { - public Object transform(Object value) { - Method method = (Method) value; - int modifiers = Constants.ACC_FINAL - | (method.getModifiers() - & ~Constants.ACC_ABSTRACT - & ~Constants.ACC_NATIVE - & ~Constants.ACC_SYNCHRONIZED); - if (forcePublic.contains(MethodWrapper.create(method))) { - modifiers = (modifiers & ~Constants.ACC_PROTECTED) | Constants.ACC_PUBLIC; - } - return ReflectUtils.getMethodInfo(method, modifiers); + List methods = CollectionUtils.transform(actualMethods, value -> { + Method method = (Method) value; + int modifiers = Constants.ACC_FINAL + | (method.getModifiers() + & ~Constants.ACC_ABSTRACT + & ~Constants.ACC_NATIVE + & ~Constants.ACC_SYNCHRONIZED); + if (forcePublic.contains(MethodWrapper.create(method))) { + modifiers = (modifiers & ~Constants.ACC_PROTECTED) | Constants.ACC_PUBLIC; } + return ReflectUtils.getMethodInfo(method, modifiers); }); ClassEmitter e = new ClassEmitter(v); @@ -754,8 +751,9 @@ public Object transform(Object value) { */ protected void filterConstructors(Class sc, List constructors) { CollectionUtils.filter(constructors, new VisibilityPredicate(sc, true)); - if (constructors.size() == 0) + if (constructors.size() == 0) { throw new IllegalArgumentException("No visible constructors in " + sc); + } } /** @@ -767,6 +765,7 @@ protected void filterConstructors(Class sc, List constructors) { * @return newly created proxy instance * @throws Exception if something goes wrong */ + @Override protected Object firstInstance(Class type) throws Exception { if (classOnly) { return type; @@ -776,6 +775,7 @@ protected Object firstInstance(Class type) throws Exception { } } + @Override protected Object nextInstance(Object instance) { EnhancerFactoryData data = (EnhancerFactoryData) instance; @@ -809,13 +809,10 @@ protected Object wrapCachedClass(Class klass) { callbackFilterField.setAccessible(true); callbackFilterField.set(null, this.filter); } - catch (NoSuchFieldException e) { - throw new CodeGenerationException(e); - } - catch (IllegalAccessException e) { + catch (NoSuchFieldException | IllegalAccessException e) { throw new CodeGenerationException(e); } - return new WeakReference(factoryData); + return new WeakReference<>(factoryData); } @Override @@ -895,10 +892,7 @@ private static void setCallbacksHelper(Class type, Callback[] callbacks, String catch (NoSuchMethodException e) { throw new IllegalArgumentException(type + " is not an enhanced class"); } - catch (IllegalAccessException e) { - throw new CodeGenerationException(e); - } - catch (InvocationTargetException e) { + catch (IllegalAccessException | InvocationTargetException e) { throw new CodeGenerationException(e); } } @@ -1027,8 +1021,9 @@ private void emitConstructors(ClassEmitter ce, List constructors) { e.return_value(); e.end_method(); } - if (!classOnly && !seenNull && arguments == null) + if (!classOnly && !seenNull && arguments == null) { throw new IllegalArgumentException("Superclass has no null constructors but no arguments were given"); + } } private int[] getCallbackKeys() { @@ -1046,11 +1041,13 @@ private void emitGetCallback(ClassEmitter ce, int[] keys) { e.load_this(); e.load_arg(0); e.process_switch(keys, new ProcessSwitchCallback() { + @Override public void processCase(int key, Label end) { e.getfield(getCallbackField(key)); e.goTo(end); } + @Override public void processDefault() { e.pop(); // stack height e.aconst_null(); @@ -1064,6 +1061,7 @@ private void emitSetCallback(ClassEmitter ce, int[] keys) { final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SET_CALLBACK, null); e.load_arg(0); e.process_switch(keys, new ProcessSwitchCallback() { + @Override public void processCase(int key, Label end) { e.load_this(); e.load_arg(1); @@ -1072,6 +1070,7 @@ public void processCase(int key, Label end) { e.goTo(end); } + @Override public void processDefault() { // TODO: error? } @@ -1171,6 +1170,7 @@ private void emitNewInstanceMultiarg(ClassEmitter ce, List constructors) { e.dup(); e.load_arg(0); EmitUtils.constructor_switch(e, constructors, new ObjectSwitchCallback() { + @Override public void processCase(Object key, Label end) { MethodInfo constructor = (MethodInfo) key; Type types[] = constructor.getSignature().getArgumentTypes(); @@ -1184,6 +1184,7 @@ public void processCase(Object key, Label end) { e.goTo(end); } + @Override public void processDefault() { e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Constructor not found"); } @@ -1243,26 +1244,32 @@ private void emitMethods(final ClassEmitter ce, List methods, List actualMethods se.putfield(THREAD_CALLBACKS_FIELD); CallbackGenerator.Context context = new CallbackGenerator.Context() { + @Override public ClassLoader getClassLoader() { return Enhancer.this.getClassLoader(); } + @Override public int getOriginalModifiers(MethodInfo method) { return ((Integer) originalModifiers.get(method)).intValue(); } + @Override public int getIndex(MethodInfo method) { return ((Integer) indexes.get(method)).intValue(); } + @Override public void emitCallback(CodeEmitter e, int index) { emitCurrentCallback(e, index); } + @Override public Signature getImplSignature(MethodInfo method) { return rename(method.getSignature(), ((Integer) positions.get(method)).intValue()); } + @Override public void emitLoadArgsAndInvoke(CodeEmitter e, MethodInfo method) { // If this is a bridge and we know the target was called from invokespecial, // then we need to invoke_virtual w/ the bridge target instead of doing @@ -1302,6 +1309,7 @@ public void emitLoadArgsAndInvoke(CodeEmitter e, MethodInfo method) { } } + @Override public CodeEmitter beginMethod(ClassEmitter ce, MethodInfo method) { CodeEmitter e = EmitUtils.begin_method(ce, method); if (!interceptDuringConstruction && diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/Factory.java b/spring-core/src/main/java/org/springframework/cglib/proxy/Factory.java index c924bb10487a..7b0df0f6e0c1 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/Factory.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/Factory.java @@ -32,14 +32,14 @@ public interface Factory { * If multiple callbacks are required an exception will be thrown. * @param callback the new interceptor to use * @return new instance of the same type - */ + */ Object newInstance(Callback callback); - + /** * Creates new instance of the same type, using the no-arg constructor. * @param callbacks the new callbacks(s) to use * @return new instance of the same type - */ + */ Object newInstance(Callback[] callbacks); /** @@ -75,6 +75,6 @@ public interface Factory { /** * Get the current set of callbacks for ths object. * @return a new array instance - */ + */ Callback[] getCallbacks(); } diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/FixedValueGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/FixedValueGenerator.java index f34e2b468953..fc21a7026edf 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/FixedValueGenerator.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/FixedValueGenerator.java @@ -15,9 +15,15 @@ */ package org.springframework.cglib.proxy; -import java.util.*; -import org.springframework.cglib.core.*; +import java.util.Iterator; +import java.util.List; + import org.springframework.asm.Type; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.TypeUtils; @SuppressWarnings({"rawtypes", "unchecked"}) class FixedValueGenerator implements CallbackGenerator { @@ -27,7 +33,8 @@ class FixedValueGenerator implements CallbackGenerator { private static final Signature LOAD_OBJECT = TypeUtils.parseSignature("Object loadObject()"); - public void generate(ClassEmitter ce, Context context, List methods) { + @Override + public void generate(ClassEmitter ce, Context context, List methods) { for (Iterator it = methods.iterator(); it.hasNext();) { MethodInfo method = (MethodInfo)it.next(); CodeEmitter e = context.beginMethod(ce, method); @@ -39,5 +46,6 @@ public void generate(ClassEmitter ce, Context context, List methods) { } } - public void generateStatic(CodeEmitter e, Context context, List methods) { } + @Override + public void generateStatic(CodeEmitter e, Context context, List methods) { } } diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/InterfaceMaker.java b/spring-core/src/main/java/org/springframework/cglib/proxy/InterfaceMaker.java index 258412c592f7..9ed15417a31a 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/InterfaceMaker.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/InterfaceMaker.java @@ -15,11 +15,18 @@ */ package org.springframework.cglib.proxy; -import java.lang.reflect.*; -import java.util.*; -import org.springframework.cglib.core.*; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + import org.springframework.asm.ClassVisitor; import org.springframework.asm.Type; +import org.springframework.cglib.core.AbstractClassGenerator; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.Signature; /** * Generates new interfaces at runtime. @@ -71,8 +78,7 @@ public void add(Method method) { */ public void add(Class clazz) { Method[] methods = clazz.getMethods(); - for (int i = 0; i < methods.length; i++) { - Method m = methods[i]; + for (Method m : methods) { if (!m.getDeclaringClass().getName().equals("java.lang.Object")) { add(m); } @@ -87,19 +93,23 @@ public Class create() { return (Class)super.create(this); } - protected ClassLoader getDefaultClassLoader() { + @Override + protected ClassLoader getDefaultClassLoader() { return null; } - - protected Object firstInstance(Class type) { + + @Override + protected Object firstInstance(Class type) { return type; } - protected Object nextInstance(Object instance) { + @Override + protected Object nextInstance(Object instance) { throw new IllegalStateException("InterfaceMaker does not cache"); } - public void generateClass(ClassVisitor v) throws Exception { + @Override + public void generateClass(ClassVisitor v) throws Exception { ClassEmitter ce = new ClassEmitter(v); ce.begin_class(Constants.V1_8, Constants.ACC_PUBLIC | Constants.ACC_INTERFACE | Constants.ACC_ABSTRACT, diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandlerGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandlerGenerator.java index d788729a5683..92e57b05862f 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandlerGenerator.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandlerGenerator.java @@ -15,9 +15,18 @@ */ package org.springframework.cglib.proxy; -import org.springframework.cglib.core.*; -import java.util.*; +import java.util.Iterator; +import java.util.List; + import org.springframework.asm.Type; +import org.springframework.cglib.core.Block; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.TypeUtils; @SuppressWarnings({"rawtypes", "unchecked"}) class InvocationHandlerGenerator @@ -34,7 +43,8 @@ class InvocationHandlerGenerator private static final Signature INVOKE = TypeUtils.parseSignature("Object invoke(Object, java.lang.reflect.Method, Object[])"); - public void generate(ClassEmitter ce, Context context, List methods) { + @Override + public void generate(ClassEmitter ce, Context context, List methods) { for (Iterator it = methods.iterator(); it.hasNext();) { MethodInfo method = (MethodInfo)it.next(); Signature impl = context.getImplSignature(method); @@ -55,7 +65,8 @@ public void generate(ClassEmitter ce, Context context, List methods) { } } - public void generateStatic(CodeEmitter e, Context context, List methods) { + @Override + public void generateStatic(CodeEmitter e, Context context, List methods) { for (Iterator it = methods.iterator(); it.hasNext();) { MethodInfo method = (MethodInfo)it.next(); EmitUtils.load_method(e, method); diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoaderGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoaderGenerator.java index 49261578b47a..d8647f7bee9d 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoaderGenerator.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoaderGenerator.java @@ -15,21 +15,31 @@ */ package org.springframework.cglib.proxy; -import java.util.*; -import org.springframework.cglib.core.*; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + import org.springframework.asm.Label; import org.springframework.asm.Type; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.TypeUtils; @SuppressWarnings({"rawtypes", "unchecked"}) class LazyLoaderGenerator implements CallbackGenerator { public static final LazyLoaderGenerator INSTANCE = new LazyLoaderGenerator(); - private static final Signature LOAD_OBJECT = + private static final Signature LOAD_OBJECT = TypeUtils.parseSignature("Object loadObject()"); private static final Type LAZY_LOADER = TypeUtils.parseType("org.springframework.cglib.proxy.LazyLoader"); - public void generate(ClassEmitter ce, Context context, List methods) { + @Override + public void generate(ClassEmitter ce, Context context, List methods) { Set indexes = new HashSet(); for (Iterator it = methods.iterator(); it.hasNext();) { MethodInfo method = (MethodInfo)it.next(); @@ -75,7 +85,7 @@ public void generate(ClassEmitter ce, Context context, List methods) { e.mark(end); e.return_value(); e.end_method(); - + } } @@ -85,5 +95,6 @@ private Signature loadMethod(int index) { Constants.TYPES_EMPTY); } - public void generateStatic(CodeEmitter e, Context context, List methods) { } + @Override + public void generateStatic(CodeEmitter e, Context context, List methods) { } } diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptor.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptor.java index 741ef943b18d..c7db04bf11f8 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptor.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptor.java @@ -35,7 +35,7 @@ public interface MethodInterceptor * @throws Throwable any exception may be thrown; if so, super method will not be invoked * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value. * @see MethodProxy - */ + */ public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable; diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptorGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptorGenerator.java index 863c4df0d9fe..9a8b008a0305 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptorGenerator.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptorGenerator.java @@ -79,12 +79,7 @@ class MethodInterceptorGenerator new Signature(FIND_PROXY_NAME, METHOD_PROXY, new Type[]{ Constants.TYPE_SIGNATURE }); private static final Signature TO_STRING = TypeUtils.parseSignature("String toString()"); - private static final Transformer METHOD_TO_CLASS = new Transformer(){ - @Override - public Object transform(Object value) { - return ((MethodInfo)value).getClassInfo(); - } - }; + private static final Transformer METHOD_TO_CLASS = value -> ((MethodInfo)value).getClassInfo(); private String getMethodField(Signature impl) { return impl.getName() + "$Method"; diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MethodProxy.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodProxy.java index 93cfcfc5a8ff..ca468ca4324a 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/MethodProxy.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodProxy.java @@ -234,8 +234,9 @@ public Object invoke(Object obj, Object[] args) throws Throwable { throw ex.getTargetException(); } catch (IllegalArgumentException ex) { - if (fastClassInfo.i1 < 0) + if (fastClassInfo.i1 < 0) { throw new IllegalArgumentException("Protected method: " + sig1); + } throw ex; } } diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MixinBeanEmitter.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinBeanEmitter.java index 865a67e5180a..8e76df1fb1cb 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/MixinBeanEmitter.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinBeanEmitter.java @@ -16,8 +16,9 @@ package org.springframework.cglib.proxy; import java.lang.reflect.Method; -import org.springframework.cglib.core.ReflectUtils; + import org.springframework.asm.ClassVisitor; +import org.springframework.cglib.core.ReflectUtils; /** * @author Chris Nokleberg @@ -29,11 +30,13 @@ public MixinBeanEmitter(ClassVisitor v, String className, Class[] classes) { super(v, className, classes, null); } - protected Class[] getInterfaces(Class[] classes) { + @Override + protected Class[] getInterfaces(Class[] classes) { return null; } - protected Method[] getMethods(Class type) { + @Override + protected Method[] getMethods(Class type) { return ReflectUtils.getPropertyMethods(ReflectUtils.getBeanProperties(type), true, true); } } diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEmitter.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEmitter.java index 9fda5dac12b8..ab2460fe9f8c 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEmitter.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEmitter.java @@ -16,10 +16,20 @@ package org.springframework.cglib.proxy; import java.lang.reflect.Method; -import java.util.*; -import org.springframework.cglib.core.*; +import java.util.HashSet; +import java.util.Set; + import org.springframework.asm.ClassVisitor; import org.springframework.asm.Type; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.MethodWrapper; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.TypeUtils; /** * @author Chris Nokleberg @@ -61,20 +71,20 @@ public MixinEmitter(ClassVisitor v, String className, Class[] classes, int[] rou Set unique = new HashSet(); for (int i = 0; i < classes.length; i++) { Method[] methods = getMethods(classes[i]); - for (int j = 0; j < methods.length; j++) { - if (unique.add(MethodWrapper.create(methods[j]))) { - MethodInfo method = ReflectUtils.getMethodInfo(methods[j]); + for (Method method : methods) { + if (unique.add(MethodWrapper.create(method))) { + MethodInfo methodInfo = ReflectUtils.getMethodInfo(method); int modifiers = Constants.ACC_PUBLIC; - if ((method.getModifiers() & Constants.ACC_VARARGS) == Constants.ACC_VARARGS) { + if ((methodInfo.getModifiers() & Constants.ACC_VARARGS) == Constants.ACC_VARARGS) { modifiers |= Constants.ACC_VARARGS; } - e = EmitUtils.begin_method(this, method, modifiers); + e = EmitUtils.begin_method(this, methodInfo, modifiers); e.load_this(); e.getfield(FIELD_NAME); e.aaload((route != null) ? route[i] : i); - e.checkcast(method.getClassInfo().getType()); + e.checkcast(methodInfo.getClassInfo().getType()); e.load_args(); - e.invoke(method); + e.invoke(methodInfo); e.return_value(); e.end_method(); } diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEverythingEmitter.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEverythingEmitter.java index e335e218c171..7a672c6a459b 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEverythingEmitter.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEverythingEmitter.java @@ -13,15 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cglib.proxy; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.asm.ClassVisitor; import org.springframework.cglib.core.CollectionUtils; import org.springframework.cglib.core.ReflectUtils; import org.springframework.cglib.core.RejectModifierPredicate; -import org.springframework.asm.ClassVisitor; /** * @author Chris Nokleberg @@ -34,15 +38,17 @@ public MixinEverythingEmitter(ClassVisitor v, String className, Class[] classes) super(v, className, classes, null); } + @Override protected Class[] getInterfaces(Class[] classes) { List list = new ArrayList(); - for (int i = 0; i < classes.length; i++) { - ReflectUtils.addAllInterfaces(classes[i], list); + for (Class clazz : classes) { + ReflectUtils.addAllInterfaces(clazz, list); } return (Class[])list.toArray(new Class[list.size()]); } - protected Method[] getMethods(Class type) { + @Override + protected Method[] getMethods(Class type) { List methods = new ArrayList(Arrays.asList(type.getMethods())); CollectionUtils.filter(methods, new RejectModifierPredicate(Modifier.FINAL | Modifier.STATIC)); return (Method[])methods.toArray(new Method[methods.size()]); diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/NoOpGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/NoOpGenerator.java index c2b6bd3bb184..71aeacd55851 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/NoOpGenerator.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/NoOpGenerator.java @@ -17,7 +17,12 @@ import java.util.Iterator; import java.util.List; -import org.springframework.cglib.core.*; + +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.TypeUtils; @SuppressWarnings({"rawtypes", "unchecked"}) class NoOpGenerator @@ -25,7 +30,8 @@ class NoOpGenerator { public static final NoOpGenerator INSTANCE = new NoOpGenerator(); - public void generate(ClassEmitter ce, Context context, List methods) { + @Override + public void generate(ClassEmitter ce, Context context, List methods) { for (Iterator it = methods.iterator(); it.hasNext();) { MethodInfo method = (MethodInfo)it.next(); if (TypeUtils.isBridge(method.getModifiers()) || ( @@ -39,6 +45,7 @@ public void generate(ClassEmitter ce, Context context, List methods) { } } } - - public void generateStatic(CodeEmitter e, Context context, List methods) { } + + @Override + public void generateStatic(CodeEmitter e, Context context, List methods) { } } diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/Proxy.java b/spring-core/src/main/java/org/springframework/cglib/proxy/Proxy.java index 0e70ff477811..15a7b1296ae3 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/Proxy.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/Proxy.java @@ -17,7 +17,6 @@ package org.springframework.cglib.proxy; import java.io.Serializable; -import java.lang.reflect.Method; import org.springframework.cglib.core.CodeGenerationException; @@ -40,20 +39,17 @@ public class Proxy implements Serializable { protected InvocationHandler h; - private static final CallbackFilter BAD_OBJECT_METHOD_FILTER = new CallbackFilter() { - @Override - public int accept(Method method) { - if (method.getDeclaringClass().getName().equals("java.lang.Object")) { - String name = method.getName(); - if (!(name.equals("hashCode") || - name.equals("equals") || - name.equals("toString"))) { - return 1; - } - } - return 0; - } - }; + private static final CallbackFilter BAD_OBJECT_METHOD_FILTER = method -> { + if (method.getDeclaringClass().getName().equals("java.lang.Object")) { + String name = method.getName(); + if (!(name.equals("hashCode") || + name.equals("equals") || + name.equals("toString"))) { + return 1; + } + } + return 0; + }; protected Proxy(InvocationHandler h) { Enhancer.registerCallbacks(getClass(), new Callback[]{ h, null }); diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/UndeclaredThrowableException.java b/spring-core/src/main/java/org/springframework/cglib/proxy/UndeclaredThrowableException.java index f566e560ef23..f3e010e87d3d 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/UndeclaredThrowableException.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/UndeclaredThrowableException.java @@ -30,7 +30,7 @@ public class UndeclaredThrowableException extends CodeGenerationException { public UndeclaredThrowableException(Throwable t) { super(t); } - + public Throwable getUndeclaredThrowable() { return getCause(); } diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/ConstructorDelegate.java b/spring-core/src/main/java/org/springframework/cglib/reflect/ConstructorDelegate.java index 331b78a093b8..5bb1c4bbff7d 100644 --- a/spring-core/src/main/java/org/springframework/cglib/reflect/ConstructorDelegate.java +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/ConstructorDelegate.java @@ -15,11 +15,20 @@ */ package org.springframework.cglib.reflect; -import java.lang.reflect.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.security.ProtectionDomain; -import org.springframework.cglib.core.*; + import org.springframework.asm.ClassVisitor; import org.springframework.asm.Type; +import org.springframework.cglib.core.AbstractClassGenerator; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.KeyFactory; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.TypeUtils; /** * @author Chris Nokleberg @@ -29,7 +38,7 @@ abstract public class ConstructorDelegate { private static final ConstructorKey KEY_FACTORY = (ConstructorKey)KeyFactory.create(ConstructorKey.class, KeyFactory.CLASS_BY_NAME); - + interface ConstructorKey { public Object newInstance(String declaring, String iface); } @@ -70,15 +79,18 @@ public ConstructorDelegate create() { return (ConstructorDelegate)super.create(key); } - protected ClassLoader getDefaultClassLoader() { + @Override + protected ClassLoader getDefaultClassLoader() { return targetClass.getClassLoader(); } - protected ProtectionDomain getProtectionDomain() { + @Override + protected ProtectionDomain getProtectionDomain() { return ReflectUtils.getProtectionDomain(targetClass); } - public void generateClass(ClassVisitor v) { + @Override + public void generateClass(ClassVisitor v) { setNamePrefix(targetClass.getName()); final Method newInstance = ReflectUtils.findNewInstance(iface); @@ -113,11 +125,13 @@ public void generateClass(ClassVisitor v) { ce.end_class(); } - protected Object firstInstance(Class type) { + @Override + protected Object firstInstance(Class type) { return ReflectUtils.newInstance(type); } - protected Object nextInstance(Object instance) { + @Override + protected Object nextInstance(Object instance) { return instance; } } diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/FastClass.java b/spring-core/src/main/java/org/springframework/cglib/reflect/FastClass.java index 830cfe2103c1..f2ad48176101 100644 --- a/spring-core/src/main/java/org/springframework/cglib/reflect/FastClass.java +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/FastClass.java @@ -15,13 +15,17 @@ */ package org.springframework.cglib.reflect; -import org.springframework.cglib.core.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.ProtectionDomain; + import org.springframework.asm.ClassVisitor; import org.springframework.asm.Type; +import org.springframework.cglib.core.AbstractClassGenerator; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.Signature; @SuppressWarnings({"rawtypes", "unchecked"}) abstract public class FastClass @@ -37,9 +41,9 @@ protected FastClass(Class type) { } public static FastClass create(Class type) { - + return create(type.getClassLoader(),type); - + } public static FastClass create(ClassLoader loader, Class type) { Generator gen = new Generator(); @@ -52,7 +56,7 @@ public static class Generator extends AbstractClassGenerator { private static final Source SOURCE = new Source(FastClass.class.getName()); private Class type; - + public Generator() { super(SOURCE); } @@ -60,35 +64,40 @@ public Generator() { public void setType(Class type) { this.type = type; } - + public FastClass create() { setNamePrefix(type.getName()); return (FastClass)super.create(type.getName()); } - protected ClassLoader getDefaultClassLoader() { + @Override + protected ClassLoader getDefaultClassLoader() { return type.getClassLoader(); } - protected ProtectionDomain getProtectionDomain() { + @Override + protected ProtectionDomain getProtectionDomain() { return ReflectUtils.getProtectionDomain(type); } - public void generateClass(ClassVisitor v) throws Exception { + @Override + public void generateClass(ClassVisitor v) throws Exception { new FastClassEmitter(v, getClassName(), type); } - protected Object firstInstance(Class type) { + @Override + protected Object firstInstance(Class type) { return ReflectUtils.newInstance(type, new Class[]{ Class.class }, new Object[]{ this.type }); } - protected Object nextInstance(Object instance) { + @Override + protected Object nextInstance(Object instance) { return instance; } } - + public Object invoke(String name, Class[] parameterTypes, Object obj, Object[] args) throws InvocationTargetException { return invoke(getIndex(name, parameterTypes), obj, args); } @@ -100,7 +109,7 @@ public Object newInstance() throws InvocationTargetException { public Object newInstance(Class[] parameterTypes, Object[] args) throws InvocationTargetException { return newInstance(getIndex(parameterTypes), args); } - + public FastMethod getMethod(Method method) { return new FastMethod(this, method); } @@ -133,15 +142,18 @@ public Class getJavaClass() { return type; } - public String toString() { + @Override + public String toString() { return type.toString(); } - public int hashCode() { + @Override + public int hashCode() { return type.hashCode(); } - public boolean equals(Object o) { + @Override + public boolean equals(Object o) { if (o == null || !(o instanceof FastClass)) { return false; } @@ -199,8 +211,8 @@ protected static String getSignatureWithoutReturnType(String name, Class[] param StringBuilder sb = new StringBuilder(); sb.append(name); sb.append('('); - for (int i = 0; i < parameterTypes.length; i++) { - sb.append(Type.getDescriptor(parameterTypes[i])); + for (Class parameterType : parameterTypes) { + sb.append(Type.getDescriptor(parameterType)); } sb.append(')'); return sb.toString(); diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/FastClassEmitter.java b/spring-core/src/main/java/org/springframework/cglib/reflect/FastClassEmitter.java index 11b45ea19de8..4e5ba38b5c13 100644 --- a/spring-core/src/main/java/org/springframework/cglib/reflect/FastClassEmitter.java +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/FastClassEmitter.java @@ -13,14 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cglib.reflect; -import java.lang.reflect.*; -import java.util.*; -import org.springframework.cglib.core.*; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + import org.springframework.asm.ClassVisitor; import org.springframework.asm.Label; import org.springframework.asm.Type; +import org.springframework.cglib.core.Block; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.CollectionUtils; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.DuplicatesPredicate; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.MethodInfoTransformer; +import org.springframework.cglib.core.ObjectSwitchCallback; +import org.springframework.cglib.core.ProcessSwitchCallback; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.TypeUtils; +import org.springframework.cglib.core.VisibilityPredicate; @SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) class FastClassEmitter extends ClassEmitter { @@ -49,7 +70,7 @@ class FastClassEmitter extends ClassEmitter { private static final Type INVOCATION_TARGET_EXCEPTION = TypeUtils.parseType("java.lang.reflect.InvocationTargetException"); private static final Type[] INVOCATION_TARGET_EXCEPTION_ARRAY = { INVOCATION_TARGET_EXCEPTION }; - + public FastClassEmitter(ClassVisitor v, String className, Class type) { super(v); @@ -70,13 +91,13 @@ public FastClassEmitter(ClassVisitor v, String className, Class type) { CollectionUtils.filter(methods, new DuplicatesPredicate()); List constructors = new ArrayList(Arrays.asList(type.getDeclaredConstructors())); CollectionUtils.filter(constructors, vp); - + // getIndex(String) emitIndexBySignature(methods); // getIndex(String, Class[]) emitIndexByClassArray(methods); - + // getIndex(Class[]) e = begin_method(Constants.ACC_PUBLIC, CONSTRUCTOR_GET_INDEX, null); e.load_args(); @@ -112,11 +133,7 @@ public FastClassEmitter(ClassVisitor v, String className, Class type) { // TODO: support constructor indices ("") private void emitIndexBySignature(List methods) { CodeEmitter e = begin_method(Constants.ACC_PUBLIC, SIGNATURE_GET_INDEX, null); - List signatures = CollectionUtils.transform(methods, new Transformer() { - public Object transform(Object obj) { - return ReflectUtils.getSignature((Method)obj).toString(); - } - }); + List signatures = CollectionUtils.transform(methods, obj -> ReflectUtils.getSignature((Method)obj).toString()); e.load_arg(0); e.invoke_virtual(Constants.TYPE_OBJECT, TO_STRING); signatureSwitchHelper(e, signatures); @@ -128,12 +145,10 @@ private void emitIndexByClassArray(List methods) { CodeEmitter e = begin_method(Constants.ACC_PUBLIC, METHOD_GET_INDEX, null); if (methods.size() > TOO_MANY_METHODS) { // hack for big classes - List signatures = CollectionUtils.transform(methods, new Transformer() { - public Object transform(Object obj) { - String s = ReflectUtils.getSignature((Method)obj).toString(); - return s.substring(0, s.lastIndexOf(')') + 1); - } - }); + List signatures = CollectionUtils.transform(methods, obj -> { + String s = ReflectUtils.getSignature((Method)obj).toString(); + return s.substring(0, s.lastIndexOf(')') + 1); + }); e.load_args(); e.invoke_static(FAST_CLASS, GET_SIGNATURE_WITHOUT_RETURN_TYPE); signatureSwitchHelper(e, signatures); @@ -147,12 +162,14 @@ public Object transform(Object obj) { private void signatureSwitchHelper(final CodeEmitter e, final List signatures) { ObjectSwitchCallback callback = new ObjectSwitchCallback() { - public void processCase(Object key, Label end) { + @Override + public void processCase(Object key, Label end) { // TODO: remove linear indexOf e.push(signatures.indexOf(key)); e.return_value(); } - public void processDefault() { + @Override + public void processDefault() { e.push(-1); e.return_value(); } @@ -164,11 +181,12 @@ public void processDefault() { } private static void invokeSwitchHelper(final CodeEmitter e, List members, final int arg, final Type base) { - final List info = CollectionUtils.transform(members, MethodInfoTransformer.getInstance()); + final List info = CollectionUtils.transform(members, MethodInfoTransformer.getInstance()); final Label illegalArg = e.make_label(); Block block = e.begin_block(); e.process_switch(getIntRange(info.size()), new ProcessSwitchCallback() { - public void processCase(int key, Label end) { + @Override + public void processCase(int key, Label end) { MethodInfo method = (MethodInfo)info.get(key); Type[] types = method.getSignature().getArgumentTypes(); for (int i = 0; i < types.length; i++) { @@ -184,7 +202,8 @@ public void processCase(int key, Label end) { } e.return_value(); } - public void processDefault() { + @Override + public void processDefault() { e.goTo(illegalArg); } }); @@ -205,18 +224,20 @@ public GetIndexCallback(CodeEmitter e, List methods) { indexes.put(it.next(), index++); } } - - public void processCase(Object key, Label end) { + + @Override + public void processCase(Object key, Label end) { e.push(((Integer)indexes.get(key))); e.return_value(); } - - public void processDefault() { + + @Override + public void processDefault() { e.push(-1); e.return_value(); } } - + private static int[] getIntRange(int length) { int[] range = new int[length]; for (int i = 0; i < length; i++) { diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/FastConstructor.java b/spring-core/src/main/java/org/springframework/cglib/reflect/FastConstructor.java index b3735dd1411c..fb18fe714457 100644 --- a/spring-core/src/main/java/org/springframework/cglib/reflect/FastConstructor.java +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/FastConstructor.java @@ -25,11 +25,13 @@ public class FastConstructor extends FastMember super(fc, constructor, fc.getIndex(constructor.getParameterTypes())); } - public Class[] getParameterTypes() { + @Override + public Class[] getParameterTypes() { return ((Constructor)member).getParameterTypes(); } - public Class[] getExceptionTypes() { + @Override + public Class[] getExceptionTypes() { return ((Constructor)member).getExceptionTypes(); } diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/FastMember.java b/spring-core/src/main/java/org/springframework/cglib/reflect/FastMember.java index f6c3ca7e5b0d..fffea00ae4ab 100644 --- a/spring-core/src/main/java/org/springframework/cglib/reflect/FastMember.java +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/FastMember.java @@ -49,18 +49,22 @@ public int getModifiers() { return member.getModifiers(); } + @Override public String toString() { return member.toString(); } + @Override public int hashCode() { return member.hashCode(); } + @Override public boolean equals(Object o) { - if (o == null || !(o instanceof FastMember)) { + if (o == null || !(o instanceof FastMember other)) { return false; } - return member.equals(((FastMember)o).member); + return member.equals(other.member); } + } diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/FastMethod.java b/spring-core/src/main/java/org/springframework/cglib/reflect/FastMethod.java index 323919298a6f..f0ec02b390a1 100644 --- a/spring-core/src/main/java/org/springframework/cglib/reflect/FastMethod.java +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/FastMethod.java @@ -18,9 +18,8 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import org.springframework.cglib.core.Signature; - import org.springframework.asm.Type; +import org.springframework.cglib.core.Signature; @SuppressWarnings({"rawtypes", "unchecked"}) public class FastMethod extends FastMember @@ -46,11 +45,13 @@ public Class getReturnType() { return ((Method)member).getReturnType(); } - public Class[] getParameterTypes() { + @Override + public Class[] getParameterTypes() { return ((Method)member).getParameterTypes(); } - public Class[] getExceptionTypes() { + @Override + public Class[] getExceptionTypes() { return ((Method)member).getExceptionTypes(); } diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/MulticastDelegate.java b/spring-core/src/main/java/org/springframework/cglib/reflect/MulticastDelegate.java index 7271442c3d17..b23a8b172073 100644 --- a/spring-core/src/main/java/org/springframework/cglib/reflect/MulticastDelegate.java +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/MulticastDelegate.java @@ -30,7 +30,6 @@ import org.springframework.cglib.core.EmitUtils; import org.springframework.cglib.core.Local; import org.springframework.cglib.core.MethodInfo; -import org.springframework.cglib.core.ProcessArrayCallback; import org.springframework.cglib.core.ReflectUtils; import org.springframework.cglib.core.Signature; import org.springframework.cglib.core.TypeUtils; @@ -166,17 +165,14 @@ private void emitProxy(ClassEmitter ce, final MethodInfo method) { e.load_this(); e.super_getfield("targets", Constants.TYPE_OBJECT_ARRAY); final Local result2 = result; - EmitUtils.process_array(e, Constants.TYPE_OBJECT_ARRAY, new ProcessArrayCallback() { - @Override - public void processElement(Type type) { - e.checkcast(Type.getType(iface)); - e.load_args(); - e.invoke(method); - if (returns) { - e.store_local(result2); - } - } - }); + EmitUtils.process_array(e, Constants.TYPE_OBJECT_ARRAY, type -> { + e.checkcast(Type.getType(iface)); + e.load_args(); + e.invoke(method); + if (returns) { + e.store_local(result2); + } + }); if (returns) { e.load_local(result); } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassFilterTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassFilterTransformer.java index d3e055485cc7..a9de1633a535 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassFilterTransformer.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassFilterTransformer.java @@ -26,7 +26,8 @@ abstract public class AbstractClassFilterTransformer extends AbstractClassTransf private ClassTransformer pass; private ClassVisitor target; - public void setTarget(ClassVisitor target) { + @Override + public void setTarget(ClassVisitor target) { super.setTarget(target); pass.setTarget(target); } @@ -37,7 +38,8 @@ protected AbstractClassFilterTransformer(ClassTransformer pass) { abstract protected boolean accept(int version, int access, String name, String signature, String superName, String[] interfaces); - public void visit(int version, + @Override + public void visit(int version, int access, String name, String signature, @@ -46,36 +48,43 @@ public void visit(int version, target = accept(version, access, name, signature, superName, interfaces) ? pass : cv; target.visit(version, access, name, signature, superName, interfaces); } - - public void visitSource(String source, String debug) { + + @Override + public void visitSource(String source, String debug) { target.visitSource(source, debug); } - - public void visitOuterClass(String owner, String name, String desc) { + + @Override + public void visitOuterClass(String owner, String name, String desc) { target.visitOuterClass(owner, name, desc); } - - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return target.visitAnnotation(desc, visible); } - - public void visitAttribute(Attribute attr) { + + @Override + public void visitAttribute(Attribute attr) { target.visitAttribute(attr); } - - public void visitInnerClass(String name, String outerName, String innerName, int access) { + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { target.visitInnerClass(name, outerName, innerName, access); } - public FieldVisitor visitField(int access, + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { return target.visitField(access, name, desc, signature, value); } - - public MethodVisitor visitMethod(int access, + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, @@ -83,7 +92,8 @@ public MethodVisitor visitMethod(int access, return target.visitMethod(access, name, desc, signature, exceptions); } - public void visitEnd() { + @Override + public void visitEnd() { target.visitEnd(); target = null; // just to be safe } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassLoader.java b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassLoader.java index 293037bb9a3a..b4a7583970f4 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassLoader.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassLoader.java @@ -15,80 +15,79 @@ */ package org.springframework.cglib.transform; -import org.springframework.cglib.core.CodeGenerationException; -import org.springframework.cglib.core.ClassGenerator; -import org.springframework.cglib.core.DebuggingClassWriter; +import java.io.IOException; + +import org.springframework.asm.Attribute; import org.springframework.asm.ClassReader; import org.springframework.asm.ClassWriter; -import org.springframework.asm.Attribute; - -import java.io.IOException; +import org.springframework.cglib.core.ClassGenerator; +import org.springframework.cglib.core.CodeGenerationException; +import org.springframework.cglib.core.DebuggingClassWriter; @SuppressWarnings({"rawtypes", "unchecked"}) abstract public class AbstractClassLoader extends ClassLoader { private ClassFilter filter; private ClassLoader classPath; private static java.security.ProtectionDomain DOMAIN ; - + static{ DOMAIN = AbstractClassLoader.class.getProtectionDomain(); } - + protected AbstractClassLoader(ClassLoader parent, ClassLoader classPath, ClassFilter filter) { super(parent); this.filter = filter; this.classPath = classPath; } - public Class loadClass(String name) throws ClassNotFoundException { - + @Override + public Class loadClass(String name) throws ClassNotFoundException { + Class loaded = findLoadedClass(name); - + if( loaded != null ){ if( loaded.getClassLoader() == this ){ return loaded; }//else reload with this class loader } - + if (!filter.accept(name)) { return super.loadClass(name); } ClassReader r; try { - - java.io.InputStream is = classPath.getResourceAsStream( + + java.io.InputStream is = classPath.getResourceAsStream( name.replace('.','/') + ".class" - ); - + ); + if (is == null) { - + throw new ClassNotFoundException(name); - + } - try { - + try { + r = new ClassReader(is); - + } finally { - + is.close(); - + } } catch (IOException e) { throw new ClassNotFoundException(name + ":" + e.getMessage()); } try { - DebuggingClassWriter w = + DebuggingClassWriter w = new DebuggingClassWriter(ClassWriter.COMPUTE_FRAMES); getGenerator(r).generateClass(w); byte[] b = w.toByteArray(); Class c = super.defineClass(name, b, 0, b.length, DOMAIN); postProcess(c); return c; - } catch (RuntimeException e) { - throw e; - } catch (Error e) { + } catch (RuntimeException | Error e) { throw e; } catch (Exception e) { throw new CodeGenerationException(e); @@ -102,7 +101,7 @@ protected ClassGenerator getGenerator(ClassReader r) { protected int getFlags() { return 0; } - + protected Attribute[] attributes() { return null; } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassTransformer.java index 954204fda4ba..140013aafdd9 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassTransformer.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassTransformer.java @@ -15,16 +15,17 @@ */ package org.springframework.cglib.transform; +import org.springframework.asm.ClassVisitor; import org.springframework.cglib.core.ClassTransformer; import org.springframework.cglib.core.Constants; -import org.springframework.asm.ClassVisitor; abstract public class AbstractClassTransformer extends ClassTransformer { protected AbstractClassTransformer() { super(Constants.ASM_API); } - public void setTarget(ClassVisitor target) { + @Override + public void setTarget(ClassVisitor target) { cv = target; } } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/AnnotationVisitorTee.java b/spring-core/src/main/java/org/springframework/cglib/transform/AnnotationVisitorTee.java index f194d9a03781..3c6bcb621b96 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/AnnotationVisitorTee.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/AnnotationVisitorTee.java @@ -15,17 +15,19 @@ */ package org.springframework.cglib.transform; -import org.springframework.cglib.core.Constants; import org.springframework.asm.AnnotationVisitor; +import org.springframework.cglib.core.Constants; public class AnnotationVisitorTee extends AnnotationVisitor { private AnnotationVisitor av1, av2; public static AnnotationVisitor getInstance(AnnotationVisitor av1, AnnotationVisitor av2) { - if (av1 == null) - return av2; - if (av2 == null) - return av1; + if (av1 == null) { + return av2; + } + if (av2 == null) { + return av1; + } return new AnnotationVisitorTee(av1, av2); } @@ -35,26 +37,31 @@ public AnnotationVisitorTee(AnnotationVisitor av1, AnnotationVisitor av2) { this.av2 = av2; } - public void visit(String name, Object value) { + @Override + public void visit(String name, Object value) { av2.visit(name, value); av2.visit(name, value); } - - public void visitEnum(String name, String desc, String value) { + + @Override + public void visitEnum(String name, String desc, String value) { av1.visitEnum(name, desc, value); av2.visitEnum(name, desc, value); } - - public AnnotationVisitor visitAnnotation(String name, String desc) { + + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { return getInstance(av1.visitAnnotation(name, desc), av2.visitAnnotation(name, desc)); } - - public AnnotationVisitor visitArray(String name) { + + @Override + public AnnotationVisitor visitArray(String name) { return getInstance(av1.visitArray(name), av2.visitArray(name)); } - - public void visitEnd() { + + @Override + public void visitEnd() { av1.visitEnd(); av2.visitEnd(); } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassFilter.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassFilter.java index adb63550fd45..951b4c596763 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/ClassFilter.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassFilter.java @@ -21,7 +21,7 @@ * @author baliuka */ public interface ClassFilter { - + boolean accept(String className); - + } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassReaderGenerator.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassReaderGenerator.java index 152d4089dee4..2ec9c5d0ea98 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/ClassReaderGenerator.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassReaderGenerator.java @@ -15,16 +15,16 @@ */ package org.springframework.cglib.transform; -import org.springframework.cglib.core.ClassGenerator; import org.springframework.asm.Attribute; import org.springframework.asm.ClassReader; import org.springframework.asm.ClassVisitor; +import org.springframework.cglib.core.ClassGenerator; public class ClassReaderGenerator implements ClassGenerator { private final ClassReader r; private final Attribute[] attrs; private final int flags; - + public ClassReaderGenerator(ClassReader r, int flags) { this(r, null, flags); } @@ -34,8 +34,9 @@ public ClassReaderGenerator(ClassReader r, Attribute[] attrs, int flags) { this.attrs = (attrs != null) ? attrs : new Attribute[0]; this.flags = flags; } - - public void generateClass(ClassVisitor v) { + + @Override + public void generateClass(ClassVisitor v) { r.accept(v, attrs, flags); } } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerChain.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerChain.java index c68160beb3e5..3a466c0fc958 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerChain.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerChain.java @@ -21,12 +21,13 @@ public class ClassTransformerChain extends AbstractClassTransformer { private ClassTransformer[] chain; - + public ClassTransformerChain(ClassTransformer[] chain) { this.chain = chain.clone(); } - public void setTarget(ClassVisitor v) { + @Override + public void setTarget(ClassVisitor v) { super.setTarget(chain[0]); ClassVisitor next = v; for (int i = chain.length - 1; i >= 0; i--) { @@ -35,7 +36,8 @@ public void setTarget(ClassVisitor v) { } } - public MethodVisitor visitMethod(int access, + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, @@ -43,7 +45,8 @@ public MethodVisitor visitMethod(int access, return cv.visitMethod(access, name, desc, signature, exceptions); } - public String toString() { + @Override + public String toString() { StringBuilder sb = new StringBuilder(); sb.append("ClassTransformerChain{"); for (int i = 0; i < chain.length; i++) { diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerTee.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerTee.java index a4572fe40656..afcbbc88e22e 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerTee.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerTee.java @@ -15,19 +15,20 @@ */ package org.springframework.cglib.transform; +import org.springframework.asm.ClassVisitor; import org.springframework.cglib.core.ClassTransformer; import org.springframework.cglib.core.Constants; -import org.springframework.asm.ClassVisitor; public class ClassTransformerTee extends ClassTransformer { private ClassVisitor branch; - + public ClassTransformerTee(ClassVisitor branch) { super(Constants.ASM_API); this.branch = branch; } - - public void setTarget(ClassVisitor target) { + + @Override + public void setTarget(ClassVisitor target) { cv = new ClassVisitorTee(branch, target); } } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassVisitorTee.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassVisitorTee.java index 638d320e1f96..44f38dd627b9 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/ClassVisitorTee.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassVisitorTee.java @@ -25,14 +25,15 @@ public class ClassVisitorTee extends ClassVisitor { private ClassVisitor cv1, cv2; - + public ClassVisitorTee(ClassVisitor cv1, ClassVisitor cv2) { super(Constants.ASM_API); this.cv1 = cv1; this.cv2 = cv2; } - public void visit(int version, + @Override + public void visit(int version, int access, String name, String signature, @@ -42,67 +43,80 @@ public void visit(int version, cv2.visit(version, access, name, signature, superName, interfaces); } - public void visitEnd() { + @Override + public void visitEnd() { cv1.visitEnd(); cv2.visitEnd(); cv1 = cv2 = null; } - public void visitInnerClass(String name, String outerName, String innerName, int access) { + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { cv1.visitInnerClass(name, outerName, innerName, access); cv2.visitInnerClass(name, outerName, innerName, access); } - public FieldVisitor visitField(int access, + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { FieldVisitor fv1 = cv1.visitField(access, name, desc, signature, value); FieldVisitor fv2 = cv2.visitField(access, name, desc, signature, value); - if (fv1 == null) - return fv2; - if (fv2 == null) - return fv1; + if (fv1 == null) { + return fv2; + } + if (fv2 == null) { + return fv1; + } return new FieldVisitorTee(fv1, fv2); } - public MethodVisitor visitMethod(int access, + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv1 = cv1.visitMethod(access, name, desc, signature, exceptions); MethodVisitor mv2 = cv2.visitMethod(access, name, desc, signature, exceptions); - if (mv1 == null) - return mv2; - if (mv2 == null) - return mv1; + if (mv1 == null) { + return mv2; + } + if (mv2 == null) { + return mv1; + } return new MethodVisitorTee(mv1, mv2); } - public void visitSource(String source, String debug) { + @Override + public void visitSource(String source, String debug) { cv1.visitSource(source, debug); cv2.visitSource(source, debug); } - public void visitOuterClass(String owner, String name, String desc) { + @Override + public void visitOuterClass(String owner, String name, String desc) { cv1.visitOuterClass(owner, name, desc); cv2.visitOuterClass(owner, name, desc); } - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return AnnotationVisitorTee.getInstance(cv1.visitAnnotation(desc, visible), cv2.visitAnnotation(desc, visible)); } - - public void visitAttribute(Attribute attrs) { + + @Override + public void visitAttribute(Attribute attrs) { cv1.visitAttribute(attrs); cv2.visitAttribute(attrs); } - public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { return AnnotationVisitorTee.getInstance(cv1.visitTypeAnnotation(typeRef, typePath, desc, visible), cv2.visitTypeAnnotation(typeRef, typePath, desc, visible)); } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/FieldVisitorTee.java b/spring-core/src/main/java/org/springframework/cglib/transform/FieldVisitorTee.java index a6c538099d91..b58496d5d92b 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/FieldVisitorTee.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/FieldVisitorTee.java @@ -15,37 +15,41 @@ */ package org.springframework.cglib.transform; -import org.springframework.cglib.core.Constants; import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.Attribute; import org.springframework.asm.FieldVisitor; import org.springframework.asm.TypePath; +import org.springframework.cglib.core.Constants; public class FieldVisitorTee extends FieldVisitor { private FieldVisitor fv1, fv2; - + public FieldVisitorTee(FieldVisitor fv1, FieldVisitor fv2) { super(Constants.ASM_API); this.fv1 = fv1; this.fv2 = fv2; } - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return AnnotationVisitorTee.getInstance(fv1.visitAnnotation(desc, visible), fv2.visitAnnotation(desc, visible)); } - - public void visitAttribute(Attribute attr) { + + @Override + public void visitAttribute(Attribute attr) { fv1.visitAttribute(attr); fv2.visitAttribute(attr); } - public void visitEnd() { + @Override + public void visitEnd() { fv1.visitEnd(); fv2.visitEnd(); } - public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { return AnnotationVisitorTee.getInstance(fv1.visitTypeAnnotation(typeRef, typePath, desc, visible), fv2.visitTypeAnnotation(typeRef, typePath, desc, visible)); } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/MethodFilterTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/MethodFilterTransformer.java index 615331ea5069..4953b4b0b1ca 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/MethodFilterTransformer.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/MethodFilterTransformer.java @@ -23,22 +23,24 @@ public class MethodFilterTransformer extends AbstractClassTransformer { private MethodFilter filter; private ClassTransformer pass; private ClassVisitor direct; - + public MethodFilterTransformer(MethodFilter filter, ClassTransformer pass) { this.filter = filter; this.pass = pass; super.setTarget(pass); } - public MethodVisitor visitMethod(int access, + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return (filter.accept(access, name, desc, signature, exceptions) ? pass : direct).visitMethod(access, name, desc, signature, exceptions); } - - public void setTarget(ClassVisitor target) { + + @Override + public void setTarget(ClassVisitor target) { pass.setTarget(target); direct = target; } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/MethodVisitorTee.java b/spring-core/src/main/java/org/springframework/cglib/transform/MethodVisitorTee.java index c0e60c57da26..05560194d07d 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/MethodVisitorTee.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/MethodVisitorTee.java @@ -27,166 +27,197 @@ public class MethodVisitorTee extends MethodVisitor { private final MethodVisitor mv1; private final MethodVisitor mv2; - + public MethodVisitorTee(MethodVisitor mv1, MethodVisitor mv2) { super(Constants.ASM_API); this.mv1 = mv1; this.mv2 = mv2; } - public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + @Override + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { mv1.visitFrame(type, nLocal, local, nStack, stack); mv2.visitFrame(type, nLocal, local, nStack, stack); } - public AnnotationVisitor visitAnnotationDefault() { + @Override + public AnnotationVisitor visitAnnotationDefault() { return AnnotationVisitorTee.getInstance(mv1.visitAnnotationDefault(), mv2.visitAnnotationDefault()); } - - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return AnnotationVisitorTee.getInstance(mv1.visitAnnotation(desc, visible), mv2.visitAnnotation(desc, visible)); } - - public AnnotationVisitor visitParameterAnnotation(int parameter, + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { return AnnotationVisitorTee.getInstance(mv1.visitParameterAnnotation(parameter, desc, visible), mv2.visitParameterAnnotation(parameter, desc, visible)); } - public void visitAttribute(Attribute attr) { + @Override + public void visitAttribute(Attribute attr) { mv1.visitAttribute(attr); mv2.visitAttribute(attr); } - - public void visitCode() { + + @Override + public void visitCode() { mv1.visitCode(); mv2.visitCode(); } - - public void visitInsn(int opcode) { + + @Override + public void visitInsn(int opcode) { mv1.visitInsn(opcode); mv2.visitInsn(opcode); } - - public void visitIntInsn(int opcode, int operand) { + + @Override + public void visitIntInsn(int opcode, int operand) { mv1.visitIntInsn(opcode, operand); mv2.visitIntInsn(opcode, operand); } - - public void visitVarInsn(int opcode, int var) { + + @Override + public void visitVarInsn(int opcode, int var) { mv1.visitVarInsn(opcode, var); mv2.visitVarInsn(opcode, var); } - - public void visitTypeInsn(int opcode, String desc) { + + @Override + public void visitTypeInsn(int opcode, String desc) { mv1.visitTypeInsn(opcode, desc); mv2.visitTypeInsn(opcode, desc); } - - public void visitFieldInsn(int opcode, String owner, String name, String desc) { + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { mv1.visitFieldInsn(opcode, owner, name, desc); mv2.visitFieldInsn(opcode, owner, name, desc); } - public void visitMethodInsn(int opcode, String owner, String name, String desc) { + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc) { mv1.visitMethodInsn(opcode, owner, name, desc); mv2.visitMethodInsn(opcode, owner, name, desc); } - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { mv1.visitMethodInsn(opcode, owner, name, desc, itf); mv2.visitMethodInsn(opcode, owner, name, desc, itf); } - - public void visitJumpInsn(int opcode, Label label) { + + @Override + public void visitJumpInsn(int opcode, Label label) { mv1.visitJumpInsn(opcode, label); mv2.visitJumpInsn(opcode, label); } - - public void visitLabel(Label label) { + + @Override + public void visitLabel(Label label) { mv1.visitLabel(label); mv2.visitLabel(label); } - - public void visitLdcInsn(Object cst) { + + @Override + public void visitLdcInsn(Object cst) { mv1.visitLdcInsn(cst); mv2.visitLdcInsn(cst); } - - public void visitIincInsn(int var, int increment) { + + @Override + public void visitIincInsn(int var, int increment) { mv1.visitIincInsn(var, increment); mv2.visitIincInsn(var, increment); } - - public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { mv1.visitTableSwitchInsn(min, max, dflt, labels); mv2.visitTableSwitchInsn(min, max, dflt, labels); } - - public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { mv1.visitLookupSwitchInsn(dflt, keys, labels); mv2.visitLookupSwitchInsn(dflt, keys, labels); } - - public void visitMultiANewArrayInsn(String desc, int dims) { + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { mv1.visitMultiANewArrayInsn(desc, dims); mv2.visitMultiANewArrayInsn(desc, dims); } - - public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { mv1.visitTryCatchBlock(start, end, handler, type); mv2.visitTryCatchBlock(start, end, handler, type); } - - public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { mv1.visitLocalVariable(name, desc, signature, start, end, index); mv2.visitLocalVariable(name, desc, signature, start, end, index); } - - public void visitLineNumber(int line, Label start) { + + @Override + public void visitLineNumber(int line, Label start) { mv1.visitLineNumber(line, start); mv2.visitLineNumber(line, start); } - - public void visitMaxs(int maxStack, int maxLocals) { + + @Override + public void visitMaxs(int maxStack, int maxLocals) { mv1.visitMaxs(maxStack, maxLocals); mv2.visitMaxs(maxStack, maxLocals); } - - public void visitEnd() { + + @Override + public void visitEnd() { mv1.visitEnd(); mv2.visitEnd(); } - public void visitParameter(String name, int access) { + @Override + public void visitParameter(String name, int access) { mv1.visitParameter(name, access); mv2.visitParameter(name, access); } - public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { return AnnotationVisitorTee.getInstance(mv1.visitTypeAnnotation(typeRef, typePath, desc, visible), mv2.visitTypeAnnotation(typeRef, typePath, desc, visible)); } - public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { mv1.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); mv2.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); } - public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { return AnnotationVisitorTee.getInstance(mv1.visitInsnAnnotation(typeRef, typePath, desc, visible), mv2.visitInsnAnnotation(typeRef, typePath, desc, visible)); } - public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { return AnnotationVisitorTee.getInstance(mv1.visitTryCatchAnnotation(typeRef, typePath, desc, visible), mv2.visitTryCatchAnnotation(typeRef, typePath, desc, visible)); } - public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) { + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) { return AnnotationVisitorTee.getInstance(mv1.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, desc, visible), mv2.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, desc, visible)); } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AbstractInterceptFieldCallback.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AbstractInterceptFieldCallback.java index 3ac5bcc36089..e284ddd64dc5 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AbstractInterceptFieldCallback.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AbstractInterceptFieldCallback.java @@ -19,24 +19,42 @@ * @author Chris Nokleberg */ public class AbstractInterceptFieldCallback implements InterceptFieldCallback { - - public int writeInt(Object obj, String name, int oldValue, int newValue) { return newValue; } - public char writeChar(Object obj, String name, char oldValue, char newValue) { return newValue; } - public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { return newValue; } - public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { return newValue; } - public short writeShort(Object obj, String name, short oldValue, short newValue) { return newValue; } - public float writeFloat(Object obj, String name, float oldValue, float newValue) { return newValue; } - public double writeDouble(Object obj, String name, double oldValue, double newValue) { return newValue; } - public long writeLong(Object obj, String name, long oldValue, long newValue) { return newValue; } - public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { return newValue; } - public int readInt(Object obj, String name, int oldValue) { return oldValue; } - public char readChar(Object obj, String name, char oldValue) { return oldValue; } - public byte readByte(Object obj, String name, byte oldValue) { return oldValue; } - public boolean readBoolean(Object obj, String name, boolean oldValue) { return oldValue; } - public short readShort(Object obj, String name, short oldValue) { return oldValue; } - public float readFloat(Object obj, String name, float oldValue) { return oldValue; } - public double readDouble(Object obj, String name, double oldValue) { return oldValue; } - public long readLong(Object obj, String name, long oldValue) { return oldValue; } - public Object readObject(Object obj, String name, Object oldValue) { return oldValue; } + @Override + public int writeInt(Object obj, String name, int oldValue, int newValue) { return newValue; } + @Override + public char writeChar(Object obj, String name, char oldValue, char newValue) { return newValue; } + @Override + public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { return newValue; } + @Override + public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { return newValue; } + @Override + public short writeShort(Object obj, String name, short oldValue, short newValue) { return newValue; } + @Override + public float writeFloat(Object obj, String name, float oldValue, float newValue) { return newValue; } + @Override + public double writeDouble(Object obj, String name, double oldValue, double newValue) { return newValue; } + @Override + public long writeLong(Object obj, String name, long oldValue, long newValue) { return newValue; } + @Override + public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { return newValue; } + + @Override + public int readInt(Object obj, String name, int oldValue) { return oldValue; } + @Override + public char readChar(Object obj, String name, char oldValue) { return oldValue; } + @Override + public byte readByte(Object obj, String name, byte oldValue) { return oldValue; } + @Override + public boolean readBoolean(Object obj, String name, boolean oldValue) { return oldValue; } + @Override + public short readShort(Object obj, String name, short oldValue) { return oldValue; } + @Override + public float readFloat(Object obj, String name, float oldValue) { return oldValue; } + @Override + public double readDouble(Object obj, String name, double oldValue) { return oldValue; } + @Override + public long readLong(Object obj, String name, long oldValue) { return oldValue; } + @Override + public Object readObject(Object obj, String name, Object oldValue) { return oldValue; } } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddDelegateTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddDelegateTransformer.java index b0f70260ecfa..4c36186f7333 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddDelegateTransformer.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddDelegateTransformer.java @@ -65,11 +65,11 @@ public void begin_class(int version, int access, String className, Type superTyp DELEGATE, delegateType, null); - for (int i = 0; i < delegateIf.length; i++) { - Method[] methods = delegateIf[i].getMethods(); - for (int j = 0; j < methods.length; j++) { - if (Modifier.isAbstract(methods[j].getModifiers())) { - addDelegate(methods[j]); + for (Class element : delegateIf) { + Method[] methods = element.getMethods(); + for (Method method : methods) { + if (Modifier.isAbstract(method.getModifiers())) { + addDelegate(method); } } } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddPropertyTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddPropertyTransformer.java index 7dd0c0db9b92..2d2018eb902c 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddPropertyTransformer.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddPropertyTransformer.java @@ -15,10 +15,12 @@ */ package org.springframework.cglib.transform.impl; -import org.springframework.cglib.transform.*; -import java.util.*; -import org.springframework.cglib.core.*; +import java.util.Map; + import org.springframework.asm.Type; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.TypeUtils; +import org.springframework.cglib.transform.ClassEmitterTransformer; @SuppressWarnings({"rawtypes", "unchecked"}) public class AddPropertyTransformer extends ClassEmitterTransformer { @@ -39,7 +41,8 @@ public AddPropertyTransformer(String[] names, Type[] types) { this.types = types; } - public void end_class() { + @Override + public void end_class() { if (!TypeUtils.isAbstract(getAccess())) { EmitUtils.add_properties(this, names, types); } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddStaticInitTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddStaticInitTransformer.java index 6d8ae4cde39d..df37c3838f3f 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddStaticInitTransformer.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddStaticInitTransformer.java @@ -16,9 +16,15 @@ package org.springframework.cglib.transform.impl; import java.lang.reflect.Method; -import org.springframework.cglib.core.*; -import org.springframework.cglib.transform.*; + import org.springframework.asm.Type; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.TypeUtils; +import org.springframework.cglib.transform.ClassEmitterTransformer; /** * @author Juozas Baliuka, Chris Nokleberg @@ -39,7 +45,8 @@ public AddStaticInitTransformer(Method classInit) { } } - protected void init() { + @Override + protected void init() { if (!TypeUtils.isInterface(getAccess())) { CodeEmitter e = getStaticHook(); EmitUtils.load_class_this(e); diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProvider.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProvider.java index b85294fce770..25742e9be0a6 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProvider.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProvider.java @@ -17,17 +17,17 @@ @SuppressWarnings({"rawtypes", "unchecked"}) public interface FieldProvider { - + String[] getFieldNames(); - + Class[] getFieldTypes(); - + void setField(int index, Object value); - + Object getField(int index); void setField(String name, Object value); - + Object getField(String name); } diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldCallback.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldCallback.java index 84225f5c9a24..aab136cb3a16 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldCallback.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldCallback.java @@ -19,7 +19,7 @@ * @author Juozas Baliuka */ public interface InterceptFieldCallback { - + int writeInt(Object obj, String name, int oldValue, int newValue); char writeChar(Object obj, String name, char oldValue, char newValue); byte writeByte(Object obj, String name, byte oldValue, byte newValue); diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldTransformer.java index 2a68dbbb85c4..837beb9d9a59 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldTransformer.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldTransformer.java @@ -15,10 +15,14 @@ */ package org.springframework.cglib.transform.impl; -import org.springframework.cglib.transform.*; -import org.springframework.cglib.core.*; import org.springframework.asm.Label; import org.springframework.asm.Type; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.Local; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.TypeUtils; +import org.springframework.cglib.transform.ClassEmitterTransformer; /** * @author Juozas Baliuka, Chris Nokleberg @@ -35,15 +39,16 @@ public class InterceptFieldTransformer extends ClassEmitterTransformer { new Signature("getInterceptFieldCallback", CALLBACK, new Type[0]); private InterceptFieldFilter filter; - + public InterceptFieldTransformer(InterceptFieldFilter filter) { this.filter = filter; } - - public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) { + + @Override + public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) { if (!TypeUtils.isInterface(access)) { super.begin_class(version, access, className, superType, TypeUtils.add(interfaces, ENABLED), sourceFile); - + super.declare_field(Constants.ACC_PRIVATE | Constants.ACC_TRANSIENT, CALLBACK_FIELD, CALLBACK, @@ -55,7 +60,7 @@ public void begin_class(int version, int access, String className, Type superTyp e.getfield(CALLBACK_FIELD); e.return_value(); e.end_method(); - + e = super.begin_method(Constants.ACC_PUBLIC, ENABLED_SET, null); e.load_this(); e.load_arg(0); @@ -67,7 +72,8 @@ public void begin_class(int version, int access, String className, Type superTyp } } - public void declare_field(int access, String name, Type type, Object value) { + @Override + public void declare_field(int access, String name, Type type, Object value) { super.declare_field(access, name, type, value); if (!TypeUtils.isStatic(access)) { if (filter.acceptRead(getClassType(), name)) { @@ -137,10 +143,12 @@ private void addWriteMethod(String name, Type type) { e.return_value(); e.end_method(); } - - public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { + + @Override + public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { return new CodeEmitter(super.begin_method(access, sig, exceptions)) { - public void visitFieldInsn(int opcode, String owner, String name, String desc) { + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { Type towner = TypeUtils.fromInternalName(owner); switch (opcode) { case Constants.GETFIELD: diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableStrategy.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableStrategy.java index 81633786b618..02667752f5d7 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableStrategy.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableStrategy.java @@ -16,10 +16,10 @@ package org.springframework.cglib.transform.impl; import org.springframework.cglib.core.ClassGenerator; +import org.springframework.cglib.core.ClassTransformer; import org.springframework.cglib.core.DefaultGeneratorStrategy; import org.springframework.cglib.core.GeneratorStrategy; import org.springframework.cglib.core.TypeUtils; -import org.springframework.cglib.core.ClassTransformer; import org.springframework.cglib.transform.MethodFilter; import org.springframework.cglib.transform.MethodFilterTransformer; import org.springframework.cglib.transform.TransformingClassGenerator; @@ -45,14 +45,11 @@ public class UndeclaredThrowableStrategy extends DefaultGeneratorStrategy { public UndeclaredThrowableStrategy(Class wrapper) { this.wrapper = wrapper; } - - private static final MethodFilter TRANSFORM_FILTER = new MethodFilter() { - public boolean accept(int access, String name, String desc, String signature, String[] exceptions) { - return !TypeUtils.isPrivate(access) && name.indexOf('$') < 0; - } - }; - protected ClassGenerator transform(ClassGenerator cg) throws Exception { + private static final MethodFilter TRANSFORM_FILTER = (access, name, desc, signature, exceptions) -> !TypeUtils.isPrivate(access) && name.indexOf('$') < 0; + + @Override + protected ClassGenerator transform(ClassGenerator cg) throws Exception { ClassTransformer tr = new UndeclaredThrowableTransformer(wrapper); tr = new MethodFilterTransformer(TRANSFORM_FILTER, tr); return new TransformingClassGenerator(cg, tr); diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableTransformer.java index 720f1d72ca81..708e1f1ce68a 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableTransformer.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableTransformer.java @@ -36,15 +36,16 @@ public UndeclaredThrowableTransformer(Class wrapper) { this.wrapper = Type.getType(wrapper); boolean found = false; Constructor[] cstructs = wrapper.getConstructors(); - for (int i = 0; i < cstructs.length; i++) { - Class[] types = cstructs[i].getParameterTypes(); + for (Constructor cstruct : cstructs) { + Class[] types = cstruct.getParameterTypes(); if (types.length == 1 && types[0].equals(Throwable.class)) { found = true; break; } } - if (!found) - throw new IllegalArgumentException(wrapper + " does not have a single-arg constructor that takes a Throwable"); + if (!found) { + throw new IllegalArgumentException(wrapper + " does not have a single-arg constructor that takes a Throwable"); + } } @Override diff --git a/spring-core/src/main/java/org/springframework/cglib/util/ParallelSorter.java b/spring-core/src/main/java/org/springframework/cglib/util/ParallelSorter.java index 75051f7ca7db..cce5d6d8c475 100644 --- a/spring-core/src/main/java/org/springframework/cglib/util/ParallelSorter.java +++ b/spring-core/src/main/java/org/springframework/cglib/util/ParallelSorter.java @@ -290,9 +290,9 @@ public void generateClass(ClassVisitor v) throws Exception { if (arrays.length == 0) { throw new IllegalArgumentException("No arrays specified to sort"); } - for (int i = 0; i < arrays.length; i++) { - if (!arrays[i].getClass().isArray()) { - throw new IllegalArgumentException(arrays[i].getClass() + " is not an array"); + for (Object array : arrays) { + if (!array.getClass().isArray()) { + throw new IllegalArgumentException(array.getClass() + " is not an array"); } } new ParallelSorterEmitter(v, getClassName(), arrays); diff --git a/spring-core/src/main/java/org/springframework/cglib/util/SorterTemplate.java b/spring-core/src/main/java/org/springframework/cglib/util/SorterTemplate.java index 081760a9a087..42896a2db617 100644 --- a/spring-core/src/main/java/org/springframework/cglib/util/SorterTemplate.java +++ b/spring-core/src/main/java/org/springframework/cglib/util/SorterTemplate.java @@ -50,10 +50,10 @@ private void quickSortHelper(int lo, int hi) { int v = j; for (;;) { while (compare(++i, v) < 0) { - /* nothing */; + /* nothing */ } while (compare(--j, v) > 0) { - /* nothing */; + /* nothing */ } if (j < i) { break; diff --git a/spring-core/src/main/java/org/springframework/core/CollectionFactory.java b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java index ea284fc269e6..cbda09255006 100644 --- a/spring-core/src/main/java/org/springframework/core/CollectionFactory.java +++ b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java @@ -55,34 +55,31 @@ */ public final class CollectionFactory { - private static final Set> approximableCollectionTypes = new HashSet<>(); + private static final Set> approximableCollectionTypes = Set.of( + // Standard collection interfaces + Collection.class, + List.class, + Set.class, + SortedSet.class, + NavigableSet.class, + // Common concrete collection classes + ArrayList.class, + LinkedList.class, + HashSet.class, + LinkedHashSet.class, + TreeSet.class, + EnumSet.class); - private static final Set> approximableMapTypes = new HashSet<>(); - - - static { - // Standard collection interfaces - approximableCollectionTypes.add(Collection.class); - approximableCollectionTypes.add(List.class); - approximableCollectionTypes.add(Set.class); - approximableCollectionTypes.add(SortedSet.class); - approximableCollectionTypes.add(NavigableSet.class); - approximableMapTypes.add(Map.class); - approximableMapTypes.add(SortedMap.class); - approximableMapTypes.add(NavigableMap.class); - - // Common concrete collection classes - approximableCollectionTypes.add(ArrayList.class); - approximableCollectionTypes.add(LinkedList.class); - approximableCollectionTypes.add(HashSet.class); - approximableCollectionTypes.add(LinkedHashSet.class); - approximableCollectionTypes.add(TreeSet.class); - approximableCollectionTypes.add(EnumSet.class); - approximableMapTypes.add(HashMap.class); - approximableMapTypes.add(LinkedHashMap.class); - approximableMapTypes.add(TreeMap.class); - approximableMapTypes.add(EnumMap.class); - } + private static final Set> approximableMapTypes = Set.of( + // Standard map interfaces + Map.class, + SortedMap.class, + NavigableMap.class, + // Common concrete map classes + HashMap.class, + LinkedHashMap.class, + TreeMap.class, + EnumMap.class); private CollectionFactory() { diff --git a/spring-core/src/main/java/org/springframework/core/Constants.java b/spring-core/src/main/java/org/springframework/core/Constants.java index f94b1842bb68..9cb3e7af7151 100644 --- a/spring-core/src/main/java/org/springframework/core/Constants.java +++ b/spring-core/src/main/java/org/springframework/core/Constants.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -132,7 +132,7 @@ public String asString(String code) throws ConstantException { /** * Parse the given String (upper or lower case accepted) and return * the appropriate value if it's the name of a constant field in the - * class that we're analysing. + * class that we're analyzing. * @param code the name of the field (never {@code null}) * @return the Object value * @throws ConstantException if there's no such field diff --git a/spring-core/src/main/java/org/springframework/core/Conventions.java b/spring-core/src/main/java/org/springframework/core/Conventions.java index be3abc598a95..7eac92ca0d14 100644 --- a/spring-core/src/main/java/org/springframework/core/Conventions.java +++ b/spring-core/src/main/java/org/springframework/core/Conventions.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -169,10 +169,7 @@ public static String getVariableNameForReturnType(Method method, Class resolv Assert.notNull(method, "Method must not be null"); if (Object.class == resolvedType) { - if (value == null) { - throw new IllegalArgumentException( - "Cannot generate variable name for an Object return type with null value"); - } + Assert.notNull(value, "Cannot generate variable name for an Object return type with null value"); return getVariableName(value); } diff --git a/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java index 746599542c57..b7bee1aea84f 100644 --- a/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java +++ b/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java @@ -18,14 +18,12 @@ /** * Default implementation of the {@link ParameterNameDiscoverer} strategy interface, - * using the Java 8 standard reflection mechanism (if available), and falling back - * to the ASM-based {@link LocalVariableTableParameterNameDiscoverer} for checking - * debug information in the class file. + * delegating to the Java 8 standard reflection mechanism, with a deprecated fallback + * to {@link LocalVariableTableParameterNameDiscoverer}. * *

If a Kotlin reflection implementation is present, * {@link KotlinReflectionParameterNameDiscoverer} is added first in the list and - * used for Kotlin classes and interfaces. When compiling or running as a GraalVM - * native image, the {@code KotlinReflectionParameterNameDiscoverer} is not used. + * used for Kotlin classes and interfaces. * *

Further discoverers may be added through {@link #addDiscoverer(ParameterNameDiscoverer)}. * @@ -34,17 +32,24 @@ * @author Sam Brannen * @since 4.0 * @see StandardReflectionParameterNameDiscoverer - * @see LocalVariableTableParameterNameDiscoverer * @see KotlinReflectionParameterNameDiscoverer */ public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer { + @SuppressWarnings("removal") public DefaultParameterNameDiscoverer() { if (KotlinDetector.isKotlinReflectPresent()) { addDiscoverer(new KotlinReflectionParameterNameDiscoverer()); } + + // Recommended approach on Java 8+: compilation with -parameters. addDiscoverer(new StandardReflectionParameterNameDiscoverer()); - addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); + + // Deprecated fallback to class file parsing for -debug symbols. + // Does not work on native images without class file resources. + if (!NativeDetector.inNativeImage()) { + addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); + } } } diff --git a/spring-core/src/main/java/org/springframework/core/KotlinReflectionParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/KotlinReflectionParameterNameDiscoverer.java index cab1ec353010..610cf5d1cb82 100644 --- a/spring-core/src/main/java/org/springframework/core/KotlinReflectionParameterNameDiscoverer.java +++ b/spring-core/src/main/java/org/springframework/core/KotlinReflectionParameterNameDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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,11 +30,13 @@ * {@link ParameterNameDiscoverer} implementation which uses Kotlin's reflection facilities * for introspecting parameter names. * - * Compared to {@link StandardReflectionParameterNameDiscoverer}, it allows in addition to + *

Compared to {@link StandardReflectionParameterNameDiscoverer}, it allows in addition to * determine interface parameter names without requiring Java 8 -parameters compiler flag. * * @author Sebastien Deleuze * @since 5.0 + * @see StandardReflectionParameterNameDiscoverer + * @see DefaultParameterNameDiscoverer */ public class KotlinReflectionParameterNameDiscoverer implements ParameterNameDiscoverer { diff --git a/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java index f20807f1190f..5d666a51f45d 100644 --- a/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java +++ b/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java @@ -47,13 +47,25 @@ * caches the ASM discovered information for each introspected Class, in a thread-safe * manner. It is recommended to reuse ParameterNameDiscoverer instances as far as possible. * + *

This class is deprecated in the 6.0 generation and scheduled for removal in 6.1 + * since it is effectively superseded by {@link StandardReflectionParameterNameDiscoverer}. + * For the time being, this discoverer logs a warning every time it actually inspects a + * class file which is particularly useful for identifying remaining gaps in usage of + * the standard "-parameters" compiler flag, and also unintended over-inspection of + * e.g. JDK core library classes (which are not compiled with the "-parameters" flag). + * * @author Adrian Colyer * @author Costin Leau * @author Juergen Hoeller * @author Chris Beams * @author Sam Brannen * @since 2.0 + * @see StandardReflectionParameterNameDiscoverer + * @see DefaultParameterNameDiscoverer + * @deprecated as of 6.0.1, in favor of {@link StandardReflectionParameterNameDiscoverer} + * (with the "-parameters" compiler flag) */ +@Deprecated(since = "6.0.1", forRemoval = true) public class LocalVariableTableParameterNameDiscoverer implements ParameterNameDiscoverer { private static final Log logger = LogFactory.getLog(LocalVariableTableParameterNameDiscoverer.class); @@ -107,6 +119,10 @@ private Map inspectClass(Class clazz) { ClassReader classReader = new ClassReader(is); Map map = new ConcurrentHashMap<>(32); classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0); + if (logger.isWarnEnabled()) { + logger.warn("Using deprecated '-debug' fallback for parameter name resolution. Compile the " + + "affected code with '-parameters' instead or avoid its introspection: " + clazz.getName()); + } return map; } catch (IOException ex) { 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 5bcb289f794f..0c9cbf060b67 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -339,11 +339,7 @@ void registerAdapters(ReactiveAdapterRegistry registry) { /** * {@code BlockHoundIntegration} for spring-core classes. - *

Explicitly allow the following: - *

    - *
  • Reading class info via {@link LocalVariableTableParameterNameDiscoverer}. - *
  • Locking within {@link ConcurrentReferenceHashMap}. - *
+ * Explicitly allows locking within {@link ConcurrentReferenceHashMap}. * @since 5.2.4 */ public static class SpringCoreBlockHoundIntegration implements BlockHoundIntegration { @@ -352,9 +348,6 @@ public static class SpringCoreBlockHoundIntegration implements BlockHoundIntegra public void applyTo(BlockHound.Builder builder) { // Avoid hard references potentially anywhere in spring-core (no need for structural dependency) - builder.allowBlockingCallsInside( - "org.springframework.core.LocalVariableTableParameterNameDiscoverer", "inspectClass"); - String className = "org.springframework.util.ConcurrentReferenceHashMap$Segment"; builder.allowBlockingCallsInside(className, "doTask"); builder.allowBlockingCallsInside(className, "clear"); diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java index f1d76c330e24..fe56c512b1c4 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -94,7 +94,7 @@ public boolean supportsEmpty() { /** * Return an empty-value instance for the underlying reactive or async type. - * Use of this type implies {@link #supportsEmpty()} is {@code true}. + *

Use of this type implies {@link #supportsEmpty()} is {@code true}. */ public Object getEmptyValue() { Assert.state(this.emptyValueSupplier != null, "Empty values not supported"); diff --git a/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java index 665befa0bcbe..9bce47f435b4 100644 --- a/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java +++ b/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -26,10 +26,15 @@ * {@link ParameterNameDiscoverer} implementation which uses JDK 8's reflection facilities * for introspecting parameter names (based on the "-parameters" compiler flag). * + *

This is a key element of {@link DefaultParameterNameDiscoverer} where it is being + * combined with {@link KotlinReflectionParameterNameDiscoverer} if Kotlin is present. + * * @author Juergen Hoeller * @since 4.0 * @see java.lang.reflect.Method#getParameters() * @see java.lang.reflect.Parameter#getName() + * @see KotlinReflectionParameterNameDiscoverer + * @see DefaultParameterNameDiscoverer */ public class StandardReflectionParameterNameDiscoverer implements ParameterNameDiscoverer { diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 500feec26ebe..1ac4f84a5e80 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -829,8 +829,7 @@ private static MultiValueMap nullIfEmpty(MultiValueMap Comparator> highAggregateIndexesFirst() { - return Comparator.> comparingInt( - MergedAnnotation::getAggregateIndex).reversed(); + return Comparator.> comparingInt(MergedAnnotation::getAggregateIndex).reversed(); } @Nullable @@ -840,8 +839,7 @@ private static AnnotationAttributes getAnnotationAttributes(MergedAnnotation if (!annotation.isPresent()) { return null; } - return annotation.asAnnotationAttributes( - Adapt.values(classValuesAsString, nestedAnnotationsAsMap)); + return annotation.asAnnotationAttributes(Adapt.values(classValuesAsString, nestedAnnotationsAsMap)); } 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 f39b32402621..86c491b7db7f 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 @@ -37,7 +37,6 @@ import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.MirrorSet; import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -262,7 +261,7 @@ private void processAliases(int attributeIndex, List aliases) { mapping.claimedAliases.addAll(aliases); if (mapping.annotation != null) { int[] resolvedMirrors = mapping.mirrorSets.resolve(null, - mapping.annotation, ReflectionUtils::invokeMethod); + mapping.annotation, AnnotationUtils::invokeAnnotationMethod); for (int i = 0; i < mapping.attributes.size(); i++) { if (aliases.contains(mapping.attributes.get(i))) { this.annotationValueMappings[attributeIndex] = resolvedMirrors[i]; @@ -559,7 +558,7 @@ Object getMappedAnnotationValue(int attributeIndex, boolean metaAnnotationsOnly) if (source == this && metaAnnotationsOnly) { return null; } - return ReflectionUtils.invokeMethod(source.attributes.get(mappedIndex), source.annotation); + return AnnotationUtils.invokeAnnotationMethod(source.attributes.get(mappedIndex), source.annotation); } /** @@ -648,7 +647,7 @@ private static boolean areEquivalent(Annotation annotation, @Nullable Object ext AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType()); for (int i = 0; i < attributes.size(); i++) { Method attribute = attributes.get(i); - Object value1 = ReflectionUtils.invokeMethod(attribute, annotation); + Object value1 = AnnotationUtils.invokeAnnotationMethod(attribute, annotation); Object value2; if (extractedValue instanceof TypeMappedAnnotation typeMappedAnnotation) { value2 = typeMappedAnnotation.getValue(attribute.getName()).orElse(null); diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java index 5181deef0782..7166a4232085 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 53add8a19392..99f6e4f4dd14 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -19,7 +19,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; @@ -1050,23 +1050,42 @@ public static Object getValue(@Nullable Annotation annotation, @Nullable String } try { Method method = annotation.annotationType().getDeclaredMethod(attributeName); - ReflectionUtils.makeAccessible(method); - return method.invoke(annotation); + return invokeAnnotationMethod(method, annotation); } catch (NoSuchMethodException ex) { return null; } - catch (InvocationTargetException ex) { - rethrowAnnotationConfigurationException(ex.getTargetException()); - throw new IllegalStateException("Could not obtain value for annotation attribute '" + - attributeName + "' in " + annotation, ex); - } catch (Throwable ex) { + rethrowAnnotationConfigurationException(ex); handleIntrospectionFailure(annotation.getClass(), ex); return null; } } + /** + * Invoke the supplied annotation attribute {@link Method} on the supplied + * {@link Annotation}. + *

An attempt will first be made to invoke the method via the annotation's + * {@link InvocationHandler} (if the annotation instance is a JDK dynamic proxy). + * If that fails, an attempt will be made to invoke the method via reflection. + * @param method the method to invoke + * @param annotation the annotation on which to invoke the method + * @return the value returned from the method invocation + * @since 5.3.24 + */ + static Object invokeAnnotationMethod(Method method, Object annotation) { + if (Proxy.isProxyClass(annotation.getClass())) { + try { + InvocationHandler handler = Proxy.getInvocationHandler(annotation); + return handler.invoke(annotation, method, null); + } + catch (Throwable ex) { + // ignore and fall back to reflection below + } + } + return ReflectionUtils.invokeMethod(method, annotation); + } + /** * If the supplied throwable is an {@link AnnotationConfigurationException}, * it will be cast to an {@code AnnotationConfigurationException} and thrown, 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 d66fb66096b8..dc122981aa0d 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 @@ -86,17 +86,6 @@ private AttributeMethods(@Nullable Class annotationType, M } - /** - * Determine if this instance only contains a single attribute named - * {@code value}. - * @return {@code true} if there is only a value attribute - */ - boolean hasOnlyValueAttribute() { - return (this.attributeMethods.length == 1 && - MergedAnnotation.VALUE.equals(this.attributeMethods[0].getName())); - } - - /** * Determine if values from the given annotation can be safely accessed without * causing any {@link TypeNotPresentException TypeNotPresentExceptions}. @@ -109,7 +98,7 @@ boolean isValid(Annotation annotation) { for (int i = 0; i < size(); i++) { if (canThrowTypeNotPresentException(i)) { try { - get(i).invoke(annotation); + AnnotationUtils.invokeAnnotationMethod(get(i), annotation); } catch (Throwable ex) { return false; @@ -134,7 +123,7 @@ void validate(Annotation annotation) { for (int i = 0; i < size(); i++) { if (canThrowTypeNotPresentException(i)) { try { - get(i).invoke(annotation); + AnnotationUtils.invokeAnnotationMethod(get(i), annotation); } catch (Throwable ex) { throw new IllegalStateException("Could not obtain annotation attribute value for " + 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 e1f7f9f72297..783cf28c9862 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 @@ -18,16 +18,13 @@ import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; -import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import java.util.Map; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; /** * Strategy used to determine annotations that act as containers for other @@ -56,10 +53,12 @@ private RepeatableContainers(@Nullable RepeatableContainers parent) { /** - * Add an additional explicit relationship between a contained and + * Add an additional explicit relationship between a container and * repeatable annotation. - * @param container the container type - * @param repeatable the contained repeatable type + *

WARNING: the arguments supplied to this method are in the reverse order + * of those supplied to {@link #of(Class, Class)}. + * @param container the container annotation type + * @param repeatable the repeatable annotation type * @return a new {@link RepeatableContainers} instance */ public RepeatableContainers and(Class container, @@ -106,7 +105,9 @@ public static RepeatableContainers standardRepeatables() { /** * Create a {@link RepeatableContainers} instance that uses predefined * repeatable and container types. - * @param repeatable the contained repeatable annotation type + *

WARNING: the arguments supplied to this method are in the reverse order + * of those supplied to {@link #and(Class, Class)}. + * @param repeatable the repeatable annotation type * @param container the container annotation type or {@code null}. If specified, * this annotation must declare a {@code value} attribute returning an array * of repeatable annotations. If not specified, the container will be @@ -125,7 +126,7 @@ public static RepeatableContainers of( } /** - * Create a {@link RepeatableContainers} instance that does not expand any + * Create a {@link RepeatableContainers} instance that does not support any * repeatable annotations. * @return a {@link RepeatableContainers} instance */ @@ -133,19 +134,6 @@ public static RepeatableContainers none() { return NoRepeatableContainers.INSTANCE; } - private static Object invokeAnnotationMethod(Annotation annotation, Method method) { - if (Proxy.isProxyClass(annotation.getClass())) { - try { - InvocationHandler handler = Proxy.getInvocationHandler(annotation); - return handler.invoke(annotation, method, null); - } - catch (Throwable ex) { - // ignore and fall back to reflection below - } - } - return ReflectionUtils.invokeMethod(method, annotation); - } - /** * Standard {@link RepeatableContainers} implementation that searches using @@ -168,7 +156,7 @@ private static class StandardRepeatableContainers extends RepeatableContainers { Annotation[] findRepeatedAnnotations(Annotation annotation) { Method method = getRepeatedAnnotationsMethod(annotation.annotationType()); if (method != null) { - return (Annotation[]) invokeAnnotationMethod(annotation, method); + return (Annotation[]) AnnotationUtils.invokeAnnotationMethod(method, annotation); } return super.findRepeatedAnnotations(annotation); } @@ -182,8 +170,8 @@ private static Method getRepeatedAnnotationsMethod(Class a private static Object computeRepeatedAnnotationsMethod(Class annotationType) { AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType); - if (methods.hasOnlyValueAttribute()) { - Method method = methods.get(0); + Method method = methods.get(MergedAnnotation.VALUE); + if (method != null) { Class returnType = method.getReturnType(); if (returnType.isArray()) { Class componentType = returnType.getComponentType(); @@ -224,10 +212,9 @@ private static class ExplicitRepeatableContainer extends RepeatableContainers { } Class returnType = valueMethod.getReturnType(); if (!returnType.isArray() || returnType.getComponentType() != repeatable) { - throw new AnnotationConfigurationException("Container type [" + - container.getName() + - "] must declare a 'value' attribute for an array of type [" + - repeatable.getName() + "]"); + throw new AnnotationConfigurationException( + "Container type [%s] must declare a 'value' attribute for an array of type [%s]" + .formatted(container.getName(), repeatable.getName())); } } catch (AnnotationConfigurationException ex) { @@ -235,9 +222,8 @@ private static class ExplicitRepeatableContainer extends RepeatableContainers { } catch (Throwable ex) { throw new AnnotationConfigurationException( - "Invalid declaration of container type [" + container.getName() + - "] for repeatable annotation [" + repeatable.getName() + "]", - ex); + "Invalid declaration of container type [%s] for repeatable annotation [%s]" + .formatted(container.getName(), repeatable.getName()), ex); } this.repeatable = repeatable; this.container = container; @@ -255,7 +241,7 @@ private Class deduceContainer(Class @Nullable Annotation[] findRepeatedAnnotations(Annotation annotation) { if (this.container.isAssignableFrom(annotation.annotationType())) { - return (Annotation[]) invokeAnnotationMethod(annotation, this.valueMethod); + return (Annotation[]) AnnotationUtils.invokeAnnotationMethod(this.valueMethod, annotation); } return super.findRepeatedAnnotations(annotation); } 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 c8eaf22e411d..69847101d59e 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 @@ -111,7 +111,7 @@ private boolean annotationEquals(Object other) { for (int i = 0; i < this.attributes.size(); i++) { Method attribute = this.attributes.get(i); Object thisValue = getAttributeValue(attribute); - Object otherValue = ReflectionUtils.invokeMethod(attribute, other); + Object otherValue = AnnotationUtils.invokeAnnotationMethod(attribute, other); if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) { return false; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java index b776c40f390f..27ced8751cba 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java @@ -32,7 +32,6 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; /** * {@link MergedAnnotation} that adapts attributes from a root annotation by @@ -42,9 +41,9 @@ * {@code BiFunction}. This allows various different annotation models to be * supported by the same class. For example, the attributes source might be an * actual {@link Annotation} instance where methods on the annotation instance - * are {@linkplain ReflectionUtils#invokeMethod(Method, Object) invoked} to extract - * values. Equally, the source could be a simple {@link Map} with values - * extracted using {@link Map#get(Object)}. + * are {@linkplain AnnotationUtils#invokeAnnotationMethod(Method, Object) invoked} + * to extract values. Similarly, the source could be a simple {@link Map} with + * values extracted using {@link Map#get(Object)}. * *

Extracted root attribute values must be compatible with the attribute * return type, namely: @@ -427,7 +426,7 @@ private Object getValueFromMetaAnnotation(int attributeIndex, boolean forMirrorR } if (value == null) { Method attribute = this.mapping.getAttributes().get(attributeIndex); - value = ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation()); + value = AnnotationUtils.invokeAnnotationMethod(attribute, this.mapping.getAnnotation()); } return value; } @@ -545,7 +544,7 @@ private MergedAnnotation adaptToMergedAnnotation(Object value, Class MergedAnnotation from(@Nullable Object source, A annotation) { Assert.notNull(annotation, "Annotation must not be null"); AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotation.annotationType()); - return new TypeMappedAnnotation<>(mappings.get(0), null, source, annotation, ReflectionUtils::invokeMethod, 0); + return new TypeMappedAnnotation<>( + mappings.get(0), null, source, annotation, AnnotationUtils::invokeAnnotationMethod, 0); } static MergedAnnotation of( @@ -638,7 +638,7 @@ static TypeMappedAnnotation createIfPossible( int aggregateIndex, IntrospectionFailureLogger logger) { return createIfPossible(mapping, source, annotation, - ReflectionUtils::invokeMethod, aggregateIndex, logger); + AnnotationUtils::invokeAnnotationMethod, aggregateIndex, logger); } @Nullable diff --git a/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java b/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java index 031fe10a7dbe..1b41823d367f 100644 --- a/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java +++ b/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java @@ -45,7 +45,7 @@ * Decode from a data buffer stream to a {@code String} stream, either splitting * or aggregating incoming data chunks to realign along newlines delimiters * and produce a stream of strings. This is useful for streaming but is also - * necessary to ensure that that multibyte characters can be decoded correctly, + * necessary to ensure that multi-byte characters can be decoded correctly, * avoiding split-character issues. The default delimiters used by default are * {@code \n} and {@code \r\n} but that can be customized. * diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java b/spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java index c1bc0d2131d2..b603fdc28bbe 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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,7 +57,7 @@ public interface Converter { * @since 5.3 */ default Converter andThen(Converter after) { - Assert.notNull(after, "After Converter must not be null"); + Assert.notNull(after, "'after' Converter must not be null"); return (S s) -> { T initialResult = convert(s); return (initialResult != null ? after.convert(initialResult) : null); diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java index 7ecd15661e61..a313a7938a1d 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java +++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java @@ -114,7 +114,7 @@ private static class ConversionServiceConverter implements Converter public ConversionServiceConverter(ConversionService conversionService, Class targetType) { Assert.notNull(conversionService, "ConversionService must not be null"); - Assert.notNull(targetType, "TargetType must not be null"); + Assert.notNull(targetType, "'targetType' must not be null"); this.conversionService = conversionService; this.targetType = targetType; } diff --git a/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java index 507fbdb8c483..857e87f73918 100644 --- a/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java +++ b/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -37,7 +37,7 @@ *

  * ConfigurableEnvironment environment = new StandardEnvironment();
  * MutablePropertySources propertySources = environment.getPropertySources();
- * Map<String, String> myMap = new HashMap<>();
+ * Map<String, Object> myMap = new HashMap<>();
  * myMap.put("xyz", "myValue");
  * propertySources.addFirst(new MapPropertySource("MY_MAP", myMap));
  * 
diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertySource.java b/spring-core/src/main/java/org/springframework/core/env/PropertySource.java index 868fdfa5059d..a88223c941cb 100644 --- a/spring-core/src/main/java/org/springframework/core/env/PropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/PropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -134,9 +134,9 @@ public boolean containsProperty(String name) { *

No properties other than {@code name} are evaluated. */ @Override - public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof PropertySource && - ObjectUtils.nullSafeEquals(getName(), ((PropertySource) other).getName()))); + public boolean equals(@Nullable Object obj) { + return (this == obj || (obj instanceof PropertySource other && + ObjectUtils.nullSafeEquals(getName(), other.getName()))); } /** diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java index 5bb1185dcf46..89a97883c23d 100644 --- a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java @@ -117,8 +117,7 @@ boolean checkReadable(URL url) { return false; } } - else if (con instanceof JarURLConnection) { - JarURLConnection jarCon = (JarURLConnection) con; + else if (con instanceof JarURLConnection jarCon) { JarEntry jarEntry = jarCon.getJarEntry(); if (jarEntry == null) { return false; diff --git a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java index ef24b3803e68..27c5bf830603 100644 --- a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java +++ b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java @@ -162,7 +162,7 @@ else if (location.startsWith(CLASSPATH_URL_PREFIX)) { else { try { // Try to parse the location as a URL... - URL url = new URL(location); + URL url = ResourceUtils.toURL(location); return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); } catch (MalformedURLException ex) { diff --git a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java index 72e43ae4efd9..ddda76455c4c 100644 --- a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java @@ -31,6 +31,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** @@ -57,10 +58,10 @@ public class UrlResource extends AbstractFileResolvingResource { private final URL url; /** - * Cleaned URL (with normalized path), used for comparisons. + * Cleaned URL String (with normalized path), used for comparisons. */ @Nullable - private volatile URL cleanedUrl; + private volatile String cleanedUrl; /** @@ -96,9 +97,25 @@ public UrlResource(URI uri) throws MalformedURLException { */ public UrlResource(String path) throws MalformedURLException { Assert.notNull(path, "Path must not be null"); + + // Equivalent without java.net.URL constructor - for building on JDK 20+ + /* + try { + String cleanedPath = StringUtils.cleanPath(path); + this.uri = ResourceUtils.toURI(cleanedPath); + this.url = this.uri.toURL(); + this.cleanedUrl = cleanedPath; + } + catch (URISyntaxException | IllegalArgumentException ex) { + MalformedURLException exToThrow = new MalformedURLException(ex.getMessage()); + exToThrow.initCause(ex); + throw exToThrow; + } + */ + this.uri = null; - this.url = new URL(path); - this.cleanedUrl = getCleanedUrl(this.url, path); + this.url = ResourceUtils.toURL(path); + this.cleanedUrl = StringUtils.cleanPath(path); } /** @@ -144,7 +161,7 @@ public UrlResource(String protocol, String location, @Nullable String fragment) * Create a new {@code UrlResource} from the given {@link URI}. *

This factory method is a convenience for {@link #UrlResource(URI)} that * catches any {@link MalformedURLException} and rethrows it wrapped in an - * {@link UncheckedIOException}; suitable for use in {@link java.util.Stream} + * {@link UncheckedIOException}; suitable for use in {@link java.util.stream.Stream} * and {@link java.util.Optional} APIs or other scenarios when a checked * {@link IOException} is undesirable. * @param uri a URI @@ -165,7 +182,7 @@ public static UrlResource from(URI uri) throws UncheckedIOException { * Create a new {@code UrlResource} from the given URL path. *

This factory method is a convenience for {@link #UrlResource(String)} * that catches any {@link MalformedURLException} and rethrows it wrapped in an - * {@link UncheckedIOException}; suitable for use in {@link java.util.Stream} + * {@link UncheckedIOException}; suitable for use in {@link java.util.stream.Stream} * and {@link java.util.Optional} APIs or other scenarios when a checked * {@link IOException} is undesirable. * @param path a URL path @@ -183,36 +200,16 @@ public static UrlResource from(String path) throws UncheckedIOException { } - /** - * Determine a cleaned URL for the given original URL. - * @param originalUrl the original URL - * @param originalPath the original URL path - * @return the cleaned URL (possibly the original URL as-is) - * @see org.springframework.util.StringUtils#cleanPath - */ - private static URL getCleanedUrl(URL originalUrl, String originalPath) { - String cleanedPath = StringUtils.cleanPath(originalPath); - if (!cleanedPath.equals(originalPath)) { - try { - return new URL(cleanedPath); - } - catch (MalformedURLException ex) { - // Cleaned URL path cannot be converted to URL -> take original URL. - } - } - return originalUrl; - } - /** * Lazily determine a cleaned URL for the given original URL. - * @see #getCleanedUrl(URL, String) */ - private URL getCleanedUrl() { - URL cleanedUrl = this.cleanedUrl; + private String getCleanedUrl() { + String cleanedUrl = this.cleanedUrl; if (cleanedUrl != null) { return cleanedUrl; } - cleanedUrl = getCleanedUrl(this.url, (this.uri != null ? this.uri : this.url).toString()); + String originalPath = (this.uri != null ? this.uri : this.url).toString(); + cleanedUrl = StringUtils.cleanPath(originalPath); this.cleanedUrl = cleanedUrl; return cleanedUrl; } @@ -305,16 +302,13 @@ public Resource createRelative(String relativePath) throws MalformedURLException * A leading slash will get dropped; a "#" symbol will get encoded. * @since 5.2 * @see #createRelative(String) - * @see java.net.URL#URL(java.net.URL, String) + * @see ResourceUtils#toRelativeURL(URL, String) */ protected URL createRelativeURL(String relativePath) throws MalformedURLException { if (relativePath.startsWith("/")) { relativePath = relativePath.substring(1); } - // # can appear in filenames, java.net.URL should not treat it as a fragment - relativePath = StringUtils.replace(relativePath, "#", "%23"); - // Use the URL constructor for applying the relative path as a URL spec - return new URL(this.url, relativePath); + return ResourceUtils.toRelativeURL(this.url, relativePath); } /** @@ -324,9 +318,16 @@ protected URL createRelativeURL(String relativePath) throws MalformedURLExceptio * @see java.net.URLDecoder#decode(String, java.nio.charset.Charset) */ @Override + @Nullable public String getFilename() { - String filename = StringUtils.getFilename(getCleanedUrl().getPath()); - return URLDecoder.decode(filename, StandardCharsets.UTF_8); + if (this.uri != null) { + // URI path is decoded and has standard separators + return StringUtils.getFilename(this.uri.getPath()); + } + else { + String filename = StringUtils.getFilename(StringUtils.cleanPath(this.url.getPath())); + return (filename != null ? URLDecoder.decode(filename, StandardCharsets.UTF_8) : null); + } } /** @@ -334,7 +335,7 @@ public String getFilename() { */ @Override public String getDescription() { - return "URL [" + this.url + "]"; + return "URL [" + (this.uri != null ? this.uri : this.url) + "]"; } diff --git a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java index ffb9374033e1..3574a6e67c33 100644 --- a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java @@ -24,6 +24,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; /** * JBoss VFS based {@link Resource} implementation. @@ -115,7 +116,7 @@ public Resource createRelative(String relativePath) throws IOException { } } - return new VfsResource(VfsUtils.getRelative(new URL(getURL(), relativePath))); + return new VfsResource(VfsUtils.getRelative(ResourceUtils.toRelativeURL(getURL(), relativePath))); } @Override diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBuffer.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBuffer.java index a39ee3453e46..ec41e5876d17 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBuffer.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBuffer.java @@ -417,7 +417,7 @@ default InputStream asInputStream() { */ default InputStream asInputStream(boolean releaseOnClose) { return new DataBufferInputStream(this, releaseOnClose); - }; + } /** * Expose this buffer's data as an {@link OutputStream}. Both data and write position are diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferInputStream.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferInputStream.java index e8a1b179a7a6..33135285ae95 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferInputStream.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferInputStream.java @@ -77,8 +77,9 @@ public boolean markSupported() { } @Override - public void mark(int mark) { - this.mark = mark; + public void mark(int readLimit) { + Assert.isTrue(readLimit > 0, "readLimit must be greater than 0"); + this.mark = this.dataBuffer.readPosition(); } @Override diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java index 8ad237c7f3a2..d255f1e28146 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java @@ -100,7 +100,7 @@ public static Flux readByteChannel( Callable channelSupplier, DataBufferFactory bufferFactory, int bufferSize) { Assert.notNull(channelSupplier, "'channelSupplier' must not be null"); - Assert.notNull(bufferFactory, "'dataBufferFactory' must not be null"); + Assert.notNull(bufferFactory, "'bufferFactory' must not be null"); Assert.isTrue(bufferSize > 0, "'bufferSize' must be > 0"); return Flux.using(channelSupplier, @@ -140,7 +140,7 @@ public static Flux readAsynchronousFileChannel( DataBufferFactory bufferFactory, int bufferSize) { Assert.notNull(channelSupplier, "'channelSupplier' must not be null"); - Assert.notNull(bufferFactory, "'dataBufferFactory' must not be null"); + Assert.notNull(bufferFactory, "'bufferFactory' must not be null"); Assert.isTrue(position >= 0, "'position' must be >= 0"); Assert.isTrue(bufferSize > 0, "'bufferSize' must be > 0"); @@ -172,7 +172,7 @@ public static Flux read( Path path, DataBufferFactory bufferFactory, int bufferSize, OpenOption... options) { Assert.notNull(path, "Path must not be null"); - Assert.notNull(bufferFactory, "BufferFactory must not be null"); + Assert.notNull(bufferFactory, "DataBufferFactory must not be null"); Assert.isTrue(bufferSize > 0, "'bufferSize' must be > 0"); if (options.length > 0) { for (OpenOption option : options) { @@ -420,7 +420,7 @@ static void closeChannel(@Nullable Channel channel) { @SuppressWarnings("unchecked") public static Flux takeUntilByteCount(Publisher publisher, long maxByteCount) { Assert.notNull(publisher, "Publisher must not be null"); - Assert.isTrue(maxByteCount >= 0, "'maxByteCount' must be a positive number"); + Assert.isTrue(maxByteCount >= 0, "'maxByteCount' must be >= 0"); return Flux.defer(() -> { AtomicLong countDown = new AtomicLong(maxByteCount); @@ -453,7 +453,7 @@ public static Flux takeUntilByteCount(Publisher pub */ public static Flux skipUntilByteCount(Publisher publisher, long maxByteCount) { Assert.notNull(publisher, "Publisher must not be null"); - Assert.isTrue(maxByteCount >= 0, "'maxByteCount' must be a positive number"); + Assert.isTrue(maxByteCount >= 0, "'maxByteCount' must be >= 0"); return Flux.defer(() -> { AtomicLong countDown = new AtomicLong(maxByteCount); @@ -590,7 +590,7 @@ public static Mono join(Publisher dataBuffers) */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static Mono join(Publisher buffers, int maxByteCount) { - Assert.notNull(buffers, "'dataBuffers' must not be null"); + Assert.notNull(buffers, "'buffers' must not be null"); if (buffers instanceof Mono mono) { return mono; diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DefaultDataBuffer.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DefaultDataBuffer.java index 2d3645cc86f5..23bdbd0cf8e7 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/DefaultDataBuffer.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DefaultDataBuffer.java @@ -425,7 +425,7 @@ public String toString(int index, int length, Charset charset) { * @see io.netty.buffer.AbstractByteBufAllocator#calculateNewCapacity(int, int) */ private int calculateCapacity(int neededCapacity) { - Assert.isTrue(neededCapacity >= 0, "'neededCapacity' must >= 0"); + Assert.isTrue(neededCapacity >= 0, "'neededCapacity' must be >= 0"); if (neededCapacity == CAPACITY_THRESHOLD) { return CAPACITY_THRESHOLD; diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBuffer.java b/spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBuffer.java index 64b780425575..6d79ef166747 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBuffer.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/Netty5DataBuffer.java @@ -28,7 +28,6 @@ import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; - /** * Implementation of the {@code DataBuffer} interface that wraps a Netty 5 * {@link Buffer}. Typically constructed with {@link Netty5DataBufferFactory}. @@ -37,8 +36,7 @@ * @author Arjen Poutsma * @since 6.0 */ -public final class Netty5DataBuffer implements CloseableDataBuffer, - TouchableDataBuffer { +public final class Netty5DataBuffer implements CloseableDataBuffer, TouchableDataBuffer { private final Buffer buffer; @@ -51,7 +49,7 @@ public final class Netty5DataBuffer implements CloseableDataBuffer, */ Netty5DataBuffer(Buffer buffer, Netty5DataBufferFactory dataBufferFactory) { Assert.notNull(buffer, "Buffer must not be null"); - Assert.notNull(dataBufferFactory, "NettyDataBufferFactory must not be null"); + Assert.notNull(dataBufferFactory, "Netty5DataBufferFactory must not be null"); this.buffer = buffer; this.dataBufferFactory = dataBufferFactory; } @@ -150,7 +148,7 @@ public Netty5DataBuffer capacity(int capacity) { @Override public DataBuffer ensureWritable(int capacity) { - Assert.isTrue(capacity >= 0, "Capacity must be larger than 0"); + Assert.isTrue(capacity >= 0, "Capacity must be >= 0"); this.buffer.ensureWritable(capacity); return this; } @@ -328,6 +326,7 @@ public void close() { } + @Override public boolean equals(@Nullable Object other) { return (this == other || (other instanceof Netty5DataBuffer dataBuffer && this.buffer.equals(dataBuffer.buffer))); diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java index a02c9460333a..b7c49c10e431 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java index 85fe14691beb..af5d58e8ec9e 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java index 58835cc229e5..0374bbab9e8d 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java @@ -77,9 +77,8 @@ public void processPropertySource(PropertySourceDescriptor descriptor) throws IO List locations = descriptor.locations(); Assert.isTrue(locations.size() > 0, "At least one @PropertySource(value) location is required"); boolean ignoreResourceNotFound = descriptor.ignoreResourceNotFound(); - PropertySourceFactory factory = (descriptor.propertySourceFactory() != null - ? instantiateClass(descriptor.propertySourceFactory()) - : DEFAULT_PROPERTY_SOURCE_FACTORY); + PropertySourceFactory factory = (descriptor.propertySourceFactory() != null ? + instantiateClass(descriptor.propertySourceFactory()) : DEFAULT_PROPERTY_SOURCE_FACTORY); for (String location : locations) { try { @@ -109,14 +108,14 @@ private void addPropertySource(org.springframework.core.env.PropertySource pr // We've already added a version, we need to extend it org.springframework.core.env.PropertySource existing = propertySources.get(name); if (existing != null) { - PropertySource newSource = (propertySource instanceof ResourcePropertySource ? - ((ResourcePropertySource) propertySource).withResourceName() : propertySource); - if (existing instanceof CompositePropertySource) { - ((CompositePropertySource) existing).addFirstPropertySource(newSource); + PropertySource newSource = (propertySource instanceof ResourcePropertySource rps ? + rps.withResourceName() : propertySource); + if (existing instanceof CompositePropertySource cps) { + cps.addFirstPropertySource(newSource); } else { - if (existing instanceof ResourcePropertySource) { - existing = ((ResourcePropertySource) existing).withResourceName(); + if (existing instanceof ResourcePropertySource rps) { + existing = rps.withResourceName(); } CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource(newSource); diff --git a/spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java b/spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java index dfa7b679385c..cd1cad5a0467 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -45,8 +45,8 @@ public class ResourceRegion { */ public ResourceRegion(Resource resource, long position, long count) { Assert.notNull(resource, "Resource must not be null"); - Assert.isTrue(position >= 0, "'position' must be larger than or equal to 0"); - Assert.isTrue(count >= 0, "'count' must be larger than or equal to 0"); + Assert.isTrue(position >= 0, "'position' must be greater than or equal to 0"); + Assert.isTrue(count >= 0, "'count' must be greater than or equal to 0"); this.resource = resource; this.position = position; this.count = count; diff --git a/spring-core/src/main/java/org/springframework/core/metrics/jfr/FlightRecorderApplicationStartup.java b/spring-core/src/main/java/org/springframework/core/metrics/jfr/FlightRecorderApplicationStartup.java index 2e47644cd6c0..60f739a0d8ff 100644 --- a/spring-core/src/main/java/org/springframework/core/metrics/jfr/FlightRecorderApplicationStartup.java +++ b/spring-core/src/main/java/org/springframework/core/metrics/jfr/FlightRecorderApplicationStartup.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java index ad8df6836628..96874edb2eac 100644 --- a/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java +++ b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java index ec520d3fa151..396bc0b51882 100644 --- a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java +++ b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java @@ -113,7 +113,7 @@ public AntPathMatcher() { * @since 4.1 */ public AntPathMatcher(String pathSeparator) { - Assert.notNull(pathSeparator, "'pathSeparator' is required"); + Assert.notNull(pathSeparator, "'pathSeparator' must not be null"); this.pathSeparator = pathSeparator; this.pathSeparatorPatternCache = new PathSeparatorPatternCache(pathSeparator); } @@ -529,7 +529,7 @@ public Map extractUriTemplateVariables(String pattern, String pa * the first pattern contains a file extension match (e.g., {@code *.html}). * In that case, the second pattern will be merged into the first. Otherwise, * an {@code IllegalArgumentException} will be thrown. - *

Examples

+ *

Examples

* * * diff --git a/spring-core/src/main/java/org/springframework/util/Assert.java b/spring-core/src/main/java/org/springframework/util/Assert.java index 786b9b9463e5..c006ea6634fd 100644 --- a/spring-core/src/main/java/org/springframework/util/Assert.java +++ b/spring-core/src/main/java/org/springframework/util/Assert.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -101,9 +101,10 @@ public static void state(boolean expression, Supplier messageSupplier) { /** * Assert a boolean expression, throwing an {@code IllegalStateException} * if the expression evaluates to {@code false}. - * @deprecated as of 4.3.7, in favor of {@link #state(boolean, String)} + * @deprecated as of 4.3.7, in favor of {@link #state(boolean, String)}; + * to be removed in 6.1 */ - @Deprecated + @Deprecated(forRemoval = true) public static void state(boolean expression) { state(expression, "[Assertion failed] - this state invariant must be true"); } @@ -143,9 +144,10 @@ public static void isTrue(boolean expression, Supplier messageSupplier) /** * Assert a boolean expression, throwing an {@code IllegalArgumentException} * if the expression evaluates to {@code false}. - * @deprecated as of 4.3.7, in favor of {@link #isTrue(boolean, String)} + * @deprecated as of 4.3.7, in favor of {@link #isTrue(boolean, String)}; + * to be removed in 6.1 */ - @Deprecated + @Deprecated(forRemoval = true) public static void isTrue(boolean expression) { isTrue(expression, "[Assertion failed] - this expression must be true"); } @@ -182,9 +184,10 @@ public static void isNull(@Nullable Object object, Supplier messageSuppl /** * Assert that an object is {@code null}. - * @deprecated as of 4.3.7, in favor of {@link #isNull(Object, String)} + * @deprecated as of 4.3.7, in favor of {@link #isNull(Object, String)}; + * to be removed in 6.1 */ - @Deprecated + @Deprecated(forRemoval = true) public static void isNull(@Nullable Object object) { isNull(object, "[Assertion failed] - the object argument must be null"); } @@ -222,9 +225,10 @@ public static void notNull(@Nullable Object object, Supplier messageSupp /** * Assert that an object is not {@code null}. - * @deprecated as of 4.3.7, in favor of {@link #notNull(Object, String)} + * @deprecated as of 4.3.7, in favor of {@link #notNull(Object, String)}; + * to be removed in 6.1 */ - @Deprecated + @Deprecated(forRemoval = true) public static void notNull(@Nullable Object object) { notNull(object, "[Assertion failed] - this argument is required; it must not be null"); } @@ -267,9 +271,10 @@ public static void hasLength(@Nullable String text, Supplier messageSupp /** * Assert that the given String is not empty; that is, * it must not be {@code null} and not the empty String. - * @deprecated as of 4.3.7, in favor of {@link #hasLength(String, String)} + * @deprecated as of 4.3.7, in favor of {@link #hasLength(String, String)}; + * to be removed in 6.1 */ - @Deprecated + @Deprecated(forRemoval = true) public static void hasLength(@Nullable String text) { hasLength(text, "[Assertion failed] - this String argument must have length; it must not be null or empty"); @@ -313,9 +318,10 @@ public static void hasText(@Nullable String text, Supplier messageSuppli /** * Assert that the given String contains valid text content; that is, it must not * be {@code null} and must contain at least one non-whitespace character. - * @deprecated as of 4.3.7, in favor of {@link #hasText(String, String)} + * @deprecated as of 4.3.7, in favor of {@link #hasText(String, String)}; + * to be removed in 6.1 */ - @Deprecated + @Deprecated(forRemoval = true) public static void hasText(@Nullable String text) { hasText(text, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank"); @@ -357,9 +363,10 @@ public static void doesNotContain(@Nullable String textToSearch, String substrin /** * Assert that the given text does not contain the given substring. - * @deprecated as of 4.3.7, in favor of {@link #doesNotContain(String, String, String)} + * @deprecated as of 4.3.7, in favor of {@link #doesNotContain(String, String, String)}; + * to be removed in 6.1 */ - @Deprecated + @Deprecated(forRemoval = true) public static void doesNotContain(@Nullable String textToSearch, String substring) { doesNotContain(textToSearch, substring, () -> "[Assertion failed] - this String argument must not contain the substring [" + substring + "]"); @@ -400,9 +407,10 @@ public static void notEmpty(@Nullable Object[] array, Supplier messageSu /** * Assert that an array contains elements; that is, it must not be * {@code null} and must contain at least one element. - * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Object[], String)} + * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Object[], String)}; + * to be removed in 6.1 */ - @Deprecated + @Deprecated(forRemoval = true) public static void notEmpty(@Nullable Object[] array) { notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element"); } @@ -449,9 +457,10 @@ public static void noNullElements(@Nullable Object[] array, Supplier mes /** * Assert that an array contains no {@code null} elements. - * @deprecated as of 4.3.7, in favor of {@link #noNullElements(Object[], String)} + * @deprecated as of 4.3.7, in favor of {@link #noNullElements(Object[], String)}; + * to be removed in 6.1 */ - @Deprecated + @Deprecated(forRemoval = true) public static void noNullElements(@Nullable Object[] array) { noNullElements(array, "[Assertion failed] - this array must not contain any null elements"); } @@ -493,9 +502,10 @@ public static void notEmpty(@Nullable Collection collection, Supplier /** * Assert that a collection contains elements; that is, it must not be * {@code null} and must contain at least one element. - * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Collection, String)} + * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Collection, String)}; + * to be removed in 6.1 */ - @Deprecated + @Deprecated(forRemoval = true) public static void notEmpty(@Nullable Collection collection) { notEmpty(collection, "[Assertion failed] - this collection must not be empty: it must contain at least 1 element"); @@ -577,9 +587,10 @@ public static void notEmpty(@Nullable Map map, Supplier messageSup /** * Assert that a Map contains entries; that is, it must not be {@code null} * and must contain at least one entry. - * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Map, String)} + * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Map, String)}; + * to be removed in 6.1 */ - @Deprecated + @Deprecated(forRemoval = true) public static void notEmpty(@Nullable Map map) { notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry"); } diff --git a/spring-core/src/main/java/org/springframework/util/ConcurrentLruCache.java b/spring-core/src/main/java/org/springframework/util/ConcurrentLruCache.java index 4104ccbf511e..8e93e2449851 100644 --- a/spring-core/src/main/java/org/springframework/util/ConcurrentLruCache.java +++ b/spring-core/src/main/java/org/springframework/util/ConcurrentLruCache.java @@ -21,18 +21,18 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongArray; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import org.springframework.lang.Nullable; - /** * Simple LRU (Least Recently Used) cache, bounded by a specified cache capacity. - *

This is a simplified, opinionated implementation of a LRU cache for internal + *

This is a simplified, opinionated implementation of an LRU cache for internal * use in Spring Framework. It is inspired from * ConcurrentLinkedHashMap. *

Read and write operations are internally recorded in dedicated buffers, @@ -81,7 +81,7 @@ public ConcurrentLruCache(int capacity, Function generator) { } private ConcurrentLruCache(int capacity, Function generator, int concurrencyLevel) { - Assert.isTrue(capacity > 0, "Capacity should be > 0"); + Assert.isTrue(capacity > 0, "Capacity must be > 0"); this.capacity = capacity; this.cache = new ConcurrentHashMap<>(16, 0.75f, concurrencyLevel); this.generator = generator; @@ -106,8 +106,8 @@ public V get(K key) { } private void put(K key, V value) { - Assert.notNull(key, "key should not be null"); - Assert.notNull(value, "value should not be null"); + Assert.notNull(key, "key must not be null"); + Assert.notNull(value, "value must not be null"); final CacheEntry cacheEntry = new CacheEntry<>(value, CacheEntryState.ACTIVE); final Node node = new Node<>(key, cacheEntry); final Node prior = this.cache.putIfAbsent(node.key, node); @@ -359,7 +359,8 @@ private static final class ReadOperations { private static int detectNumberOfBuffers() { int availableProcessors = Runtime.getRuntime().availableProcessors(); - return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(availableProcessors - 1)); + int nextPowerOfTwo = 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(availableProcessors - 1)); + return Math.min(4, nextPowerOfTwo); } private static final int BUFFERS_MASK = BUFFER_COUNT - 1; @@ -375,7 +376,7 @@ private static int detectNumberOfBuffers() { /* * Number of operations recorded, for each buffer */ - private final AtomicLong[] recordedCount = new AtomicLong[BUFFER_COUNT]; + private final AtomicLongArray recordedCount = new AtomicLongArray(BUFFER_COUNT); /* * Number of operations read, for each buffer @@ -385,10 +386,10 @@ private static int detectNumberOfBuffers() { /* * Number of operations processed, for each buffer */ - private final AtomicLong[] processedCount = new AtomicLong[BUFFER_COUNT]; + private final AtomicLongArray processedCount = new AtomicLongArray(BUFFER_COUNT); @SuppressWarnings("rawtypes") - private final AtomicReference>[][] buffers = new AtomicReference[BUFFER_COUNT][BUFFER_SIZE]; + private final AtomicReferenceArray>[] buffers = new AtomicReferenceArray[BUFFER_COUNT]; private final EvictionQueue evictionQueue; @@ -396,12 +397,7 @@ private static int detectNumberOfBuffers() { ReadOperations(EvictionQueue evictionQueue) { this.evictionQueue = evictionQueue; for (int i = 0; i < BUFFER_COUNT; i++) { - this.recordedCount[i] = new AtomicLong(); - this.processedCount[i] = new AtomicLong(); - this.buffers[i] = new AtomicReference[BUFFER_SIZE]; - for (int j = 0; j < BUFFER_SIZE; j++) { - this.buffers[i][j] = new AtomicReference<>(); - } + this.buffers[i] = new AtomicReferenceArray<>(BUFFER_SIZE); } } @@ -411,12 +407,11 @@ private static int getBufferIndex() { boolean recordRead(Node node) { int bufferIndex = getBufferIndex(); - final AtomicLong counter = this.recordedCount[bufferIndex]; - final long writeCount = counter.get(); - counter.lazySet(writeCount + 1); + final long writeCount = this.recordedCount.get(bufferIndex); + this.recordedCount.lazySet(bufferIndex, writeCount + 1); final int index = (int) (writeCount & BUFFER_INDEX_MASK); - this.buffers[bufferIndex][index].lazySet(node); - final long pending = (writeCount - this.processedCount[bufferIndex].get()); + this.buffers[bufferIndex].lazySet(index, node); + final long pending = (writeCount - this.processedCount.get(bufferIndex)); return (pending < MAX_PENDING_OPERATIONS); } @@ -429,27 +424,28 @@ void drain() { } void clear() { - for (AtomicReference>[] buffer : this.buffers) { - for (AtomicReference> slot : buffer) { - slot.lazySet(null); + for (int i = 0; i < BUFFER_COUNT; i++) { + AtomicReferenceArray> buffer = this.buffers[i]; + for (int j = 0; j < BUFFER_SIZE; j++) { + buffer.lazySet(j, null); } } } private void drainReadBuffer(int bufferIndex) { - final long writeCount = this.recordedCount[bufferIndex].get(); + final long writeCount = this.recordedCount.get(bufferIndex); for (int i = 0; i < MAX_DRAIN_COUNT; i++) { final int index = (int) (this.readCount[bufferIndex] & BUFFER_INDEX_MASK); - final AtomicReference> slot = this.buffers[bufferIndex][index]; - final Node node = slot.get(); + final AtomicReferenceArray> buffer = this.buffers[bufferIndex]; + final Node node = buffer.get(index); if (node == null) { break; } - slot.lazySet(null); + buffer.lazySet(index, null); this.evictionQueue.moveToBack(node); this.readCount[bufferIndex]++; } - this.processedCount[bufferIndex].lazySet(writeCount); + this.processedCount.lazySet(bufferIndex, writeCount); } } diff --git a/spring-core/src/main/java/org/springframework/util/FileCopyUtils.java b/spring-core/src/main/java/org/springframework/util/FileCopyUtils.java index 73140dd80e70..d947f0d9407f 100644 --- a/spring-core/src/main/java/org/springframework/util/FileCopyUtils.java +++ b/spring-core/src/main/java/org/springframework/util/FileCopyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/main/java/org/springframework/util/MimeType.java b/spring-core/src/main/java/org/springframework/util/MimeType.java index 7fe9a46e9ff2..a1464d82217e 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeType.java +++ b/spring-core/src/main/java/org/springframework/util/MimeType.java @@ -708,7 +708,7 @@ private static Map addCharsetParameter(Charset charset, Map the type of mime types that may be compared by this comparator * @deprecated As of 6.0, with no direct replacement */ - @Deprecated(since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) public static class SpecificityComparator implements Comparator { @Override diff --git a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java index 0a76878a82f0..b15948a6a4b3 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java +++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java @@ -55,7 +55,8 @@ public abstract class MimeTypeUtils { * Comparator formally used by {@link #sortBySpecificity(List)}. * @deprecated As of 6.0, with no direct replacement */ - @Deprecated(since = "6.0") + @SuppressWarnings("removal") + @Deprecated(since = "6.0", forRemoval = true) public static final Comparator SPECIFICITY_COMPARATOR = new MimeType.SpecificityComparator<>(); /** diff --git a/spring-core/src/main/java/org/springframework/util/PropertiesPersister.java b/spring-core/src/main/java/org/springframework/util/PropertiesPersister.java index 0c8248dc7469..3cdd816bbd9c 100644 --- a/spring-core/src/main/java/org/springframework/util/PropertiesPersister.java +++ b/spring-core/src/main/java/org/springframework/util/PropertiesPersister.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java index 2caaf33597f7..110c9342105f 100644 --- a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -109,7 +109,7 @@ public static boolean isUrl(@Nullable String resourceLocation) { return true; } try { - new URL(resourceLocation); + toURL(resourceLocation); return true; } catch (MalformedURLException ex) { @@ -141,7 +141,7 @@ public static URL getURL(String resourceLocation) throws FileNotFoundException { } try { // try URL - return new URL(resourceLocation); + return toURL(resourceLocation); } catch (MalformedURLException ex) { // no URL -> treat as file path @@ -181,7 +181,7 @@ public static File getFile(String resourceLocation) throws FileNotFoundException } try { // try URL - return getFile(new URL(resourceLocation)); + return getFile(toURL(resourceLocation)); } catch (MalformedURLException ex) { // no URL -> treat as file path @@ -311,7 +311,7 @@ public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException { if (separatorIndex != -1) { String jarFile = urlFile.substring(0, separatorIndex); try { - return new URL(jarFile); + return toURL(jarFile); } catch (MalformedURLException ex) { // Probably no protocol in original jar URL, like "jar:C:/mypath/myjar.jar". @@ -319,7 +319,7 @@ public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException { if (!jarFile.startsWith("/")) { jarFile = "/" + jarFile; } - return new URL(FILE_URL_PREFIX + jarFile); + return toURL(FILE_URL_PREFIX + jarFile); } } else { @@ -346,11 +346,11 @@ public static URL extractArchiveURL(URL jarUrl) throws MalformedURLException { // Tomcat's "war:file:...mywar.war*/WEB-INF/lib/myjar.jar!/myentry.txt" String warFile = urlFile.substring(0, endIndex); if (URL_PROTOCOL_WAR.equals(jarUrl.getProtocol())) { - return new URL(warFile); + return toURL(warFile); } int startIndex = warFile.indexOf(WAR_URL_PREFIX); if (startIndex != -1) { - return new URL(warFile.substring(startIndex + WAR_URL_PREFIX.length())); + return toURL(warFile.substring(startIndex + WAR_URL_PREFIX.length())); } } @@ -381,6 +381,51 @@ public static URI toURI(String location) throws URISyntaxException { return new URI(StringUtils.replace(location, " ", "%20")); } + /** + * Create a URL instance for the given location String, + * going through URI construction and then URL conversion. + * @param location the location String to convert into a URL instance + * @return the URL instance + * @throws MalformedURLException if the location wasn't a valid URL + * @since 6.0 + */ + public static URL toURL(String location) throws MalformedURLException { + // Equivalent without java.net.URL constructor - for building on JDK 20+ + /* + try { + return toURI(StringUtils.cleanPath(location)).toURL(); + } + catch (URISyntaxException | IllegalArgumentException ex) { + MalformedURLException exToThrow = new MalformedURLException(ex.getMessage()); + exToThrow.initCause(ex); + throw exToThrow; + } + */ + + return new URL(location); + } + + /** + * Create a URL instance for the given root URL and relative path, + * going through URI construction and then URL conversion. + * @param root the root URL to start from + * @param relativePath the relative path to apply + * @return the relative URL instance + * @throws MalformedURLException if the end result is not a valid URL + * @since 6.0 + */ + public static URL toRelativeURL(URL root, String relativePath) throws MalformedURLException { + // # can appear in filenames, java.net.URL should not treat it as a fragment + relativePath = StringUtils.replace(relativePath, "#", "%23"); + + // Equivalent without java.net.URL constructor - for building on JDK 20+ + /* + return toURL(StringUtils.applyRelativePath(root.toString(), relativePath)); + */ + + return new URL(root, relativePath); + } + /** * Set the {@link URLConnection#setUseCaches "useCaches"} flag on the * given connection, preferring {@code false} but leaving the diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index 3c2716be5ade..f2eb26323414 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java @@ -221,9 +221,9 @@ public static boolean containsWhitespace(@Nullable String str) { * @param str the {@code String} to check * @return the trimmed {@code String} * @see java.lang.Character#isWhitespace - * @deprecated in favor of {@link String#strip()} + * @deprecated since 6.0, in favor of {@link String#strip()} */ - @Deprecated + @Deprecated(since = "6.0") public static String trimWhitespace(String str) { if (!hasLength(str)) { return str; @@ -277,9 +277,9 @@ public static String trimAllWhitespace(String str) { * @param str the {@code String} to check * @return the trimmed {@code String} * @see java.lang.Character#isWhitespace - * @deprecated in favor of {@link String#stripLeading()} + * @deprecated since 6.0, in favor of {@link String#stripLeading()} */ - @Deprecated + @Deprecated(since = "6.0") public static String trimLeadingWhitespace(String str) { if (!hasLength(str)) { return str; @@ -293,9 +293,9 @@ public static String trimLeadingWhitespace(String str) { * @param str the {@code String} to check * @return the trimmed {@code String} * @see java.lang.Character#isWhitespace - * @deprecated in favor of {@link String#stripTrailing()} + * @deprecated since 6.0, in favor of {@link String#stripTrailing()} */ - @Deprecated + @Deprecated(since = "6.0") public static String trimTrailingWhitespace(String str) { if (!hasLength(str)) { return str; diff --git a/spring-core/src/main/java/org/springframework/util/UnmodifiableMultiValueMap.java b/spring-core/src/main/java/org/springframework/util/UnmodifiableMultiValueMap.java index 72822fcf0785..5ce6268dc156 100644 --- a/spring-core/src/main/java/org/springframework/util/UnmodifiableMultiValueMap.java +++ b/spring-core/src/main/java/org/springframework/util/UnmodifiableMultiValueMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java index 50e4745e06c6..ddedb43094dc 100644 --- a/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java +++ b/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java @@ -85,7 +85,7 @@ private NullSafeComparator(boolean nullsLow) { * @param nullsLow whether to treat nulls lower or higher than non-null objects */ public NullSafeComparator(Comparator comparator, boolean nullsLow) { - Assert.notNull(comparator, "Non-null Comparator is required"); + Assert.notNull(comparator, "Comparator must not be null"); this.nonNullComparator = comparator; this.nullsLow = nullsLow; } diff --git a/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java b/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java index 05a0408b72f9..b895a4edb3af 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java +++ b/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -49,7 +49,7 @@ public class SimpleNamespaceContext implements NamespaceContext { @Override public String getNamespaceURI(String prefix) { - Assert.notNull(prefix, "No prefix given"); + Assert.notNull(prefix, "'prefix' must not be null"); if (XMLConstants.XML_NS_PREFIX.equals(prefix)) { return XMLConstants.XML_NS_URI; } @@ -78,7 +78,7 @@ public Iterator getPrefixes(String namespaceUri) { } private Set getPrefixesSet(String namespaceUri) { - Assert.notNull(namespaceUri, "No namespaceUri given"); + Assert.notNull(namespaceUri, "'namespaceUri' must not be null"); if (this.defaultNamespaceUri.equals(namespaceUri)) { return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX); } @@ -114,11 +114,11 @@ public void bindDefaultNamespaceUri(String namespaceUri) { /** * Bind the given prefix to the given namespace. * @param prefix the namespace prefix - * @param namespaceUri the namespace uri + * @param namespaceUri the namespace URI */ public void bindNamespaceUri(String prefix, String namespaceUri) { - Assert.notNull(prefix, "No prefix given"); - Assert.notNull(namespaceUri, "No namespaceUri given"); + Assert.notNull(prefix, "'prefix' must not be null"); + Assert.notNull(namespaceUri, "'namespaceUri' must not be null"); if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) { this.defaultNamespaceUri = namespaceUri; } diff --git a/spring-core/src/main/resources/META-INF/spring/aot.factories b/spring-core/src/main/resources/META-INF/spring/aot.factories index e1e6672e5611..9d735f3b4240 100644 --- a/spring-core/src/main/resources/META-INF/spring/aot.factories +++ b/spring-core/src/main/resources/META-INF/spring/aot.factories @@ -1,2 +1,3 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.springframework.aot.hint.support.ObjectToObjectConverterRuntimeHints,\ org.springframework.aot.hint.support.SpringFactoriesLoaderRuntimeHints diff --git a/spring-core/src/test/java/org/springframework/SpringCoreTestSuite.java b/spring-core/src/test/java/org/springframework/SpringCoreTestSuite.java new file mode 100644 index 000000000000..a2d623435387 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/SpringCoreTestSuite.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2022 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; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +/** + * JUnit Platform based test suite for tests in the spring-core module. + * + *

This suite is only intended to be used manually within an IDE. + * + * @author Sam Brannen + */ +@Suite +@SelectPackages({"org.springframework.core", "org.springframework.util"}) +@IncludeClassNamePatterns(".*Tests?$") +class SpringCoreTestSuite { +} diff --git a/spring-core/src/test/java/org/springframework/aot/generate/DefaultMethodReferenceTests.java b/spring-core/src/test/java/org/springframework/aot/generate/DefaultMethodReferenceTests.java index b9643151ca9c..d5c54e75abbe 100644 --- a/spring-core/src/test/java/org/springframework/aot/generate/DefaultMethodReferenceTests.java +++ b/spring-core/src/test/java/org/springframework/aot/generate/DefaultMethodReferenceTests.java @@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link DefaultMethodReference}. @@ -86,7 +87,7 @@ void toCodeBlockWithStaticMethod() { void toCodeBlockWithStaticMethodRequiresDeclaringClass() { MethodSpec method = createTestMethod("methodName", new TypeName[0], Modifier.STATIC); MethodReference methodReference = new DefaultMethodReference(method, null); - assertThatIllegalArgumentException().isThrownBy(methodReference::toCodeBlock) + assertThatIllegalStateException().isThrownBy(methodReference::toCodeBlock) .withMessage("static method reference must define a declaring class"); } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java index 266738136d76..8341c91713f9 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java @@ -20,8 +20,10 @@ import java.util.List; import java.util.Set; +import com.fasterxml.jackson.annotation.JsonProperty; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.core.ResolvableType; import static org.assertj.core.api.Assertions.assertThat; @@ -219,6 +221,37 @@ void registerTypeForSerializationWithRecord() { }); } + @Test + void registerTypeForSerializationWithRecordWithProperty() { + bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleRecordWithProperty.class); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleRecordWithProperty.class, "getNameProperty")) + .accepts(this.hints); + } + + @Test + void registerTypeForSerializationWithAnonymousClass() { + Runnable anonymousRunnable = () -> { }; + bindingRegistrar.registerReflectionHints(this.hints.reflection(), anonymousRunnable.getClass()); + } + + @Test + void registerTypeForJacksonAnnotations() { + bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleClassWithJsonProperty.class); + assertThat(RuntimeHintsPredicates.reflection().onField(SampleClassWithJsonProperty.class, "privateField")) + .accepts(this.hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleClassWithJsonProperty.class, "packagePrivateMethod").invoke()) + .accepts(this.hints); + } + + @Test + void registerTypeForInheritedJacksonAnnotations() { + bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleClassWithInheritedJsonProperty.class); + assertThat(RuntimeHintsPredicates.reflection().onField(SampleClassWithJsonProperty.class, "privateField")) + .accepts(this.hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleClassWithJsonProperty.class, "packagePrivateMethod").invoke()) + .accepts(this.hints); + } + static class SampleEmptyClass { } @@ -291,7 +324,7 @@ public SampleClassC getC() { } } - class SampleClassC { + static class SampleClassC { public String getString() { return ""; } @@ -303,4 +336,24 @@ enum SampleEnum { record SampleRecord(String name) {} + record SampleRecordWithProperty(String name) { + + public String getNameProperty() { + return ""; + } + } + + static class SampleClassWithJsonProperty { + + @JsonProperty + private String privateField = ""; + + @JsonProperty + String packagePrivateMethod() { + return ""; + } + } + + static class SampleClassWithInheritedJsonProperty extends SampleClassWithJsonProperty {} + } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHintsTests.java new file mode 100644 index 000000000000..6cea9b055bad --- /dev/null +++ b/spring-core/src/test/java/org/springframework/aot/hint/support/ObjectToObjectConverterRuntimeHintsTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2022 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.aot.hint.support; + +import java.time.LocalDate; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ObjectToObjectConverterRuntimeHints}. + * + * @author Sebastien Deleuze + */ +class ObjectToObjectConverterRuntimeHintsTests { + + private RuntimeHints hints; + + @BeforeEach + void setup() { + this.hints = new RuntimeHints(); + SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories") + .load(RuntimeHintsRegistrar.class).forEach(registrar -> registrar + .registerHints(this.hints, ClassUtils.getDefaultClassLoader())); + } + + @Test + void javaSqlDateHasHints() throws NoSuchMethodException { + assertThat(RuntimeHintsPredicates.reflection().onMethod(java.sql.Date.class, "toLocalDate")).accepts(this.hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(java.sql.Date.class.getMethod("valueOf", LocalDate.class))).accepts(this.hints); + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/ConstantsTests.java b/spring-core/src/test/java/org/springframework/core/ConstantsTests.java index 377f6f8acdd1..ee2156319f0e 100644 --- a/spring-core/src/test/java/org/springframework/core/ConstantsTests.java +++ b/spring-core/src/test/java/org/springframework/core/ConstantsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -56,17 +56,17 @@ void getNames() { Constants c = new Constants(A.class); Set names = c.getNames(""); - assertThat(names.size()).isEqualTo(c.getSize()); + assertThat(names).hasSize(c.getSize()); assertThat(names.contains("DOG")).isTrue(); assertThat(names.contains("CAT")).isTrue(); assertThat(names.contains("S1")).isTrue(); names = c.getNames("D"); - assertThat(names.size()).isEqualTo(1); + assertThat(names).hasSize(1); assertThat(names.contains("DOG")).isTrue(); names = c.getNames("d"); - assertThat(names.size()).isEqualTo(1); + assertThat(names).hasSize(1); assertThat(names.contains("DOG")).isTrue(); } @@ -75,24 +75,24 @@ void getValues() { Constants c = new Constants(A.class); Set values = c.getValues(""); - assertThat(values.size()).isEqualTo(7); - assertThat(values.contains(Integer.valueOf(0))).isTrue(); - assertThat(values.contains(Integer.valueOf(66))).isTrue(); + assertThat(values).hasSize(7); + assertThat(values.contains(0)).isTrue(); + assertThat(values.contains(66)).isTrue(); assertThat(values.contains("")).isTrue(); values = c.getValues("D"); - assertThat(values.size()).isEqualTo(1); - assertThat(values.contains(Integer.valueOf(0))).isTrue(); + assertThat(values).hasSize(1); + assertThat(values.contains(0)).isTrue(); values = c.getValues("prefix"); - assertThat(values.size()).isEqualTo(2); - assertThat(values.contains(Integer.valueOf(1))).isTrue(); - assertThat(values.contains(Integer.valueOf(2))).isTrue(); + assertThat(values).hasSize(2); + assertThat(values.contains(1)).isTrue(); + assertThat(values.contains(2)).isTrue(); values = c.getValuesForProperty("myProperty"); - assertThat(values.size()).isEqualTo(2); - assertThat(values.contains(Integer.valueOf(1))).isTrue(); - assertThat(values.contains(Integer.valueOf(2))).isTrue(); + assertThat(values).hasSize(2); + assertThat(values.contains(1)).isTrue(); + assertThat(values.contains(2)).isTrue(); } @Test @@ -103,24 +103,24 @@ void getValuesInTurkey() { Constants c = new Constants(A.class); Set values = c.getValues(""); - assertThat(values.size()).isEqualTo(7); - assertThat(values.contains(Integer.valueOf(0))).isTrue(); - assertThat(values.contains(Integer.valueOf(66))).isTrue(); + assertThat(values).hasSize(7); + assertThat(values.contains(0)).isTrue(); + assertThat(values.contains(66)).isTrue(); assertThat(values.contains("")).isTrue(); values = c.getValues("D"); - assertThat(values.size()).isEqualTo(1); - assertThat(values.contains(Integer.valueOf(0))).isTrue(); + assertThat(values).hasSize(1); + assertThat(values.contains(0)).isTrue(); values = c.getValues("prefix"); - assertThat(values.size()).isEqualTo(2); - assertThat(values.contains(Integer.valueOf(1))).isTrue(); - assertThat(values.contains(Integer.valueOf(2))).isTrue(); + assertThat(values).hasSize(2); + assertThat(values.contains(1)).isTrue(); + assertThat(values.contains(2)).isTrue(); values = c.getValuesForProperty("myProperty"); - assertThat(values.size()).isEqualTo(2); - assertThat(values.contains(Integer.valueOf(1))).isTrue(); - assertThat(values.contains(Integer.valueOf(2))).isTrue(); + assertThat(values).hasSize(2); + assertThat(values.contains(1)).isTrue(); + assertThat(values.contains(2)).isTrue(); } finally { Locale.setDefault(oldLocale); @@ -132,30 +132,30 @@ void suffixAccess() { Constants c = new Constants(A.class); Set names = c.getNamesForSuffix("_PROPERTY"); - assertThat(names.size()).isEqualTo(2); + assertThat(names).hasSize(2); assertThat(names.contains("NO_PROPERTY")).isTrue(); assertThat(names.contains("YES_PROPERTY")).isTrue(); Set values = c.getValuesForSuffix("_PROPERTY"); - assertThat(values.size()).isEqualTo(2); - assertThat(values.contains(Integer.valueOf(3))).isTrue(); - assertThat(values.contains(Integer.valueOf(4))).isTrue(); + assertThat(values).hasSize(2); + assertThat(values.contains(3)).isTrue(); + assertThat(values.contains(4)).isTrue(); } @Test void toCode() { Constants c = new Constants(A.class); - assertThat(c.toCode(Integer.valueOf(0), "")).isEqualTo("DOG"); - assertThat(c.toCode(Integer.valueOf(0), "D")).isEqualTo("DOG"); - assertThat(c.toCode(Integer.valueOf(0), "DO")).isEqualTo("DOG"); - assertThat(c.toCode(Integer.valueOf(0), "DoG")).isEqualTo("DOG"); - assertThat(c.toCode(Integer.valueOf(0), null)).isEqualTo("DOG"); - assertThat(c.toCode(Integer.valueOf(66), "")).isEqualTo("CAT"); - assertThat(c.toCode(Integer.valueOf(66), "C")).isEqualTo("CAT"); - assertThat(c.toCode(Integer.valueOf(66), "ca")).isEqualTo("CAT"); - assertThat(c.toCode(Integer.valueOf(66), "cAt")).isEqualTo("CAT"); - assertThat(c.toCode(Integer.valueOf(66), null)).isEqualTo("CAT"); + assertThat(c.toCode(0, "")).isEqualTo("DOG"); + assertThat(c.toCode(0, "D")).isEqualTo("DOG"); + assertThat(c.toCode(0, "DO")).isEqualTo("DOG"); + assertThat(c.toCode(0, "DoG")).isEqualTo("DOG"); + assertThat(c.toCode(0, null)).isEqualTo("DOG"); + assertThat(c.toCode(66, "")).isEqualTo("CAT"); + assertThat(c.toCode(66, "C")).isEqualTo("CAT"); + assertThat(c.toCode(66, "ca")).isEqualTo("CAT"); + assertThat(c.toCode(66, "cAt")).isEqualTo("CAT"); + assertThat(c.toCode(66, null)).isEqualTo("CAT"); assertThat(c.toCode("", "")).isEqualTo("S1"); assertThat(c.toCode("", "s")).isEqualTo("S1"); assertThat(c.toCode("", "s1")).isEqualTo("S1"); @@ -165,21 +165,21 @@ void toCode() { assertThatExceptionOfType(Constants.ConstantException.class).isThrownBy(() -> c.toCode("bogus", null)); - assertThat(c.toCodeForProperty(Integer.valueOf(1), "myProperty")).isEqualTo("MY_PROPERTY_NO"); - assertThat(c.toCodeForProperty(Integer.valueOf(2), "myProperty")).isEqualTo("MY_PROPERTY_YES"); + assertThat(c.toCodeForProperty(1, "myProperty")).isEqualTo("MY_PROPERTY_NO"); + assertThat(c.toCodeForProperty(2, "myProperty")).isEqualTo("MY_PROPERTY_YES"); assertThatExceptionOfType(Constants.ConstantException.class).isThrownBy(() -> c.toCodeForProperty("bogus", "bogus")); - assertThat(c.toCodeForSuffix(Integer.valueOf(0), "")).isEqualTo("DOG"); - assertThat(c.toCodeForSuffix(Integer.valueOf(0), "G")).isEqualTo("DOG"); - assertThat(c.toCodeForSuffix(Integer.valueOf(0), "OG")).isEqualTo("DOG"); - assertThat(c.toCodeForSuffix(Integer.valueOf(0), "DoG")).isEqualTo("DOG"); - assertThat(c.toCodeForSuffix(Integer.valueOf(0), null)).isEqualTo("DOG"); - assertThat(c.toCodeForSuffix(Integer.valueOf(66), "")).isEqualTo("CAT"); - assertThat(c.toCodeForSuffix(Integer.valueOf(66), "T")).isEqualTo("CAT"); - assertThat(c.toCodeForSuffix(Integer.valueOf(66), "at")).isEqualTo("CAT"); - assertThat(c.toCodeForSuffix(Integer.valueOf(66), "cAt")).isEqualTo("CAT"); - assertThat(c.toCodeForSuffix(Integer.valueOf(66), null)).isEqualTo("CAT"); + assertThat(c.toCodeForSuffix(0, "")).isEqualTo("DOG"); + assertThat(c.toCodeForSuffix(0, "G")).isEqualTo("DOG"); + assertThat(c.toCodeForSuffix(0, "OG")).isEqualTo("DOG"); + assertThat(c.toCodeForSuffix(0, "DoG")).isEqualTo("DOG"); + assertThat(c.toCodeForSuffix(0, null)).isEqualTo("DOG"); + assertThat(c.toCodeForSuffix(66, "")).isEqualTo("CAT"); + assertThat(c.toCodeForSuffix(66, "T")).isEqualTo("CAT"); + assertThat(c.toCodeForSuffix(66, "at")).isEqualTo("CAT"); + assertThat(c.toCodeForSuffix(66, "cAt")).isEqualTo("CAT"); + assertThat(c.toCodeForSuffix(66, null)).isEqualTo("CAT"); assertThat(c.toCodeForSuffix("", "")).isEqualTo("S1"); assertThat(c.toCodeForSuffix("", "1")).isEqualTo("S1"); assertThat(c.toCodeForSuffix("", "s1")).isEqualTo("S1"); @@ -217,7 +217,7 @@ void withClassThatExposesNoConstants() throws Exception { assertThat(c.getSize()).isEqualTo(0); final Set values = c.getValues(""); assertThat(values).isNotNull(); - assertThat(values.size()).isEqualTo(0); + assertThat(values).isEmpty(); } @Test diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java index efd1f67aff35..46e19190f4f5 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -121,7 +121,7 @@ void testGetTypeVariableMap() throws Exception { assertThat(map.toString()).isEqualTo("{T=class java.lang.Integer}"); map = GenericTypeResolver.getTypeVariableMap(TypedTopLevelClass.TypedNested.class); - assertThat(map.size()).isEqualTo(2); + assertThat(map).hasSize(2); Type t = null; Type x = null; for (Map.Entry entry : map.entrySet()) { @@ -168,7 +168,7 @@ void getGenericsOnArrayFromReturnCannotBeResolved() throws Exception { void resolveIncompleteTypeVariables() { Class[] resolved = GenericTypeResolver.resolveTypeArguments(IdFixingRepository.class, Repository.class); assertThat(resolved).isNotNull(); - assertThat(resolved.length).isEqualTo(2); + assertThat(resolved).hasSize(2); assertThat(resolved[0]).isEqualTo(Object.class); assertThat(resolved[1]).isEqualTo(Long.class); } diff --git a/spring-core/src/test/java/org/springframework/core/LocalVariableTableParameterNameDiscovererTests.java b/spring-core/src/test/java/org/springframework/core/LocalVariableTableParameterNameDiscovererTests.java index 4ab6fc86967c..ecd54e55cbf6 100644 --- a/spring-core/src/test/java/org/springframework/core/LocalVariableTableParameterNameDiscovererTests.java +++ b/spring-core/src/test/java/org/springframework/core/LocalVariableTableParameterNameDiscovererTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -34,7 +34,8 @@ */ class LocalVariableTableParameterNameDiscovererTests { - private final LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); + @SuppressWarnings("removal") + private final ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); @Test @@ -162,23 +163,23 @@ void generifiedClass() throws Exception { Constructor ctor = clazz.getDeclaredConstructor(Object.class); String[] names = discoverer.getParameterNames(ctor); - assertThat(names.length).isEqualTo(1); + assertThat(names).hasSize(1); assertThat(names[0]).isEqualTo("key"); ctor = clazz.getDeclaredConstructor(Object.class, Object.class); names = discoverer.getParameterNames(ctor); - assertThat(names.length).isEqualTo(2); + assertThat(names).hasSize(2); assertThat(names[0]).isEqualTo("key"); assertThat(names[1]).isEqualTo("value"); Method m = clazz.getMethod("generifiedStaticMethod", Object.class); names = discoverer.getParameterNames(m); - assertThat(names.length).isEqualTo(1); + assertThat(names).hasSize(1); assertThat(names[0]).isEqualTo("param"); m = clazz.getMethod("generifiedMethod", Object.class, long.class, Object.class, Object.class); names = discoverer.getParameterNames(m); - assertThat(names.length).isEqualTo(4); + assertThat(names).hasSize(4); assertThat(names[0]).isEqualTo("param"); assertThat(names[1]).isEqualTo("x"); assertThat(names[2]).isEqualTo("key"); @@ -186,21 +187,21 @@ void generifiedClass() throws Exception { m = clazz.getMethod("voidStaticMethod", Object.class, long.class, int.class); names = discoverer.getParameterNames(m); - assertThat(names.length).isEqualTo(3); + assertThat(names).hasSize(3); assertThat(names[0]).isEqualTo("obj"); assertThat(names[1]).isEqualTo("x"); assertThat(names[2]).isEqualTo("i"); m = clazz.getMethod("nonVoidStaticMethod", Object.class, long.class, int.class); names = discoverer.getParameterNames(m); - assertThat(names.length).isEqualTo(3); + assertThat(names).hasSize(3); assertThat(names[0]).isEqualTo("obj"); assertThat(names[1]).isEqualTo("x"); assertThat(names[2]).isEqualTo("i"); m = clazz.getMethod("getDate"); names = discoverer.getParameterNames(m); - assertThat(names.length).isEqualTo(0); + assertThat(names).isEmpty(); } @Disabled("Ignored because Ubuntu packages OpenJDK with debug symbols enabled. See SPR-8078.") diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index 48641a7c70c8..43548a3edde9 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -535,7 +535,7 @@ void hasGenerics() throws Exception { void getGenericsFromParameterizedType() throws Exception { ResolvableType type = ResolvableType.forClass(List.class, ExtendsList.class); ResolvableType[] generics = type.getGenerics(); - assertThat(generics.length).isEqualTo(1); + assertThat(generics).hasSize(1); assertThat(generics[0].resolve()).isEqualTo(CharSequence.class); } @@ -543,7 +543,7 @@ void getGenericsFromParameterizedType() throws Exception { void getGenericsFromClass() throws Exception { ResolvableType type = ResolvableType.forClass(List.class); ResolvableType[] generics = type.getGenerics(); - assertThat(generics.length).isEqualTo(1); + assertThat(generics).hasSize(1); assertThat(generics[0].getType().toString()).isEqualTo("E"); } @@ -558,7 +558,7 @@ void noGetGenerics() throws Exception { void getResolvedGenerics() throws Exception { ResolvableType type = ResolvableType.forClass(List.class, ExtendsList.class); Class[] generics = type.resolveGenerics(); - assertThat(generics.length).isEqualTo(1); + assertThat(generics).hasSize(1); assertThat(generics[0]).isEqualTo(CharSequence.class); } @@ -1307,7 +1307,7 @@ void spr12701() throws Exception { Type type = resolvableType.getType(); assertThat(type).isInstanceOf(ParameterizedType.class); assertThat(((ParameterizedType) type).getRawType()).isEqualTo(Callable.class); - assertThat(((ParameterizedType) type).getActualTypeArguments().length).isEqualTo(1); + assertThat(((ParameterizedType) type).getActualTypeArguments()).hasSize(1); assertThat(((ParameterizedType) type).getActualTypeArguments()[0]).isEqualTo(String.class); } diff --git a/spring-core/src/test/java/org/springframework/core/SpringCoreBlockHoundIntegrationTests.java b/spring-core/src/test/java/org/springframework/core/SpringCoreBlockHoundIntegrationTests.java index 13e089469e7a..2ea43bb80b7f 100644 --- a/spring-core/src/test/java/org/springframework/core/SpringCoreBlockHoundIntegrationTests.java +++ b/spring-core/src/test/java/org/springframework/core/SpringCoreBlockHoundIntegrationTests.java @@ -16,7 +16,6 @@ package org.springframework.core; -import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -28,7 +27,6 @@ import reactor.core.scheduler.ReactorBlockHoundIntegration; import reactor.core.scheduler.Schedulers; -import org.springframework.tests.sample.objects.TestObject; import org.springframework.util.ConcurrentReferenceHashMap; import static org.assertj.core.api.Assertions.assertThat; @@ -52,11 +50,10 @@ @DisabledOnJre(value= {JRE.JAVA_18, JRE.JAVA_19}, disabledReason = "BlockHound is not compatible with Java 18+") class SpringCoreBlockHoundIntegrationTests { - @BeforeAll - static void setUp() { + static void setup() { BlockHound.builder() - .with(new ReactorBlockHoundIntegration()) // Reactor non-blocking thread predicate + .with(new ReactorBlockHoundIntegration()) // Reactor non-blocking thread predicate .with(new ReactiveAdapterRegistry.SpringCoreBlockHoundIntegration()) .install(); } @@ -68,15 +65,6 @@ void blockHoundIsInstalled() { .hasMessageContaining("Blocking call!"); } - @Test - void localVariableTableParameterNameDiscoverer() { - testNonBlockingTask(() -> { - Method setName = TestObject.class.getMethod("setName", String.class); - String[] names = new LocalVariableTableParameterNameDiscoverer().getParameterNames(setName); - assertThat(names).containsExactly("name"); - }); - } - @Test void concurrentReferenceHashMap() { int size = 10000; 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 aea76fca67c1..3eba643295e1 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 @@ -20,6 +20,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -77,6 +78,7 @@ * @see AnnotationUtilsTests * @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests * @see ComposedRepeatableAnnotationsTests + * @see NestedRepeatableAnnotationsTests */ class AnnotatedElementUtilsTests { @@ -874,7 +876,7 @@ void getAllMergedAnnotationsOnClassWithInterface() throws Exception { void findAllMergedAnnotationsOnClassWithInterface() throws Exception { Method method = TransactionalServiceImpl.class.getMethod("doIt"); Set allMergedAnnotations = findAllMergedAnnotations(method, Transactional.class); - assertThat(allMergedAnnotations.size()).isEqualTo(1); + assertThat(allMergedAnnotations).hasSize(1); } @Test // SPR-16060 @@ -908,6 +910,31 @@ void getMergedAnnotationOnThreeDeepMetaWithValue() { assertThat(annotation.value()).containsExactly("FromValueAttributeMeta"); } + /** + * @since 5.3.25 + */ + @Test // gh-29685 + void getMergedRepeatableAnnotationsWithContainerWithMultipleAttributes() { + Set repeatableAnnotations = + AnnotatedElementUtils.getMergedRepeatableAnnotations( + StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class, + StandardRepeatableWithContainerWithMultipleAttributes.class); + assertThat(repeatableAnnotations).map(StandardRepeatableWithContainerWithMultipleAttributes::value) + .containsExactly("a", "b"); + } + + /** + * @since 5.3.25 + */ + @Test // gh-29685 + void findMergedRepeatableAnnotationsWithContainerWithMultipleAttributes() { + Set repeatableAnnotations = + AnnotatedElementUtils.findMergedRepeatableAnnotations( + StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class, + StandardRepeatableWithContainerWithMultipleAttributes.class); + assertThat(repeatableAnnotations).map(StandardRepeatableWithContainerWithMultipleAttributes::value) + .containsExactly("a", "b"); + } // ------------------------------------------------------------------------- @@ -1567,4 +1594,24 @@ class ForAnnotationsClass { static class ValueAttributeMetaMetaClass { } + @Retention(RetentionPolicy.RUNTIME) + @interface StandardContainerWithMultipleAttributes { + + StandardRepeatableWithContainerWithMultipleAttributes[] value(); + + String name() default ""; + } + + @Retention(RetentionPolicy.RUNTIME) + @Repeatable(StandardContainerWithMultipleAttributes.class) + @interface StandardRepeatableWithContainerWithMultipleAttributes { + + String value() default ""; + } + + @StandardRepeatableWithContainerWithMultipleAttributes("a") + @StandardRepeatableWithContainerWithMultipleAttributes("b") + static class StandardRepeatablesWithContainerWithMultipleAttributesTestCase { + } + } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java index 415e89b979bf..49ea67b0b296 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java @@ -108,12 +108,12 @@ void singleElementToSingleElementArrayConversionSupport() throws Exception { AnnotationAttributes[] array = attributes.getAnnotationArray("nestedAttributes"); assertThat(array).isNotNull(); - assertThat(array.length).isEqualTo(1); + assertThat(array).hasSize(1); assertThat(array[0].getString("name")).isEqualTo("Dilbert"); Filter[] filters = attributes.getAnnotationArray("filters", Filter.class); assertThat(filters).isNotNull(); - assertThat(filters.length).isEqualTo(1); + assertThat(filters).hasSize(1); assertThat(filters[0].pattern()).isEqualTo("foo"); } @@ -130,7 +130,7 @@ void nestedAnnotations() throws Exception { Filter[] retrievedFilters = attributes.getAnnotationArray("filters", Filter.class); assertThat(retrievedFilters).isNotNull(); - assertThat(retrievedFilters.length).isEqualTo(2); + assertThat(retrievedFilters).hasSize(2); assertThat(retrievedFilters[1].pattern()).isEqualTo("foo"); } 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 ffd4bb43962d..248a809d604b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -507,7 +507,7 @@ void getValueFromAnnotation() throws Exception { @Test void getValueFromNonPublicAnnotation() throws Exception { Annotation[] declaredAnnotations = NonPublicAnnotatedClass.class.getDeclaredAnnotations(); - assertThat(declaredAnnotations.length).isEqualTo(1); + assertThat(declaredAnnotations).hasSize(1); Annotation annotation = declaredAnnotations[0]; assertThat(annotation).isNotNull(); assertThat(annotation.annotationType().getSimpleName()).isEqualTo("NonPublicAnnotation"); @@ -527,7 +527,7 @@ void getDefaultValueFromAnnotation() throws Exception { @Test void getDefaultValueFromNonPublicAnnotation() { Annotation[] declaredAnnotations = NonPublicAnnotatedClass.class.getDeclaredAnnotations(); - assertThat(declaredAnnotations.length).isEqualTo(1); + assertThat(declaredAnnotations).hasSize(1); Annotation annotation = declaredAnnotations[0]; assertThat(annotation).isNotNull(); assertThat(annotation.annotationType().getSimpleName()).isEqualTo("NonPublicAnnotation"); @@ -706,17 +706,17 @@ void getDeclaredRepeatableAnnotationsDeclaredOnSuperclass() { // Java 8 MyRepeatable[] array = clazz.getDeclaredAnnotationsByType(MyRepeatable.class); assertThat(array).isNotNull(); - assertThat(array.length).isEqualTo(0); + assertThat(array).isEmpty(); // Spring Set set = getDeclaredRepeatableAnnotations(clazz, MyRepeatable.class, MyRepeatableContainer.class); assertThat(set).isNotNull(); - assertThat(set).hasSize(0); + assertThat(set).isEmpty(); // When container type is omitted and therefore inferred from @Repeatable set = getDeclaredRepeatableAnnotations(clazz, MyRepeatable.class); assertThat(set).isNotNull(); - assertThat(set).hasSize(0); + assertThat(set).isEmpty(); } @Test diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AttributeMethodsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AttributeMethodsTests.java index 6fb0dcdb433b..a15ff7fa5192 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AttributeMethodsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AttributeMethodsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -58,24 +58,6 @@ void forAnnotationTypeWhenHasMultipleAttributesReturnsAttributes() { assertThat(getAll(methods)).flatExtracting(Method::getName).containsExactly("intValue", "value"); } - @Test - void hasOnlyValueAttributeWhenHasOnlyValueAttributeReturnsTrue() { - AttributeMethods methods = AttributeMethods.forAnnotationType(ValueOnly.class); - assertThat(methods.hasOnlyValueAttribute()).isTrue(); - } - - @Test - void hasOnlyValueAttributeWhenHasOnlySingleNonValueAttributeReturnsFalse() { - AttributeMethods methods = AttributeMethods.forAnnotationType(NonValueOnly.class); - assertThat(methods.hasOnlyValueAttribute()).isFalse(); - } - - @Test - void hasOnlyValueAttributeWhenHasOnlyMultipleAttributesIncludingValueReturnsFalse() { - AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class); - assertThat(methods.hasOnlyValueAttribute()).isFalse(); - } - @Test void indexOfNameReturnsIndex() { AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class); diff --git a/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java index 655013383dcb..857f2f674efd 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -112,7 +112,7 @@ void getNoninheritedComposedRepeatableAnnotationsOnSuperclass() { Class element = SubNoninheritedRepeatableClass.class; Set annotations = getMergedRepeatableAnnotations(element, Noninherited.class); assertThat(annotations).isNotNull(); - assertThat(annotations.size()).isEqualTo(0); + assertThat(annotations).isEmpty(); } @Test @@ -216,7 +216,7 @@ private void assertGetRepeatableAnnotations(AnnotatedElement element) { Set peteRepeats = getMergedRepeatableAnnotations(element, PeteRepeat.class); assertThat(peteRepeats).isNotNull(); - assertThat(peteRepeats.size()).isEqualTo(3); + assertThat(peteRepeats).hasSize(3); Iterator iterator = peteRepeats.iterator(); assertThat(iterator.next().value()).isEqualTo("A"); @@ -229,7 +229,7 @@ private void assertFindRepeatableAnnotations(AnnotatedElement element) { Set peteRepeats = findMergedRepeatableAnnotations(element, PeteRepeat.class); assertThat(peteRepeats).isNotNull(); - assertThat(peteRepeats.size()).isEqualTo(3); + assertThat(peteRepeats).hasSize(3); Iterator iterator = peteRepeats.iterator(); assertThat(iterator.next().value()).isEqualTo("A"); @@ -239,7 +239,7 @@ private void assertFindRepeatableAnnotations(AnnotatedElement element) { private void assertNoninheritedRepeatableAnnotations(Set annotations) { assertThat(annotations).isNotNull(); - assertThat(annotations.size()).isEqualTo(3); + assertThat(annotations).hasSize(3); Iterator iterator = annotations.iterator(); assertThat(iterator.next().value()).isEqualTo("A"); diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationPredicatesTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationPredicatesTests.java index 2d272f27628a..ca2285ba9e34 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationPredicatesTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationPredicatesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsComposedOnSingleAnnotatedElementTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsComposedOnSingleAnnotatedElementTests.java index 5c6aa83ee16a..154c9d9f4fa7 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsComposedOnSingleAnnotatedElementTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsComposedOnSingleAnnotatedElementTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -180,9 +180,7 @@ Method getBridgeMethod() throws NoSuchMethodException { methods.add(method); } }); - Method bridgeMethod = methods.get(0).getReturnType().equals(Object.class) - ? methods.get(0) - : methods.get(1); + Method bridgeMethod = methods.get(0).getReturnType() == Object.class ? methods.get(0) : methods.get(1); assertThat(bridgeMethod.isBridge()).isTrue(); return bridgeMethod; } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java index b201008380ab..dffb0c2101c6 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -62,7 +62,7 @@ void getMultipleNoninheritedComposedAnnotationsOnClass() { Class element = MultipleNoninheritedComposedCachesClass.class; Set cacheables = getAllMergedAnnotations(element, Cacheable.class); assertThat(cacheables).isNotNull(); - assertThat(cacheables.size()).isEqualTo(2); + assertThat(cacheables).hasSize(2); Iterator iterator = cacheables.iterator(); Cacheable cacheable1 = iterator.next(); @@ -76,7 +76,7 @@ void getMultipleNoninheritedComposedAnnotationsOnSuperclass() { Class element = SubMultipleNoninheritedComposedCachesClass.class; Set cacheables = getAllMergedAnnotations(element, Cacheable.class); assertThat(cacheables).isNotNull(); - assertThat(cacheables.size()).isEqualTo(0); + assertThat(cacheables).isEmpty(); } @Test @@ -89,7 +89,7 @@ void getMultipleComposedAnnotationsOnInterface() { Class element = MultipleComposedCachesOnInterfaceClass.class; Set cacheables = getAllMergedAnnotations(element, Cacheable.class); assertThat(cacheables).isNotNull(); - assertThat(cacheables.size()).isEqualTo(0); + assertThat(cacheables).isEmpty(); } @Test @@ -109,7 +109,7 @@ void getComposedPlusLocalAnnotationsOnMethod() throws Exception { void getMultipleComposedAnnotationsOnBridgeMethod() throws Exception { Set cacheables = getAllMergedAnnotations(getBridgeMethod(), Cacheable.class); assertThat(cacheables).isNotNull(); - assertThat(cacheables.size()).isEqualTo(0); + assertThat(cacheables).isEmpty(); } @Test @@ -127,7 +127,7 @@ void findMultipleNoninheritedComposedAnnotationsOnClass() { Class element = MultipleNoninheritedComposedCachesClass.class; Set cacheables = findAllMergedAnnotations(element, Cacheable.class); assertThat(cacheables).isNotNull(); - assertThat(cacheables.size()).isEqualTo(2); + assertThat(cacheables).hasSize(2); Iterator iterator = cacheables.iterator(); Cacheable cacheable1 = iterator.next(); @@ -141,7 +141,7 @@ void findMultipleNoninheritedComposedAnnotationsOnSuperclass() { Class element = SubMultipleNoninheritedComposedCachesClass.class; Set cacheables = findAllMergedAnnotations(element, Cacheable.class); assertThat(cacheables).isNotNull(); - assertThat(cacheables.size()).isEqualTo(2); + assertThat(cacheables).hasSize(2); Iterator iterator = cacheables.iterator(); Cacheable cacheable1 = iterator.next(); @@ -213,7 +213,7 @@ private void assertGetAllMergedAnnotationsBehavior(AnnotatedElement element) { Set cacheables = getAllMergedAnnotations(element, Cacheable.class); assertThat(cacheables).isNotNull(); - assertThat(cacheables.size()).isEqualTo(2); + assertThat(cacheables).hasSize(2); Iterator iterator = cacheables.iterator(); Cacheable fooCacheable = iterator.next(); @@ -229,7 +229,7 @@ private void assertFindAllMergedAnnotationsBehavior(AnnotatedElement element) { Set cacheables = findAllMergedAnnotations(element, Cacheable.class); assertThat(cacheables).isNotNull(); - assertThat(cacheables.size()).isEqualTo(2); + assertThat(cacheables).hasSize(2); Iterator iterator = cacheables.iterator(); Cacheable fooCacheable = iterator.next(); diff --git a/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java b/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java index b3a9860394a2..ee1f88ee5993 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java @@ -20,9 +20,14 @@ import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.core.annotation.AnnotatedElementUtilsTests.StandardContainerWithMultipleAttributes; +import org.springframework.core.annotation.AnnotatedElementUtilsTests.StandardRepeatablesWithContainerWithMultipleAttributesTestCase; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -31,189 +36,171 @@ * Tests for {@link RepeatableContainers}. * * @author Phillip Webb + * @author Sam Brannen */ class RepeatableContainersTests { - @Test - void standardRepeatablesWhenNonRepeatableReturnsNull() { - Object[] values = findRepeatedAnnotationValues( - RepeatableContainers.standardRepeatables(), WithNonRepeatable.class, - NonRepeatable.class); - assertThat(values).isNull(); - } + @Nested + class StandardRepeatableContainersTests { - @Test - void standardRepeatablesWhenSingleReturnsNull() { - Object[] values = findRepeatedAnnotationValues( - RepeatableContainers.standardRepeatables(), - WithSingleStandardRepeatable.class, StandardRepeatable.class); - assertThat(values).isNull(); - } + @Test + void standardRepeatablesWhenNonRepeatableReturnsNull() { + Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(), + NonRepeatableTestCase.class, NonRepeatable.class); + assertThat(values).isNull(); + } - @Test - void standardRepeatablesWhenContainerReturnsRepeats() { - Object[] values = findRepeatedAnnotationValues( - RepeatableContainers.standardRepeatables(), WithStandardRepeatables.class, - StandardContainer.class); - assertThat(values).containsExactly("a", "b"); - } + @Test + void standardRepeatablesWhenSingleReturnsNull() { + Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(), + SingleStandardRepeatableTestCase.class, StandardRepeatable.class); + assertThat(values).isNull(); + } - @Test - void standardRepeatablesWhenContainerButNotRepeatableReturnsNull() { - Object[] values = findRepeatedAnnotationValues( - RepeatableContainers.standardRepeatables(), WithExplicitRepeatables.class, - ExplicitContainer.class); - assertThat(values).isNull(); - } + @Test + void standardRepeatablesWhenContainerButNotRepeatableReturnsNull() { + Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(), + ExplicitRepeatablesTestCase.class, ExplicitContainer.class); + assertThat(values).isNull(); + } - @Test - void ofExplicitWhenNonRepeatableReturnsNull() { - Object[] values = findRepeatedAnnotationValues( - RepeatableContainers.of(ExplicitRepeatable.class, - ExplicitContainer.class), - WithNonRepeatable.class, NonRepeatable.class); - assertThat(values).isNull(); - } + @Test + void standardRepeatablesWhenContainerReturnsRepeats() { + Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(), + StandardRepeatablesTestCase.class, StandardContainer.class); + assertThat(values).containsExactly("a", "b"); + } - @Test - void ofExplicitWhenStandardRepeatableContainerReturnsNull() { - Object[] values = findRepeatedAnnotationValues( - RepeatableContainers.of(ExplicitRepeatable.class, - ExplicitContainer.class), - WithStandardRepeatables.class, StandardContainer.class); - assertThat(values).isNull(); - } + @Test + void standardRepeatablesWithContainerWithMultipleAttributes() { + Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(), + StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class, + StandardContainerWithMultipleAttributes.class); + assertThat(values).containsExactly("a", "b"); + } - @Test - void ofExplicitWhenContainerReturnsRepeats() { - Object[] values = findRepeatedAnnotationValues( - RepeatableContainers.of(ExplicitRepeatable.class, - ExplicitContainer.class), - WithExplicitRepeatables.class, ExplicitContainer.class); - assertThat(values).containsExactly("a", "b"); } - @Test - void ofExplicitWhenHasNoValueThrowsException() { - assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> - RepeatableContainers.of(ExplicitRepeatable.class, InvalidNoValue.class)) - .withMessageContaining("Invalid declaration of container type [" - + InvalidNoValue.class.getName() - + "] for repeatable annotation [" - + ExplicitRepeatable.class.getName() + "]"); - } + @Nested + class ExplicitRepeatableContainerTests { - @Test - void ofExplicitWhenValueIsNotArrayThrowsException() { - assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> - RepeatableContainers.of(ExplicitRepeatable.class, InvalidNotArray.class)) - .withMessage("Container type [" - + InvalidNotArray.class.getName() - + "] must declare a 'value' attribute for an array of type [" - + ExplicitRepeatable.class.getName() + "]"); - } + @Test + void ofExplicitWhenNonRepeatableReturnsNull() { + Object[] values = findRepeatedAnnotationValues( + RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class), + NonRepeatableTestCase.class, NonRepeatable.class); + assertThat(values).isNull(); + } - @Test - void ofExplicitWhenValueIsArrayOfWrongTypeThrowsException() { - assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> - RepeatableContainers.of(ExplicitRepeatable.class, InvalidWrongArrayType.class)) - .withMessage("Container type [" - + InvalidWrongArrayType.class.getName() - + "] must declare a 'value' attribute for an array of type [" - + ExplicitRepeatable.class.getName() + "]"); - } + @Test + void ofExplicitWhenStandardRepeatableContainerReturnsNull() { + Object[] values = findRepeatedAnnotationValues( + RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class), + StandardRepeatablesTestCase.class, StandardContainer.class); + assertThat(values).isNull(); + } - @Test - void ofExplicitWhenAnnotationIsNullThrowsException() { - assertThatIllegalArgumentException().isThrownBy(() -> - RepeatableContainers.of(null, null)) - .withMessage("Repeatable must not be null"); - } + @Test + void ofExplicitWhenContainerReturnsRepeats() { + Object[] values = findRepeatedAnnotationValues( + RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class), + ExplicitRepeatablesTestCase.class, ExplicitContainer.class); + assertThat(values).containsExactly("a", "b"); + } - @Test - void ofExplicitWhenContainerIsNullDeducesContainer() { - Object[] values = findRepeatedAnnotationValues( - RepeatableContainers.of(StandardRepeatable.class, null), - WithStandardRepeatables.class, StandardContainer.class); - assertThat(values).containsExactly("a", "b"); - } + @Test + void ofExplicitWhenContainerIsNullDeducesContainer() { + Object[] values = findRepeatedAnnotationValues(RepeatableContainers.of(StandardRepeatable.class, null), + StandardRepeatablesTestCase.class, StandardContainer.class); + assertThat(values).containsExactly("a", "b"); + } + + @Test + void ofExplicitWhenHasNoValueThrowsException() { + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, InvalidNoValue.class)) + .withMessageContaining("Invalid declaration of container type [%s] for repeatable annotation [%s]", + InvalidNoValue.class.getName(), ExplicitRepeatable.class.getName()); + } + + @Test + void ofExplicitWhenValueIsNotArrayThrowsException() { + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, InvalidNotArray.class)) + .withMessage("Container type [%s] must declare a 'value' attribute for an array of type [%s]", + InvalidNotArray.class.getName(), ExplicitRepeatable.class.getName()); + } + + @Test + void ofExplicitWhenValueIsArrayOfWrongTypeThrowsException() { + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, InvalidWrongArrayType.class)) + .withMessage("Container type [%s] must declare a 'value' attribute for an array of type [%s]", + InvalidWrongArrayType.class.getName(), ExplicitRepeatable.class.getName()); + } + + @Test + void ofExplicitWhenAnnotationIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> RepeatableContainers.of(null, null)) + .withMessage("Repeatable must not be null"); + } + + @Test + void ofExplicitWhenContainerIsNullAndNotRepeatableThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> RepeatableContainers.of(ExplicitRepeatable.class, null)) + .withMessage("Annotation type must be a repeatable annotation: failed to resolve container type for %s", + ExplicitRepeatable.class.getName()); + } - @Test - void ofExplicitWhenContainerIsNullAndNotRepeatableThrowsException() { - assertThatIllegalArgumentException().isThrownBy(() -> - RepeatableContainers.of(ExplicitRepeatable.class, null)) - .withMessage("Annotation type must be a repeatable annotation: " + - "failed to resolve container type for " + - ExplicitRepeatable.class.getName()); } @Test void standardAndExplicitReturnsRepeats() { - RepeatableContainers repeatableContainers = RepeatableContainers.standardRepeatables().and( - ExplicitContainer.class, ExplicitRepeatable.class); - assertThat(findRepeatedAnnotationValues(repeatableContainers, - WithStandardRepeatables.class, StandardContainer.class)).containsExactly( - "a", "b"); - assertThat(findRepeatedAnnotationValues(repeatableContainers, - WithExplicitRepeatables.class, ExplicitContainer.class)).containsExactly( - "a", "b"); + RepeatableContainers repeatableContainers = RepeatableContainers.standardRepeatables() + .and(ExplicitContainer.class, ExplicitRepeatable.class); + assertThat(findRepeatedAnnotationValues(repeatableContainers, StandardRepeatablesTestCase.class, StandardContainer.class)) + .containsExactly("a", "b"); + assertThat(findRepeatedAnnotationValues(repeatableContainers, ExplicitRepeatablesTestCase.class, ExplicitContainer.class)) + .containsExactly("a", "b"); } @Test void noneAlwaysReturnsNull() { - Object[] values = findRepeatedAnnotationValues( - RepeatableContainers.none(), WithStandardRepeatables.class, - StandardContainer.class); + Object[] values = findRepeatedAnnotationValues(RepeatableContainers.none(), StandardRepeatablesTestCase.class, + StandardContainer.class); assertThat(values).isNull(); } @Test void equalsAndHashcode() { - RepeatableContainers c1 = RepeatableContainers.of(ExplicitRepeatable.class, - ExplicitContainer.class); - RepeatableContainers c2 = RepeatableContainers.of(ExplicitRepeatable.class, - ExplicitContainer.class); + RepeatableContainers c1 = RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class); + RepeatableContainers c2 = RepeatableContainers.of(ExplicitRepeatable.class, ExplicitContainer.class); RepeatableContainers c3 = RepeatableContainers.standardRepeatables(); - RepeatableContainers c4 = RepeatableContainers.standardRepeatables().and( - ExplicitContainer.class, ExplicitRepeatable.class); - assertThat(c1.hashCode()).isEqualTo(c2.hashCode()); + RepeatableContainers c4 = RepeatableContainers.standardRepeatables().and(ExplicitContainer.class, ExplicitRepeatable.class); + assertThat(c1).hasSameHashCodeAs(c2); assertThat(c1).isEqualTo(c1).isEqualTo(c2); assertThat(c1).isNotEqualTo(c3).isNotEqualTo(c4); } - private Object[] findRepeatedAnnotationValues(RepeatableContainers containers, + + private static Object[] findRepeatedAnnotationValues(RepeatableContainers containers, Class element, Class annotationType) { - Annotation[] annotations = containers.findRepeatedAnnotations( - element.getAnnotation(annotationType)); + Annotation[] annotations = containers.findRepeatedAnnotations(element.getAnnotation(annotationType)); return extractValues(annotations); } - private Object[] extractValues(Annotation[] annotations) { - try { - if (annotations == null) { - return null; - } - Object[] result = new String[annotations.length]; - for (int i = 0; i < annotations.length; i++) { - result[i] = annotations[i].annotationType().getMethod("value").invoke( - annotations[i]); - } - return result; - } - catch (Exception ex) { - throw new RuntimeException(ex); + private static Object[] extractValues(Annotation[] annotations) { + if (annotations == null) { + return null; } + return Arrays.stream(annotations).map(AnnotationUtils::getValue).toArray(Object[]::new); } - @Retention(RetentionPolicy.RUNTIME) - @interface NonRepeatable { - - String value() default ""; - } @Retention(RetentionPolicy.RUNTIME) - @Repeatable(StandardContainer.class) - @interface StandardRepeatable { + @interface NonRepeatable { String value() default ""; } @@ -225,7 +212,8 @@ private Object[] extractValues(Annotation[] annotations) { } @Retention(RetentionPolicy.RUNTIME) - @interface ExplicitRepeatable { + @Repeatable(StandardContainer.class) + @interface StandardRepeatable { String value() default ""; } @@ -236,6 +224,12 @@ private Object[] extractValues(Annotation[] annotations) { ExplicitRepeatable[] value(); } + @Retention(RetentionPolicy.RUNTIME) + @interface ExplicitRepeatable { + + String value() default ""; + } + @Retention(RetentionPolicy.RUNTIME) @interface InvalidNoValue { } @@ -253,20 +247,20 @@ private Object[] extractValues(Annotation[] annotations) { } @NonRepeatable("a") - static class WithNonRepeatable { + static class NonRepeatableTestCase { } @StandardRepeatable("a") - static class WithSingleStandardRepeatable { + static class SingleStandardRepeatableTestCase { } @StandardRepeatable("a") @StandardRepeatable("b") - static class WithStandardRepeatables { + static class StandardRepeatablesTestCase { } @ExplicitContainer({ @ExplicitRepeatable("a"), @ExplicitRepeatable("b") }) - static class WithExplicitRepeatables { + static class ExplicitRepeatablesTestCase { } } diff --git a/spring-core/src/test/java/org/springframework/core/codec/ResourceRegionEncoderTests.java b/spring-core/src/test/java/org/springframework/core/codec/ResourceRegionEncoderTests.java index 6412af933945..4cf45826d7a3 100644 --- a/spring-core/src/test/java/org/springframework/core/codec/ResourceRegionEncoderTests.java +++ b/spring-core/src/test/java/org/springframework/core/codec/ResourceRegionEncoderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java index e77ffaa41b19..c48a8105d5ba 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -65,7 +65,7 @@ void parameterPrimitive() throws Exception { assertThat(desc.getName()).isEqualTo("int"); assertThat(desc.toString()).isEqualTo("int"); assertThat(desc.isPrimitive()).isTrue(); - assertThat(desc.getAnnotations().length).isEqualTo(0); + assertThat(desc.getAnnotations()).isEmpty(); assertThat(desc.isCollection()).isFalse(); assertThat(desc.isMap()).isFalse(); } @@ -78,7 +78,7 @@ void parameterScalar() throws Exception { assertThat(desc.getName()).isEqualTo("java.lang.String"); assertThat(desc.toString()).isEqualTo("java.lang.String"); assertThat(!desc.isPrimitive()).isTrue(); - assertThat(desc.getAnnotations().length).isEqualTo(0); + assertThat(desc.getAnnotations()).isEmpty(); assertThat(desc.isCollection()).isFalse(); assertThat(desc.isArray()).isFalse(); assertThat(desc.isMap()).isFalse(); @@ -93,7 +93,7 @@ void parameterList() throws Exception { assertThat(desc.getName()).isEqualTo("java.util.List"); assertThat(desc.toString()).isEqualTo("java.util.List>>>"); assertThat(!desc.isPrimitive()).isTrue(); - assertThat(desc.getAnnotations().length).isEqualTo(0); + assertThat(desc.getAnnotations()).isEmpty(); assertThat(desc.isCollection()).isTrue(); assertThat(desc.isArray()).isFalse(); assertThat(desc.getElementTypeDescriptor().getType()).isEqualTo(List.class); @@ -114,7 +114,7 @@ void parameterListNoParamTypes() throws Exception { assertThat(desc.getName()).isEqualTo("java.util.List"); assertThat(desc.toString()).isEqualTo("java.util.List"); assertThat(!desc.isPrimitive()).isTrue(); - assertThat(desc.getAnnotations().length).isEqualTo(0); + assertThat(desc.getAnnotations()).isEmpty(); assertThat(desc.isCollection()).isTrue(); assertThat(desc.isArray()).isFalse(); assertThat((Object) desc.getElementTypeDescriptor()).isNull(); @@ -130,7 +130,7 @@ void parameterArray() throws Exception { assertThat(desc.getName()).isEqualTo("java.lang.Integer[]"); assertThat(desc.toString()).isEqualTo("java.lang.Integer[]"); assertThat(!desc.isPrimitive()).isTrue(); - assertThat(desc.getAnnotations().length).isEqualTo(0); + assertThat(desc.getAnnotations()).isEmpty(); assertThat(desc.isCollection()).isFalse(); assertThat(desc.isArray()).isTrue(); assertThat(desc.getElementTypeDescriptor().getType()).isEqualTo(Integer.class); @@ -147,7 +147,7 @@ void parameterMap() throws Exception { assertThat(desc.getName()).isEqualTo("java.util.Map"); assertThat(desc.toString()).isEqualTo("java.util.Map>"); assertThat(!desc.isPrimitive()).isTrue(); - assertThat(desc.getAnnotations().length).isEqualTo(0); + assertThat(desc.getAnnotations()).isEmpty(); assertThat(desc.isCollection()).isFalse(); assertThat(desc.isArray()).isFalse(); assertThat(desc.isMap()).isTrue(); @@ -162,7 +162,7 @@ void parameterMap() throws Exception { void parameterAnnotated() throws Exception { TypeDescriptor t1 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0)); assertThat(t1.getType()).isEqualTo(String.class); - assertThat(t1.getAnnotations().length).isEqualTo(1); + assertThat(t1.getAnnotations()).hasSize(1); assertThat(t1.getAnnotation(ParameterAnnotation.class)).isNotNull(); assertThat(t1.hasAnnotation(ParameterAnnotation.class)).isTrue(); assertThat(t1.getAnnotation(ParameterAnnotation.class).value()).isEqualTo(123); @@ -335,7 +335,7 @@ void fieldMap() throws Exception { @Test void fieldAnnotated() throws Exception { TypeDescriptor typeDescriptor = new TypeDescriptor(getClass().getField("fieldAnnotated")); - assertThat(typeDescriptor.getAnnotations().length).isEqualTo(1); + assertThat(typeDescriptor.getAnnotations()).hasSize(1); assertThat(typeDescriptor.getAnnotation(FieldAnnotation.class)).isNotNull(); } @@ -463,7 +463,7 @@ void collection() { assertThat(desc.getName()).isEqualTo("java.util.List"); assertThat(desc.toString()).isEqualTo("java.util.List"); assertThat(!desc.isPrimitive()).isTrue(); - assertThat(desc.getAnnotations().length).isEqualTo(0); + assertThat(desc.getAnnotations()).isEmpty(); assertThat(desc.isCollection()).isTrue(); assertThat(desc.isArray()).isFalse(); assertThat(desc.getElementTypeDescriptor().getType()).isEqualTo(Integer.class); @@ -479,7 +479,7 @@ void collectionNested() { assertThat(desc.getName()).isEqualTo("java.util.List"); assertThat(desc.toString()).isEqualTo("java.util.List>"); assertThat(!desc.isPrimitive()).isTrue(); - assertThat(desc.getAnnotations().length).isEqualTo(0); + assertThat(desc.getAnnotations()).isEmpty(); assertThat(desc.isCollection()).isTrue(); assertThat(desc.isArray()).isFalse(); assertThat(desc.getElementTypeDescriptor().getType()).isEqualTo(List.class); @@ -495,7 +495,7 @@ void map() { assertThat(desc.getName()).isEqualTo("java.util.Map"); assertThat(desc.toString()).isEqualTo("java.util.Map"); assertThat(!desc.isPrimitive()).isTrue(); - assertThat(desc.getAnnotations().length).isEqualTo(0); + assertThat(desc.getAnnotations()).isEmpty(); assertThat(desc.isCollection()).isFalse(); assertThat(desc.isArray()).isFalse(); assertThat(desc.isMap()).isTrue(); @@ -512,7 +512,7 @@ void mapNested() { assertThat(desc.getName()).isEqualTo("java.util.Map"); assertThat(desc.toString()).isEqualTo("java.util.Map>"); assertThat(!desc.isPrimitive()).isTrue(); - assertThat(desc.getAnnotations().length).isEqualTo(0); + assertThat(desc.getAnnotations()).isEmpty(); assertThat(desc.isCollection()).isFalse(); assertThat(desc.isArray()).isFalse(); assertThat(desc.isMap()).isTrue(); @@ -524,7 +524,7 @@ void mapNested() { @Test void narrow() { TypeDescriptor desc = TypeDescriptor.valueOf(Number.class); - Integer value = Integer.valueOf(3); + Integer value = 3; desc = desc.narrow(value); assertThat(desc.getType()).isEqualTo(Integer.class); } @@ -532,7 +532,7 @@ void narrow() { @Test void elementType() { TypeDescriptor desc = TypeDescriptor.valueOf(List.class); - Integer value = Integer.valueOf(3); + Integer value = 3; desc = desc.elementTypeDescriptor(value); assertThat(desc.getType()).isEqualTo(Integer.class); } @@ -550,7 +550,7 @@ void elementTypePreserveContext() throws Exception { @Test void mapKeyType() { TypeDescriptor desc = TypeDescriptor.valueOf(Map.class); - Integer value = Integer.valueOf(3); + Integer value = 3; desc = desc.getMapKeyTypeDescriptor(value); assertThat(desc.getType()).isEqualTo(Integer.class); } @@ -568,7 +568,7 @@ void mapKeyTypePreserveContext() throws Exception { @Test void mapValueType() { TypeDescriptor desc = TypeDescriptor.valueOf(Map.class); - Integer value = Integer.valueOf(3); + Integer value = 3; desc = desc.getMapValueTypeDescriptor(value); assertThat(desc.getType()).isEqualTo(Integer.class); } diff --git a/spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java index 69654757eda4..d71ab1342a93 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java @@ -25,7 +25,6 @@ import java.time.ZoneId; import java.util.AbstractList; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Currency; @@ -154,7 +153,7 @@ void shortToString() { @Test void stringToInteger() { - assertThat(conversionService.convert("1", Integer.class)).isEqualTo((int) Integer.valueOf(1)); + assertThat(conversionService.convert("1", Integer.class)).isEqualTo(1); } @Test @@ -259,7 +258,7 @@ void integerToEnumNull() { @Test void enumToInteger() { - assertThat(conversionService.convert(Foo.BAR, Integer.class)).isEqualTo((int) Integer.valueOf(0)); + assertThat(conversionService.convert(Foo.BAR, Integer.class)).isEqualTo(0); } @Test @@ -343,9 +342,7 @@ void characterToNumber() { @Test void convertArrayToCollectionInterface() { List result = conversionService.convert(new String[] {"1", "2", "3"}, List.class); - assertThat(result.get(0)).isEqualTo("1"); - assertThat(result.get(1)).isEqualTo("2"); - assertThat(result.get(2)).isEqualTo("3"); + assertThat(result).isEqualTo(List.of("1", "2", "3")); } @Test @@ -353,9 +350,7 @@ void convertArrayToCollectionGenericTypeConversion() throws Exception { @SuppressWarnings("unchecked") List result = (List) conversionService.convert(new String[] {"1", "2", "3"}, TypeDescriptor .valueOf(String[].class), new TypeDescriptor(getClass().getDeclaredField("genericList"))); - assertThat((int) result.get(0)).isEqualTo((int) Integer.valueOf(1)); - assertThat((int) result.get(1)).isEqualTo((int) Integer.valueOf(2)); - assertThat((int) result.get(2)).isEqualTo((int) Integer.valueOf(3)); + assertThat(result).isEqualTo(List.of(1, 2, 3)); } @Test @@ -365,7 +360,7 @@ void convertArrayToStream() throws Exception { Stream result = (Stream) this.conversionService.convert(source, TypeDescriptor.valueOf(String[].class), new TypeDescriptor(getClass().getDeclaredField("genericStream"))); - assertThat(result.mapToInt(x -> x).sum()).isEqualTo(8); + assertThat(result).containsExactly(1, 3, 4); } @Test @@ -376,17 +371,13 @@ void spr7766() throws Exception { List colors = (List) conversionService.convert(new String[] {"ffffff", "#000000"}, TypeDescriptor.valueOf(String[].class), new TypeDescriptor(new MethodParameter(getClass().getMethod("handlerMethod", List.class), 0))); - assertThat(colors.size()).isEqualTo(2); - assertThat(colors.get(0)).isEqualTo(Color.WHITE); - assertThat(colors.get(1)).isEqualTo(Color.BLACK); + assertThat(colors).containsExactly(Color.WHITE, Color.BLACK); } @Test void convertArrayToCollectionImpl() { ArrayList result = conversionService.convert(new String[] {"1", "2", "3"}, ArrayList.class); - assertThat(result.get(0)).isEqualTo("1"); - assertThat(result.get(1)).isEqualTo("2"); - assertThat(result.get(2)).isEqualTo("3"); + assertThat(result).isEqualTo(List.of("1", "2", "3")); } @Test @@ -416,34 +407,25 @@ void convertEmptyArrayToString() { @Test void convertStringToArray() { String[] result = conversionService.convert("1,2,3", String[].class); - assertThat(result.length).isEqualTo(3); - assertThat(result[0]).isEqualTo("1"); - assertThat(result[1]).isEqualTo("2"); - assertThat(result[2]).isEqualTo("3"); + assertThat(result).containsExactly("1", "2", "3"); } @Test void convertStringToArrayWithElementConversion() { Integer[] result = conversionService.convert("1,2,3", Integer[].class); - assertThat(result.length).isEqualTo(3); - assertThat((int) result[0]).isEqualTo((int) Integer.valueOf(1)); - assertThat((int) result[1]).isEqualTo((int) Integer.valueOf(2)); - assertThat((int) result[2]).isEqualTo((int) Integer.valueOf(3)); + assertThat(result).containsExactly(1, 2, 3); } @Test void convertStringToPrimitiveArrayWithElementConversion() { int[] result = conversionService.convert("1,2,3", int[].class); - assertThat(result.length).isEqualTo(3); - assertThat(result[0]).isEqualTo(1); - assertThat(result[1]).isEqualTo(2); - assertThat(result[2]).isEqualTo(3); + assertThat(result).containsExactly(1, 2, 3); } @Test void convertEmptyStringToArray() { String[] result = conversionService.convert("", String[].class); - assertThat(result.length).isEqualTo(0); + assertThat(result).isEmpty(); } @Test @@ -457,7 +439,7 @@ void convertArrayToObject() { void convertArrayToObjectWithElementConversion() { String[] array = new String[] {"3"}; Integer result = conversionService.convert(array, Integer.class); - assertThat((int) result).isEqualTo((int) Integer.valueOf(3)); + assertThat(result).isEqualTo(3); } @Test @@ -470,51 +452,40 @@ void convertArrayToObjectAssignableTargetType() { @Test void convertObjectToArray() { Object[] result = conversionService.convert(3L, Object[].class); - assertThat(result.length).isEqualTo(1); + assertThat(result).hasSize(1); assertThat(result[0]).isEqualTo(3L); } @Test void convertObjectToArrayWithElementConversion() { Integer[] result = conversionService.convert(3L, Integer[].class); - assertThat(result.length).isEqualTo(1); - assertThat((int) result[0]).isEqualTo((int) Integer.valueOf(3)); + assertThat(result).containsExactly(3); } @Test void convertCollectionToArray() { - List list = new ArrayList<>(); - list.add("1"); - list.add("2"); - list.add("3"); + List list = List.of("1", "2", "3"); String[] result = conversionService.convert(list, String[].class); - assertThat(result[0]).isEqualTo("1"); - assertThat(result[1]).isEqualTo("2"); - assertThat(result[2]).isEqualTo("3"); + assertThat(result).containsExactly("1", "2", "3"); } @Test void convertCollectionToArrayWithElementConversion() { - List list = new ArrayList<>(); - list.add("1"); - list.add("2"); - list.add("3"); + List list = List.of("1", "2", "3"); Integer[] result = conversionService.convert(list, Integer[].class); - assertThat((int) result[0]).isEqualTo((int) Integer.valueOf(1)); - assertThat((int) result[1]).isEqualTo((int) Integer.valueOf(2)); - assertThat((int) result[2]).isEqualTo((int) Integer.valueOf(3)); + assertThat(result).containsExactly(1, 2, 3); } @Test void convertCollectionToString() { - List list = Arrays.asList("foo", "bar"); + List list = List.of("foo", "bar"); String result = conversionService.convert(list, String.class); assertThat(result).isEqualTo("foo,bar"); } @Test void convertCollectionToStringWithElementConversion() throws Exception { - List list = Arrays.asList(3, 5); + List list = List.of(3, 5); String result = (String) conversionService.convert(list, new TypeDescriptor(getClass().getField("genericList")), TypeDescriptor.valueOf(String.class)); assertThat(result).isEqualTo("3,5"); @@ -523,40 +494,34 @@ void convertCollectionToStringWithElementConversion() throws Exception { @Test void convertStringToCollection() { List result = conversionService.convert("1,2,3", List.class); - assertThat(result.size()).isEqualTo(3); - assertThat(result.get(0)).isEqualTo("1"); - assertThat(result.get(1)).isEqualTo("2"); - assertThat(result.get(2)).isEqualTo("3"); + assertThat(result).isEqualTo(List.of("1", "2", "3")); } @Test void convertStringToCollectionWithElementConversion() throws Exception { List result = (List) conversionService.convert("1,2,3", TypeDescriptor.valueOf(String.class), new TypeDescriptor(getClass().getField("genericList"))); - assertThat(result.size()).isEqualTo(3); - assertThat(result.get(0)).isEqualTo(1); - assertThat(result.get(1)).isEqualTo(2); - assertThat(result.get(2)).isEqualTo(3); + assertThat(result).isEqualTo(List.of(1, 2, 3)); } @Test void convertEmptyStringToCollection() { Collection result = conversionService.convert("", Collection.class); - assertThat(result.size()).isEqualTo(0); + assertThat(result).isEmpty(); } @Test void convertCollectionToObject() { List list = Collections.singletonList(3L); Long result = conversionService.convert(list, Long.class); - assertThat(result).isEqualTo(Long.valueOf(3)); + assertThat(result).isEqualTo(3L); } @Test void convertCollectionToObjectWithElementConversion() { List list = Collections.singletonList("3"); Integer result = conversionService.convert(list, Integer.class); - assertThat((int) result).isEqualTo((int) Integer.valueOf(3)); + assertThat(result).isEqualTo(3); } @Test @@ -580,8 +545,7 @@ void convertCollectionToObjectWithCustomConverter() { @Test void convertObjectToCollection() { List result = conversionService.convert(3L, List.class); - assertThat(result.size()).isEqualTo(1); - assertThat(result.get(0)).isEqualTo(3L); + assertThat(result).isEqualTo(List.of(3L)); } @Test @@ -589,56 +553,43 @@ void convertObjectToCollectionWithElementConversion() throws Exception { @SuppressWarnings("unchecked") List result = (List) conversionService.convert(3L, TypeDescriptor.valueOf(Long.class), new TypeDescriptor(getClass().getField("genericList"))); - assertThat(result.size()).isEqualTo(1); - assertThat((int) result.get(0)).isEqualTo((int) Integer.valueOf(3)); + assertThat(result).containsExactly(3); } @Test void convertStringArrayToIntegerArray() { Integer[] result = conversionService.convert(new String[] {"1", "2", "3"}, Integer[].class); - assertThat((int) result[0]).isEqualTo((int) Integer.valueOf(1)); - assertThat((int) result[1]).isEqualTo((int) Integer.valueOf(2)); - assertThat((int) result[2]).isEqualTo((int) Integer.valueOf(3)); + assertThat(result).containsExactly(1, 2, 3); } @Test void convertStringArrayToIntArray() { int[] result = conversionService.convert(new String[] {"1", "2", "3"}, int[].class); - assertThat(result[0]).isEqualTo(1); - assertThat(result[1]).isEqualTo(2); - assertThat(result[2]).isEqualTo(3); + assertThat(result).containsExactly(1, 2, 3); } @Test void convertIntegerArrayToIntegerArray() { Integer[] result = conversionService.convert(new Integer[] {1, 2, 3}, Integer[].class); - assertThat((int) result[0]).isEqualTo((int) Integer.valueOf(1)); - assertThat((int) result[1]).isEqualTo((int) Integer.valueOf(2)); - assertThat((int) result[2]).isEqualTo((int) Integer.valueOf(3)); + assertThat(result).containsExactly(1, 2, 3); } @Test void convertIntegerArrayToIntArray() { int[] result = conversionService.convert(new Integer[] {1, 2, 3}, int[].class); - assertThat(result[0]).isEqualTo(1); - assertThat(result[1]).isEqualTo(2); - assertThat(result[2]).isEqualTo(3); + assertThat(result).containsExactly(1, 2, 3); } @Test void convertObjectArrayToIntegerArray() { Integer[] result = conversionService.convert(new Object[] {1, 2, 3}, Integer[].class); - assertThat((int) result[0]).isEqualTo((int) Integer.valueOf(1)); - assertThat((int) result[1]).isEqualTo((int) Integer.valueOf(2)); - assertThat((int) result[2]).isEqualTo((int) Integer.valueOf(3)); + assertThat(result).containsExactly(1, 2, 3); } @Test void convertObjectArrayToIntArray() { int[] result = conversionService.convert(new Object[] {1, 2, 3}, int[].class); - assertThat(result[0]).isEqualTo(1); - assertThat(result[1]).isEqualTo(2); - assertThat(result[2]).isEqualTo(3); + assertThat(result).containsExactly(1, 2, 3); } @Test @@ -651,28 +602,26 @@ void convertByteArrayToWrapperArray() { @Test void convertArrayToArrayAssignable() { int[] result = conversionService.convert(new int[] {1, 2, 3}, int[].class); - assertThat(result[0]).isEqualTo(1); - assertThat(result[1]).isEqualTo(2); - assertThat(result[2]).isEqualTo(3); + assertThat(result).containsExactly(1, 2, 3); } @Test void convertListOfNonStringifiable() { - List list = Arrays.asList(new TestEntity(1L), new TestEntity(2L)); + List list = List.of(new TestEntity(1L), new TestEntity(2L)); assertThat(conversionService.canConvert(list.getClass(), String.class)).isTrue(); try { conversionService.convert(list, String.class); } catch (ConversionFailedException ex) { - assertThat(ex.getMessage().contains(list.getClass().getName())).isTrue(); - assertThat(ex.getCause() instanceof ConverterNotFoundException).isTrue(); - assertThat(ex.getCause().getMessage().contains(TestEntity.class.getName())).isTrue(); + assertThat(ex.getMessage()).contains(list.getClass().getName()); + assertThat(ex.getCause()).isInstanceOf(ConverterNotFoundException.class); + assertThat(ex.getCause().getMessage()).contains(TestEntity.class.getName()); } } @Test void convertListOfStringToString() { - List list = Arrays.asList("Foo", "Bar"); + List list = List.of("Foo", "Bar"); assertThat(conversionService.canConvert(list.getClass(), String.class)).isTrue(); String result = conversionService.convert(list, String.class); assertThat(result).isEqualTo("Foo,Bar"); @@ -680,9 +629,9 @@ void convertListOfStringToString() { @Test void convertListOfListToString() { - List list1 = Arrays.asList("Foo", "Bar"); - List list2 = Arrays.asList("Baz", "Boop"); - List> list = Arrays.asList(list1, list2); + List list1 = List.of("Foo", "Bar"); + List list2 = List.of("Baz", "Boop"); + List> list = List.of(list1, list2); assertThat(conversionService.canConvert(list.getClass(), String.class)).isTrue(); String result = conversionService.convert(list, String.class); assertThat(result).isEqualTo("Foo,Bar,Baz,Boop"); @@ -697,9 +646,7 @@ void convertCollectionToCollection() throws Exception { @SuppressWarnings("unchecked") List bar = (List) conversionService.convert(foo, TypeDescriptor.forObject(foo), new TypeDescriptor(getClass().getField("genericList"))); - assertThat((int) bar.get(0)).isEqualTo((int) Integer.valueOf(1)); - assertThat((int) bar.get(1)).isEqualTo((int) Integer.valueOf(2)); - assertThat((int) bar.get(2)).isEqualTo((int) Integer.valueOf(3)); + assertThat(bar).containsExactly(1, 2, 3); } @Test @@ -734,10 +681,10 @@ void convertCollectionToCollectionSpecialCaseSourceImpl() throws Exception { Collection values = map.values(); List bar = (List) conversionService.convert(values, TypeDescriptor.forObject(values), new TypeDescriptor(getClass().getField("genericList"))); - assertThat(bar.size()).isEqualTo(3); - assertThat((int) bar.get(0)).isEqualTo((int) Integer.valueOf(1)); - assertThat((int) bar.get(1)).isEqualTo((int) Integer.valueOf(2)); - assertThat((int) bar.get(2)).isEqualTo((int) Integer.valueOf(3)); + assertThat(bar).hasSize(3); + assertThat(bar.get(0)).isEqualTo(1); + assertThat(bar.get(1)).isEqualTo(2); + assertThat(bar.get(2)).isEqualTo(3); } @Test @@ -748,8 +695,8 @@ void collection() { @SuppressWarnings("unchecked") List integers = (List) conversionService.convert(strings, TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Integer.class))); - assertThat((int) integers.get(0)).isEqualTo((int) Integer.valueOf(3)); - assertThat((int) integers.get(1)).isEqualTo((int) Integer.valueOf(9)); + assertThat(integers.get(0)).isEqualTo(3); + assertThat(integers.get(1)).isEqualTo(9); } @Test @@ -770,7 +717,7 @@ void convertHashMapValuesToList() { hashMap.put("1", 1); hashMap.put("2", 2); List converted = conversionService.convert(hashMap.values(), List.class); - assertThat(converted).isEqualTo(Arrays.asList(1, 2)); + assertThat(converted).isEqualTo(List.of(1, 2)); } @Test @@ -781,8 +728,8 @@ void map() { @SuppressWarnings("unchecked") Map integers = (Map) conversionService.convert(strings, TypeDescriptor.map(Map.class, TypeDescriptor.valueOf(Integer.class), TypeDescriptor.valueOf(Integer.class))); - assertThat((int) integers.get(3)).isEqualTo((int) Integer.valueOf(9)); - assertThat((int) integers.get(6)).isEqualTo((int) Integer.valueOf(31)); + assertThat(integers.get(3)).isEqualTo(9); + assertThat(integers.get(6)).isEqualTo(31); } @Test @@ -798,7 +745,7 @@ void convertPropertiesToString() { @Test void convertStringToProperties() { Properties result = conversionService.convert("a=b\nc=2\nd=", Properties.class); - assertThat(result.size()).isEqualTo(3); + assertThat(result).hasSize(3); assertThat(result.getProperty("a")).isEqualTo("b"); assertThat(result.getProperty("c")).isEqualTo("2"); assertThat(result.getProperty("d")).isEqualTo(""); @@ -879,7 +826,7 @@ void convertObjectToObjectWithJavaTimeOfMethod() { @Test void convertObjectToObjectNoValueOfMethodOrConstructor() { assertThatExceptionOfType(ConverterNotFoundException.class).isThrownBy(() -> - conversionService.convert(Long.valueOf(3), SSN.class)); + conversionService.convert(3L, SSN.class)); } @Test @@ -947,7 +894,7 @@ void convertObjectToOptional() { TypeDescriptor descriptor = new TypeDescriptor(parameter); Object actual = conversionService.convert("1,2,3", TypeDescriptor.valueOf(String.class), descriptor); assertThat(actual.getClass()).isEqualTo(Optional.class); - assertThat(((Optional>) actual).get()).isEqualTo(Arrays.asList(1, 2, 3)); + assertThat(((Optional>) actual).get()).isEqualTo(List.of(1, 2, 3)); } @Test diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java index b7eca2c60473..f1ee4af81126 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -195,7 +195,7 @@ private void testCollectionConversionToArrayList(Collection aSource) { aSource, TypeDescriptor.forObject(aSource), TypeDescriptor.forObject(new ArrayList())); boolean condition = myConverted instanceof ArrayList; assertThat(condition).isTrue(); - assertThat(((ArrayList) myConverted).size()).isEqualTo(aSource.size()); + assertThat(((ArrayList) myConverted)).hasSize(aSource.size()); } @Test diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java index fe580d29aba2..a82108ca57ab 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java @@ -24,7 +24,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -104,7 +103,7 @@ void canConvertNullSourceType() { @Test void convert() { conversionService.addConverterFactory(new StringToNumberConverterFactory()); - assertThat(conversionService.convert("3", Integer.class)).isEqualTo((int) Integer.valueOf(3)); + assertThat(conversionService.convert("3", Integer.class)).isEqualTo(3); } @Test @@ -192,7 +191,7 @@ void convertWrongTypeArgument() { void convertSuperSourceType() { conversionService.addConverter(CharSequence.class, Integer.class, source -> Integer.valueOf(source.toString())); Integer result = conversionService.convert("3", Integer.class); - assertThat((int) result).isEqualTo((int) Integer.valueOf(3)); + assertThat(result).isEqualTo(3); } // SPR-8718 @@ -221,7 +220,7 @@ void convertObjectToPrimitiveViaConverterFactory() { conversionService.addConverterFactory(new StringToNumberConverterFactory()); assertThat(conversionService.canConvert(String.class, int.class)).isTrue(); Integer three = conversionService.convert("3", int.class); - assertThat(three.intValue()).isEqualTo(3); + assertThat(three).isEqualTo(3); } @Test @@ -234,26 +233,21 @@ void genericConverterDelegatingBackToConversionServiceConverterNotFound() { @Test void listToIterableConversion() { - List raw = new ArrayList<>(); - raw.add("one"); - raw.add("two"); + List raw = List.of("one", "two"); Object converted = conversionService.convert(raw, Iterable.class); assertThat(converted).isSameAs(raw); } @Test void listToObjectConversion() { - List raw = new ArrayList<>(); - raw.add("one"); - raw.add("two"); + List raw = List.of("one", "two"); Object converted = conversionService.convert(raw, Object.class); assertThat(converted).isSameAs(raw); } @Test void mapToObjectConversion() { - Map raw = new HashMap<>(); - raw.put("key", "value"); + Map raw = Map.of("key", "value"); Object converted = conversionService.convert(raw, Object.class); assertThat(converted).isSameAs(raw); } @@ -341,7 +335,7 @@ void emptyListToArray() { TypeDescriptor sourceType = TypeDescriptor.forObject(list); TypeDescriptor targetType = TypeDescriptor.valueOf(String[].class); assertThat(conversionService.canConvert(sourceType, targetType)).isTrue(); - assertThat(((String[]) conversionService.convert(list, sourceType, targetType)).length).isEqualTo(0); + assertThat(((String[]) conversionService.convert(list, sourceType, targetType))).isEmpty(); } @Test diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java index fd900a79e779..24cc47ef919c 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -240,7 +240,7 @@ void multiValueMapToMultiValueMap() throws Exception { TypeDescriptor targetType = new TypeDescriptor(getClass().getField("multiValueMapTarget")); MultiValueMap converted = (MultiValueMap) conversionService.convert(source, targetType); - assertThat(converted.size()).isEqualTo(2); + assertThat(converted).hasSize(2); assertThat(converted.get("a")).isEqualTo(Arrays.asList("1", "2", "3")); assertThat(converted.get("b")).isEqualTo(Arrays.asList("4", "5", "6")); } @@ -255,7 +255,7 @@ void mapToMultiValueMap() throws Exception { TypeDescriptor targetType = new TypeDescriptor(getClass().getField("multiValueMapTarget")); MultiValueMap converted = (MultiValueMap) conversionService.convert(source, targetType); - assertThat(converted.size()).isEqualTo(2); + assertThat(converted).hasSize(2); assertThat(converted.get("a")).isEqualTo(Arrays.asList("1")); assertThat(converted.get("b")).isEqualTo(Arrays.asList("2")); } diff --git a/spring-core/src/test/java/org/springframework/core/env/MutablePropertySourcesTests.java b/spring-core/src/test/java/org/springframework/core/env/MutablePropertySourcesTests.java index 222104a2a32b..7ff78bf4499e 100644 --- a/spring-core/src/test/java/org/springframework/core/env/MutablePropertySourcesTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/MutablePropertySourcesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -39,7 +39,7 @@ void test() { sources.addLast(new MockPropertySource("d").withProperty("p1", "dValue")); sources.addLast(new MockPropertySource("f").withProperty("p1", "fValue")); - assertThat(sources.size()).isEqualTo(3); + assertThat(sources).hasSize(3); assertThat(sources.contains("a")).isFalse(); assertThat(sources.contains("b")).isTrue(); assertThat(sources.contains("c")).isFalse(); @@ -56,7 +56,7 @@ void test() { sources.addBefore("b", new MockPropertySource("a")); sources.addAfter("b", new MockPropertySource("c")); - assertThat(sources.size()).isEqualTo(5); + assertThat(sources).hasSize(5); assertThat(sources.precedenceOf(PropertySource.named("a"))).isEqualTo(0); assertThat(sources.precedenceOf(PropertySource.named("b"))).isEqualTo(1); assertThat(sources.precedenceOf(PropertySource.named("c"))).isEqualTo(2); @@ -66,7 +66,7 @@ void test() { sources.addBefore("f", new MockPropertySource("e")); sources.addAfter("f", new MockPropertySource("g")); - assertThat(sources.size()).isEqualTo(7); + assertThat(sources).hasSize(7); assertThat(sources.precedenceOf(PropertySource.named("a"))).isEqualTo(0); assertThat(sources.precedenceOf(PropertySource.named("b"))).isEqualTo(1); assertThat(sources.precedenceOf(PropertySource.named("c"))).isEqualTo(2); @@ -76,7 +76,7 @@ void test() { assertThat(sources.precedenceOf(PropertySource.named("g"))).isEqualTo(6); sources.addLast(new MockPropertySource("a")); - assertThat(sources.size()).isEqualTo(7); + assertThat(sources).hasSize(7); assertThat(sources.precedenceOf(PropertySource.named("b"))).isEqualTo(0); assertThat(sources.precedenceOf(PropertySource.named("c"))).isEqualTo(1); assertThat(sources.precedenceOf(PropertySource.named("d"))).isEqualTo(2); @@ -86,7 +86,7 @@ void test() { assertThat(sources.precedenceOf(PropertySource.named("a"))).isEqualTo(6); sources.addFirst(new MockPropertySource("a")); - assertThat(sources.size()).isEqualTo(7); + assertThat(sources).hasSize(7); assertThat(sources.precedenceOf(PropertySource.named("a"))).isEqualTo(0); assertThat(sources.precedenceOf(PropertySource.named("b"))).isEqualTo(1); assertThat(sources.precedenceOf(PropertySource.named("c"))).isEqualTo(2); @@ -96,11 +96,11 @@ void test() { assertThat(sources.precedenceOf(PropertySource.named("g"))).isEqualTo(6); assertThat(PropertySource.named("a")).isEqualTo(sources.remove("a")); - assertThat(sources.size()).isEqualTo(6); + assertThat(sources).hasSize(6); assertThat(sources.contains("a")).isFalse(); assertThat((Object) sources.remove("a")).isNull(); - assertThat(sources.size()).isEqualTo(6); + assertThat(sources).hasSize(6); String bogusPS = "bogus"; assertThatIllegalArgumentException().isThrownBy(() -> @@ -108,13 +108,13 @@ void test() { .withMessageContaining("does not exist"); sources.addFirst(new MockPropertySource("a")); - assertThat(sources.size()).isEqualTo(7); + assertThat(sources).hasSize(7); assertThat(sources.precedenceOf(PropertySource.named("a"))).isEqualTo(0); assertThat(sources.precedenceOf(PropertySource.named("b"))).isEqualTo(1); assertThat(sources.precedenceOf(PropertySource.named("c"))).isEqualTo(2); sources.replace("a", new MockPropertySource("a-replaced")); - assertThat(sources.size()).isEqualTo(7); + assertThat(sources).hasSize(7); assertThat(sources.precedenceOf(PropertySource.named("a-replaced"))).isEqualTo(0); assertThat(sources.precedenceOf(PropertySource.named("b"))).isEqualTo(1); assertThat(sources.precedenceOf(PropertySource.named("c"))).isEqualTo(2); diff --git a/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java b/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java index 6256832b2f63..beeaf18e8ea4 100644 --- a/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java @@ -89,7 +89,7 @@ void collectionsOperations() { assertThat(propertySources.remove(PropertySource.named("ps1"))).isTrue(); assertThat(propertySources).hasSize(1); assertThat(propertySources.remove(PropertySource.named("ps1"))).isTrue(); - assertThat(propertySources).hasSize(0); + assertThat(propertySources).isEmpty(); PropertySource ps2 = new MapPropertySource("ps2", map2); propertySources.add(ps1); diff --git a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java index 5bf89d311f03..0998e75be4eb 100644 --- a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -113,12 +113,12 @@ void propertySourceTypes() { @Test void activeProfilesIsEmptyByDefault() { - assertThat(environment.getActiveProfiles().length).isEqualTo(0); + assertThat(environment.getActiveProfiles()).isEmpty(); } @Test void defaultProfilesContainsDefaultProfileByDefault() { - assertThat(environment.getDefaultProfiles().length).isEqualTo(1); + assertThat(environment.getDefaultProfiles()).hasSize(1); assertThat(environment.getDefaultProfiles()[0]).isEqualTo("default"); } @@ -127,7 +127,7 @@ void setActiveProfiles() { environment.setActiveProfiles("local", "embedded"); String[] activeProfiles = environment.getActiveProfiles(); assertThat(activeProfiles).contains("local", "embedded"); - assertThat(activeProfiles.length).isEqualTo(2); + assertThat(activeProfiles).hasSize(2); } @Test @@ -172,17 +172,17 @@ void setDefaultProfiles_withNotOperator() { @Test void addActiveProfile() { - assertThat(environment.getActiveProfiles().length).isEqualTo(0); + assertThat(environment.getActiveProfiles()).isEmpty(); environment.setActiveProfiles("local", "embedded"); assertThat(environment.getActiveProfiles()).contains("local", "embedded"); - assertThat(environment.getActiveProfiles().length).isEqualTo(2); + assertThat(environment.getActiveProfiles()).hasSize(2); environment.addActiveProfile("p1"); assertThat(environment.getActiveProfiles()).contains("p1"); - assertThat(environment.getActiveProfiles().length).isEqualTo(3); + assertThat(environment.getActiveProfiles()).hasSize(3); environment.addActiveProfile("p2"); environment.addActiveProfile("p3"); assertThat(environment.getActiveProfiles()).contains("p2", "p3"); - assertThat(environment.getActiveProfiles().length).isEqualTo(5); + assertThat(environment.getActiveProfiles()).hasSize(5); } @Test @@ -218,9 +218,9 @@ void defaultProfileWithCircularPlaceholder() { @Test void getActiveProfiles_systemPropertiesEmpty() { - assertThat(environment.getActiveProfiles().length).isEqualTo(0); + assertThat(environment.getActiveProfiles()).isEmpty(); System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, ""); - assertThat(environment.getActiveProfiles().length).isEqualTo(0); + assertThat(environment.getActiveProfiles()).isEmpty(); System.clearProperty(ACTIVE_PROFILES_PROPERTY_NAME); } @@ -249,14 +249,14 @@ void getActiveProfiles_fromSystemProperties_withMultipleProfiles_withWhitespace( void getDefaultProfiles() { assertThat(environment.getDefaultProfiles()).isEqualTo(new String[] {RESERVED_DEFAULT_PROFILE_NAME}); environment.getPropertySources().addFirst(new MockPropertySource().withProperty(DEFAULT_PROFILES_PROPERTY_NAME, "pd1")); - assertThat(environment.getDefaultProfiles().length).isEqualTo(1); + assertThat(environment.getDefaultProfiles()).hasSize(1); assertThat(Arrays.asList(environment.getDefaultProfiles())).contains("pd1"); } @Test void setDefaultProfiles() { environment.setDefaultProfiles(); - assertThat(environment.getDefaultProfiles().length).isEqualTo(0); + assertThat(environment.getDefaultProfiles()).isEmpty(); environment.setDefaultProfiles("pd1"); assertThat(Arrays.asList(environment.getDefaultProfiles())).contains("pd1"); environment.setDefaultProfiles("pd2", "pd3"); diff --git a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java index 786290a3f9f3..6cd9c4bc9b29 100644 --- a/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java @@ -318,6 +318,9 @@ void inputStream(DataBufferFactory bufferFactory) throws Exception { assertThat(result).isEqualTo((byte) 'b'); assertThat(inputStream.available()).isEqualTo(3); + assertThat(inputStream.markSupported()).isTrue(); + inputStream.mark(2); + byte[] bytes = new byte[2]; int len = inputStream.read(bytes); assertThat(len).isEqualTo(2); @@ -333,6 +336,12 @@ void inputStream(DataBufferFactory bufferFactory) throws Exception { assertThat(inputStream.read()).isEqualTo(-1); assertThat(inputStream.read(bytes)).isEqualTo(-1); + inputStream.reset(); + bytes = new byte[3]; + len = inputStream.read(bytes); + assertThat(len).isEqualTo(3); + assertThat(bytes).containsExactly('c', 'd', 'e'); + release(buffer); } diff --git a/spring-core/src/test/java/org/springframework/core/serializer/SerializationConverterTests.java b/spring-core/src/test/java/org/springframework/core/serializer/SerializationConverterTests.java index 7b75825d0c6e..f57abf820e7c 100644 --- a/spring-core/src/test/java/org/springframework/core/serializer/SerializationConverterTests.java +++ b/spring-core/src/test/java/org/springframework/core/serializer/SerializationConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/test/java/org/springframework/core/task/SimpleAsyncTaskExecutorTests.java b/spring-core/src/test/java/org/springframework/core/task/SimpleAsyncTaskExecutorTests.java index 70376abc931c..fbdf751435e6 100644 --- a/spring-core/src/test/java/org/springframework/core/task/SimpleAsyncTaskExecutorTests.java +++ b/spring-core/src/test/java/org/springframework/core/task/SimpleAsyncTaskExecutorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java index c7f9d0a3b031..2066b056a741 100644 --- a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java @@ -90,7 +90,7 @@ private void doTestSubClassAnnotationInfo(AnnotationMetadata metadata, boolean a assertThat(metadata.isConcrete()).isTrue(); assertThat(metadata.hasSuperClass()).isTrue(); assertThat(metadata.getSuperClassName()).isEqualTo(AnnotatedComponent.class.getName()); - assertThat(metadata.getInterfaceNames().length).isEqualTo(0); + assertThat(metadata.getInterfaceNames()).isEmpty(); assertThat(metadata.isAnnotated(Component.class.getName())).isFalse(); assertThat(metadata.isAnnotated(Scope.class.getName())).isFalse(); assertThat(metadata.isAnnotated(SpecialAttr.class.getName())).isFalse(); @@ -114,7 +114,7 @@ private void doTestSubClassAnnotationInfo(AnnotationMetadata metadata, boolean a assertThat(metadata.getAnnotationAttributes(Component.class.getName())).isNull(); assertThat(metadata.getAnnotationAttributes(MetaAnnotation.class.getName(), false)).isNull(); assertThat(metadata.getAnnotationAttributes(MetaAnnotation.class.getName(), true)).isNull(); - assertThat(metadata.getAnnotatedMethods(DirectAnnotation.class.getName()).size()).isEqualTo(0); + assertThat(metadata.getAnnotatedMethods(DirectAnnotation.class.getName())).isEmpty(); assertThat(metadata.isAnnotated(IsAnnotatedAnnotation.class.getName())).isFalse(); assertThat(metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName())).isNull(); } @@ -141,10 +141,10 @@ private void doTestMetadataForInterfaceClass(AnnotationMetadata metadata) { assertThat(metadata.isConcrete()).isFalse(); assertThat(metadata.hasSuperClass()).isFalse(); assertThat(metadata.getSuperClassName()).isNull(); - assertThat(metadata.getInterfaceNames().length).isEqualTo(2); + assertThat(metadata.getInterfaceNames()).hasSize(2); assertThat(metadata.getInterfaceNames()[0]).isEqualTo(ClassMetadata.class.getName()); assertThat(metadata.getInterfaceNames()[1]).isEqualTo(AnnotatedTypeMetadata.class.getName()); - assertThat(metadata.getAnnotationTypes()).hasSize(0); + assertThat(metadata.getAnnotationTypes()).isEmpty(); } @Test @@ -169,7 +169,7 @@ private void doTestMetadataForAnnotationClass(AnnotationMetadata metadata) { assertThat(metadata.isConcrete()).isFalse(); assertThat(metadata.hasSuperClass()).isFalse(); assertThat(metadata.getSuperClassName()).isNull(); - assertThat(metadata.getInterfaceNames().length).isEqualTo(1); + assertThat(metadata.getInterfaceNames()).hasSize(1); assertThat(metadata.getInterfaceNames()[0]).isEqualTo(Annotation.class.getName()); assertThat(metadata.isAnnotated(Documented.class.getName())).isFalse(); assertThat(metadata.isAnnotated(Scope.class.getName())).isFalse(); @@ -287,7 +287,7 @@ private void doTestAnnotationInfo(AnnotationMetadata metadata) { assertThat(metadata.isConcrete()).isTrue(); assertThat(metadata.hasSuperClass()).isTrue(); assertThat(metadata.getSuperClassName()).isEqualTo(Object.class.getName()); - assertThat(metadata.getInterfaceNames().length).isEqualTo(1); + assertThat(metadata.getInterfaceNames()).hasSize(1); assertThat(metadata.getInterfaceNames()[0]).isEqualTo(Serializable.class.getName()); assertThat(metadata.isAnnotated(Component.class.getName())).isTrue(); @@ -335,7 +335,7 @@ private void doTestAnnotationInfo(AnnotationMetadata metadata) { assertThat((Class[]) nestedAnno.get("classArray")).isEqualTo(new Class[] {String.class}); AnnotationAttributes[] nestedAnnoArray = specialAttrs.getAnnotationArray("nestedAnnoArray"); - assertThat(nestedAnnoArray.length).isEqualTo(2); + assertThat(nestedAnnoArray).hasSize(2); assertThat(nestedAnnoArray[0].getString("value")).isEqualTo("default"); assertThat(nestedAnnoArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT)).isTrue(); assertThat((Class[]) nestedAnnoArray[0].get("classArray")).isEqualTo(new Class[] {Void.class}); @@ -351,7 +351,7 @@ private void doTestAnnotationInfo(AnnotationMetadata metadata) { assertThat(optional.getClassArray("classArray")).isEqualTo(new Class[] {Void.class}); AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray"); - assertThat(optionalArray.length).isEqualTo(1); + assertThat(optionalArray).hasSize(1); assertThat(optionalArray[0].getString("value")).isEqualTo("optional"); assertThat(optionalArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT)).isTrue(); assertThat((Class[]) optionalArray[0].get("classArray")).isEqualTo(new Class[] {Void.class}); @@ -363,7 +363,7 @@ private void doTestAnnotationInfo(AnnotationMetadata metadata) { allMeta = metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("additional"); assertThat(new HashSet<>(allMeta)).isEqualTo(new HashSet(Arrays.asList("direct", ""))); assertThat(metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("additional")).isEqualTo(""); - assertThat(((String[]) metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("additionalArray")).length).isEqualTo(0); + assertThat(((String[]) metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("additionalArray"))).isEmpty(); } { // perform tests with classValuesAsString = true AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes( diff --git a/spring-core/src/test/java/org/springframework/tests/MockitoUtils.java b/spring-core/src/test/java/org/springframework/tests/MockitoUtils.java index 60df908e6019..b0cb590548ff 100644 --- a/spring-core/src/test/java/org/springframework/tests/MockitoUtils.java +++ b/spring-core/src/test/java/org/springframework/tests/MockitoUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -50,7 +50,7 @@ public static void verifySameInvocations(T expected, T actual, InvocationArg private static void verifySameInvocations(List expectedInvocations, List actualInvocations, InvocationArgumentsAdapter... argumentAdapters) { - assertThat(expectedInvocations.size()).isEqualTo(actualInvocations.size()); + assertThat(expectedInvocations).hasSize(actualInvocations.size()); for (int i = 0; i < expectedInvocations.size(); i++) { verifySameInvocation(expectedInvocations.get(i), actualInvocations.get(i), argumentAdapters); } diff --git a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java index 71eb80a23f03..5f1256a70672 100644 --- a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java +++ b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java @@ -647,22 +647,22 @@ void cachePatternsSetToTrue() { @Test void preventCreatingStringMatchersIfPathDoesNotStartsWithPatternPrefix() { pathMatcher.setCachePatterns(true); - assertThat(pathMatcher.stringMatcherCache.size()).isEqualTo(0); + assertThat(pathMatcher.stringMatcherCache).isEmpty(); pathMatcher.match("test?", "test"); - assertThat(pathMatcher.stringMatcherCache.size()).isEqualTo(1); + assertThat(pathMatcher.stringMatcherCache).hasSize(1); pathMatcher.match("test?", "best"); pathMatcher.match("test/*", "view/test.jpg"); pathMatcher.match("test/**/test.jpg", "view/test.jpg"); pathMatcher.match("test/{name}.jpg", "view/test.jpg"); - assertThat(pathMatcher.stringMatcherCache.size()).isEqualTo(1); + assertThat(pathMatcher.stringMatcherCache).hasSize(1); } @Test void creatingStringMatchersIfPatternPrefixCannotDetermineIfPathMatch() { pathMatcher.setCachePatterns(true); - assertThat(pathMatcher.stringMatcherCache.size()).isEqualTo(0); + assertThat(pathMatcher.stringMatcherCache).isEmpty(); pathMatcher.match("test", "testian"); pathMatcher.match("test?", "testFf"); @@ -673,7 +673,7 @@ void creatingStringMatchersIfPatternPrefixCannotDetermineIfPathMatch() { pathMatcher.match("/**/{name}.jpg", "/test/lorem.jpg"); pathMatcher.match("/*/dir/{name}.jpg", "/*/dir/lorem.jpg"); - assertThat(pathMatcher.stringMatcherCache.size()).isEqualTo(7); + assertThat(pathMatcher.stringMatcherCache).hasSize(7); } @Test diff --git a/spring-core/src/test/java/org/springframework/util/CollectionUtilsTests.java b/spring-core/src/test/java/org/springframework/util/CollectionUtilsTests.java index 451a6895ccff..56d520157f0b 100644 --- a/spring-core/src/test/java/org/springframework/util/CollectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/CollectionUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -70,12 +70,12 @@ void mergeArrayIntoCollection() { void mergePrimitiveArrayIntoCollection() { int[] arr = new int[] {1, 2}; List> list = new ArrayList<>(); - list.add(Integer.valueOf(3)); + list.add(3); CollectionUtils.mergeArrayIntoCollection(arr, list); - assertThat(list.get(0)).isEqualTo(Integer.valueOf(3)); - assertThat(list.get(1)).isEqualTo(Integer.valueOf(1)); - assertThat(list.get(2)).isEqualTo(Integer.valueOf(2)); + assertThat(list.get(0)).isEqualTo(3); + assertThat(list.get(1)).isEqualTo(1); + assertThat(list.get(2)).isEqualTo(2); } @Test @@ -84,7 +84,7 @@ void mergePropertiesIntoMap() { defaults.setProperty("prop1", "value1"); Properties props = new Properties(defaults); props.setProperty("prop2", "value2"); - props.put("prop3", Integer.valueOf(3)); + props.put("prop3", 3); Map map = new HashMap<>(); map.put("prop4", "value4"); @@ -92,7 +92,7 @@ void mergePropertiesIntoMap() { CollectionUtils.mergePropertiesIntoMap(props, map); assertThat(map.get("prop1")).isEqualTo("value1"); assertThat(map.get("prop2")).isEqualTo("value2"); - assertThat(map.get("prop3")).isEqualTo(Integer.valueOf(3)); + assertThat(map.get("prop3")).isEqualTo(3); assertThat(map.get("prop4")).isEqualTo("value4"); } diff --git a/spring-core/src/test/java/org/springframework/util/ConcurrentReferenceHashMapTests.java b/spring-core/src/test/java/org/springframework/util/ConcurrentReferenceHashMapTests.java index ca9c41147c41..df1ef39989ac 100644 --- a/spring-core/src/test/java/org/springframework/util/ConcurrentReferenceHashMapTests.java +++ b/spring-core/src/test/java/org/springframework/util/ConcurrentReferenceHashMapTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -126,7 +126,7 @@ void shouldNeedPositiveConcurrencyLevel() { @Test void shouldPutAndGet() { // NOTE we are using mock references so we don't need to worry about GC - assertThat(this.map).hasSize(0); + assertThat(this.map).isEmpty(); this.map.put(123, "123"); assertThat(this.map.get(123)).isEqualTo("123"); assertThat(this.map).hasSize(1); @@ -327,7 +327,7 @@ void shouldReplaceNullValue() { @Test void shouldGetSize() { - assertThat(this.map).hasSize(0); + assertThat(this.map).isEmpty(); this.map.put(123, "123"); this.map.put(123, null); this.map.put(456, "456"); @@ -400,7 +400,7 @@ void shouldClear() { this.map.put(456, null); this.map.put(null, "789"); this.map.clear(); - assertThat(this.map).hasSize(0); + assertThat(this.map).isEmpty(); assertThat(this.map.containsKey(123)).isFalse(); assertThat(this.map.containsKey(456)).isFalse(); assertThat(this.map.containsKey(null)).isFalse(); diff --git a/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java b/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java index 692d6876f159..675810fe96e3 100644 --- a/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java +++ b/spring-core/src/test/java/org/springframework/util/FastByteArrayOutputStreamTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -45,7 +45,7 @@ class FastByteArrayOutputStreamTests { @Test void size() throws Exception { this.os.write(this.helloBytes); - assertThat(this.helloBytes.length).isEqualTo(this.os.size()); + assertThat(this.helloBytes).hasSize(this.os.size()); } @Test @@ -123,7 +123,7 @@ void getInputStream() throws Exception { @Test void getInputStreamAvailable() throws Exception { this.os.write(this.helloBytes); - assertThat(this.helloBytes.length).isEqualTo(this.os.getInputStream().available()); + assertThat(this.helloBytes).hasSize(this.os.getInputStream().available()); } @Test diff --git a/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java b/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java index 9f50d9d1e9e7..2bf9247ac839 100644 --- a/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java +++ b/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -38,7 +38,7 @@ void putAndGet() { assertThat(map.put("key", "value1")).isNull(); assertThat(map.put("key", "value2")).isEqualTo("value1"); assertThat(map.put("key", "value3")).isEqualTo("value2"); - assertThat(map.size()).isEqualTo(1); + assertThat(map).hasSize(1); assertThat(map.get("key")).isEqualTo("value3"); assertThat(map.get("KEY")).isEqualTo("value3"); assertThat(map.get("Key")).isEqualTo("value3"); @@ -55,7 +55,7 @@ void putWithOverlappingKeys() { assertThat(map.put("key", "value1")).isNull(); assertThat(map.put("KEY", "value2")).isEqualTo("value1"); assertThat(map.put("Key", "value3")).isEqualTo("value2"); - assertThat(map.size()).isEqualTo(1); + assertThat(map).hasSize(1); assertThat(map.get("key")).isEqualTo("value3"); assertThat(map.get("KEY")).isEqualTo("value3"); assertThat(map.get("Key")).isEqualTo("value3"); @@ -128,8 +128,8 @@ void mapClone() { assertThat(copy.get("Key")).isEqualTo("value1"); copy.put("Key", "value2"); - assertThat(map.size()).isEqualTo(1); - assertThat(copy.size()).isEqualTo(1); + assertThat(map).hasSize(1); + assertThat(copy).hasSize(1); assertThat(map.get("key")).isEqualTo("value1"); assertThat(map.get("KEY")).isEqualTo("value1"); assertThat(map.get("Key")).isEqualTo("value1"); @@ -159,7 +159,7 @@ void removeFromKeySet() { void removeFromKeySetViaIterator() { map.put("key", "value"); nextAndRemove(map.keySet().iterator()); - assertThat(map.size()).isEqualTo(0); + assertThat(map).isEmpty(); map.computeIfAbsent("key", k -> "newvalue"); assertThat(map.get("key")).isEqualTo("newvalue"); } @@ -168,7 +168,7 @@ void removeFromKeySetViaIterator() { void clearFromValues() { map.put("key", "value"); map.values().clear(); - assertThat(map.size()).isEqualTo(0); + assertThat(map).isEmpty(); map.computeIfAbsent("key", k -> "newvalue"); assertThat(map.get("key")).isEqualTo("newvalue"); } @@ -177,7 +177,7 @@ void clearFromValues() { void removeFromValues() { map.put("key", "value"); map.values().remove("value"); - assertThat(map.size()).isEqualTo(0); + assertThat(map).isEmpty(); map.computeIfAbsent("key", k -> "newvalue"); assertThat(map.get("key")).isEqualTo("newvalue"); } @@ -186,7 +186,7 @@ void removeFromValues() { void removeFromValuesViaIterator() { map.put("key", "value"); nextAndRemove(map.values().iterator()); - assertThat(map.size()).isEqualTo(0); + assertThat(map).isEmpty(); map.computeIfAbsent("key", k -> "newvalue"); assertThat(map.get("key")).isEqualTo("newvalue"); } @@ -195,7 +195,7 @@ void removeFromValuesViaIterator() { void clearFromEntrySet() { map.put("key", "value"); map.entrySet().clear(); - assertThat(map.size()).isEqualTo(0); + assertThat(map).isEmpty(); map.computeIfAbsent("key", k -> "newvalue"); assertThat(map.get("key")).isEqualTo("newvalue"); } @@ -204,7 +204,7 @@ void clearFromEntrySet() { void removeFromEntrySet() { map.put("key", "value"); map.entrySet().remove(map.entrySet().iterator().next()); - assertThat(map.size()).isEqualTo(0); + assertThat(map).isEmpty(); map.computeIfAbsent("key", k -> "newvalue"); assertThat(map.get("key")).isEqualTo("newvalue"); } @@ -213,7 +213,7 @@ void removeFromEntrySet() { void removeFromEntrySetViaIterator() { map.put("key", "value"); nextAndRemove(map.entrySet().iterator()); - assertThat(map.size()).isEqualTo(0); + assertThat(map).isEmpty(); map.computeIfAbsent("key", k -> "newvalue"); assertThat(map.get("key")).isEqualTo("newvalue"); } diff --git a/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java b/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java index 9c62f82be106..2fa926a0ea23 100644 --- a/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java +++ b/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -344,7 +344,7 @@ private void testWithQuotedParameters(String... mimeTypes) { String s = String.join(",", mimeTypes); List actual = MimeTypeUtils.parseMimeTypes(s); - assertThat(actual.size()).isEqualTo(mimeTypes.length); + assertThat(actual).hasSize(mimeTypes.length); for (int i = 0; i < mimeTypes.length; i++) { assertThat(actual.get(i).toString()).isEqualTo(mimeTypes[i]); } diff --git a/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java b/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java index 58581114e36e..8c1bf473d04c 100644 --- a/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -134,7 +134,7 @@ void parseNumberAsNegativeHex() { @Test void convertDoubleToBigInteger() { - Double decimal = Double.valueOf(3.14d); + Double decimal = 3.14d; assertThat(NumberUtils.convertNumberToTargetClass(decimal, BigInteger.class)).isEqualTo(new BigInteger("3")); } @@ -272,48 +272,48 @@ void parseNegativeOverflowUsingNumberFormat() { @Test void convertToInteger() { - assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(-1), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(-1))); - assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(0), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(0))); - assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(1), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(1))); + assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(-1), Integer.class)).isEqualTo(Integer.valueOf(-1)); + assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(0), Integer.class)).isEqualTo(Integer.valueOf(0)); + assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(1), Integer.class)).isEqualTo(Integer.valueOf(1)); assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Integer.MAX_VALUE), Integer.class)).isEqualTo(Integer.valueOf(Integer.MAX_VALUE)); assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Integer.MAX_VALUE + 1), Integer.class)).isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Integer.MIN_VALUE), Integer.class)).isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Integer.MIN_VALUE - 1), Integer.class)).isEqualTo(Integer.valueOf(Integer.MAX_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(-1), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(-1))); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(0), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(0))); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(1), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(1))); + assertThat(NumberUtils.convertNumberToTargetClass((long) -1, Integer.class)).isEqualTo(Integer.valueOf(-1)); + assertThat(NumberUtils.convertNumberToTargetClass(0L, Integer.class)).isEqualTo(Integer.valueOf(0)); + assertThat(NumberUtils.convertNumberToTargetClass(1L, Integer.class)).isEqualTo(Integer.valueOf(1)); assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(Integer.MAX_VALUE), Integer.class)).isEqualTo(Integer.valueOf(Integer.MAX_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(Integer.MAX_VALUE + 1), Integer.class)).isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(Integer.MIN_VALUE), Integer.class)).isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(Integer.MIN_VALUE - 1), Integer.class)).isEqualTo(Integer.valueOf(Integer.MAX_VALUE)); - - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(-1), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(-1))); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(0), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(0))); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(1), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(1))); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(Integer.MAX_VALUE), Integer.class)).isEqualTo(Integer.valueOf(Integer.MAX_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(Integer.MAX_VALUE + 1), Integer.class)).isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(Integer.MIN_VALUE), Integer.class)).isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(Integer.MIN_VALUE - 1), Integer.class)).isEqualTo(Integer.valueOf(Integer.MAX_VALUE)); - - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf((short) -1), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(-1))); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf((short) 0), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(0))); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf((short) 1), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(1))); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf(Short.MAX_VALUE), Integer.class)).isEqualTo(Integer.valueOf(Short.MAX_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf((short) (Short.MAX_VALUE + 1)), Integer.class)).isEqualTo(Integer.valueOf(Short.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf(Short.MIN_VALUE), Integer.class)).isEqualTo(Integer.valueOf(Short.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf((short) (Short.MIN_VALUE - 1)), Integer.class)).isEqualTo(Integer.valueOf(Short.MAX_VALUE)); - - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) -1), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(-1))); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) 0), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(0))); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) 1), Integer.class)).isEqualTo(Integer.valueOf(Integer.valueOf(1))); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf(Byte.MAX_VALUE), Integer.class)).isEqualTo(Integer.valueOf(Byte.MAX_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MAX_VALUE + 1)), Integer.class)).isEqualTo(Integer.valueOf(Byte.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf(Byte.MIN_VALUE), Integer.class)).isEqualTo(Integer.valueOf(Byte.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MIN_VALUE - 1)), Integer.class)).isEqualTo(Integer.valueOf(Byte.MAX_VALUE)); - - assertToNumberOverflow(Long.valueOf(Long.MAX_VALUE + 1), Integer.class); - assertToNumberOverflow(Long.valueOf(Long.MIN_VALUE - 1), Integer.class); + assertThat(NumberUtils.convertNumberToTargetClass((long) (Integer.MAX_VALUE + 1), Integer.class)).isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass((long) Integer.MIN_VALUE, Integer.class)).isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass((long) (Integer.MIN_VALUE - 1), Integer.class)).isEqualTo(Integer.valueOf(Integer.MAX_VALUE)); + + assertThat(NumberUtils.convertNumberToTargetClass(-1, Integer.class)).isEqualTo(Integer.valueOf(-1)); + assertThat(NumberUtils.convertNumberToTargetClass(0, Integer.class)).isEqualTo(Integer.valueOf(0)); + assertThat(NumberUtils.convertNumberToTargetClass(1, Integer.class)).isEqualTo(Integer.valueOf(1)); + assertThat(NumberUtils.convertNumberToTargetClass(Integer.MAX_VALUE, Integer.class)).isEqualTo(Integer.valueOf(Integer.MAX_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Integer.MAX_VALUE + 1, Integer.class)).isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Integer.MIN_VALUE, Integer.class)).isEqualTo(Integer.valueOf(Integer.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Integer.MIN_VALUE - 1, Integer.class)).isEqualTo(Integer.valueOf(Integer.MAX_VALUE)); + + assertThat(NumberUtils.convertNumberToTargetClass((short) -1, Integer.class)).isEqualTo(Integer.valueOf(-1)); + assertThat(NumberUtils.convertNumberToTargetClass((short) 0, Integer.class)).isEqualTo(Integer.valueOf(0)); + assertThat(NumberUtils.convertNumberToTargetClass((short) 1, Integer.class)).isEqualTo(Integer.valueOf(1)); + assertThat(NumberUtils.convertNumberToTargetClass(Short.MAX_VALUE, Integer.class)).isEqualTo(Integer.valueOf(Short.MAX_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass((short) (Short.MAX_VALUE + 1), Integer.class)).isEqualTo(Integer.valueOf(Short.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Short.MIN_VALUE, Integer.class)).isEqualTo(Integer.valueOf(Short.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass((short) (Short.MIN_VALUE - 1), Integer.class)).isEqualTo(Integer.valueOf(Short.MAX_VALUE)); + + assertThat(NumberUtils.convertNumberToTargetClass((byte) -1, Integer.class)).isEqualTo(Integer.valueOf(-1)); + assertThat(NumberUtils.convertNumberToTargetClass((byte) 0, Integer.class)).isEqualTo(Integer.valueOf(0)); + assertThat(NumberUtils.convertNumberToTargetClass((byte) 1, Integer.class)).isEqualTo(Integer.valueOf(1)); + assertThat(NumberUtils.convertNumberToTargetClass(Byte.MAX_VALUE, Integer.class)).isEqualTo(Integer.valueOf(Byte.MAX_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass((byte) (Byte.MAX_VALUE + 1), Integer.class)).isEqualTo(Integer.valueOf(Byte.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Byte.MIN_VALUE, Integer.class)).isEqualTo(Integer.valueOf(Byte.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass((byte) (Byte.MIN_VALUE - 1), Integer.class)).isEqualTo(Integer.valueOf(Byte.MAX_VALUE)); + + assertToNumberOverflow(Long.MAX_VALUE + 1, Integer.class); + assertToNumberOverflow(Long.MIN_VALUE - 1, Integer.class); assertToNumberOverflow(BigInteger.valueOf(Integer.MAX_VALUE).add(BigInteger.ONE), Integer.class); assertToNumberOverflow(BigInteger.valueOf(Integer.MIN_VALUE).subtract(BigInteger.ONE), Integer.class); assertToNumberOverflow(new BigDecimal("18446744073709551611"), Integer.class); @@ -321,45 +321,45 @@ void convertToInteger() { @Test void convertToLong() { - assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(-1), Long.class)).isEqualTo(Long.valueOf(Long.valueOf(-1))); - assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(0), Long.class)).isEqualTo(Long.valueOf(Long.valueOf(0))); - assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(1), Long.class)).isEqualTo(Long.valueOf(Long.valueOf(1))); + assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(-1), Long.class)).isEqualTo(Long.valueOf(-1)); + assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(0), Long.class)).isEqualTo(Long.valueOf(0)); + assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(1), Long.class)).isEqualTo(Long.valueOf(1)); assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Long.MAX_VALUE), Long.class)).isEqualTo(Long.valueOf(Long.MAX_VALUE)); assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Long.MAX_VALUE + 1), Long.class)).isEqualTo(Long.valueOf(Long.MIN_VALUE)); assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Long.MIN_VALUE), Long.class)).isEqualTo(Long.valueOf(Long.MIN_VALUE)); assertThat(NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Long.MIN_VALUE - 1), Long.class)).isEqualTo(Long.valueOf(Long.MAX_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(-1), Long.class)).isEqualTo(Long.valueOf(Long.valueOf(-1))); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(0), Long.class)).isEqualTo(Long.valueOf(Long.valueOf(0))); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(1), Long.class)).isEqualTo(Long.valueOf(Long.valueOf(1))); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(Long.MAX_VALUE), Long.class)).isEqualTo(Long.valueOf(Long.MAX_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(Long.MAX_VALUE + 1), Long.class)).isEqualTo(Long.valueOf(Long.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(Long.MIN_VALUE), Long.class)).isEqualTo(Long.valueOf(Long.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Long.valueOf(Long.MIN_VALUE - 1), Long.class)).isEqualTo(Long.valueOf(Long.MAX_VALUE)); - - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(-1), Long.class)).isEqualTo(Long.valueOf(Integer.valueOf(-1))); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(0), Long.class)).isEqualTo(Long.valueOf(Integer.valueOf(0))); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(1), Long.class)).isEqualTo(Long.valueOf(Integer.valueOf(1))); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(Integer.MAX_VALUE), Long.class)).isEqualTo(Long.valueOf(Integer.MAX_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(Integer.MAX_VALUE + 1), Long.class)).isEqualTo(Long.valueOf(Integer.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(Integer.MIN_VALUE), Long.class)).isEqualTo(Long.valueOf(Integer.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Integer.valueOf(Integer.MIN_VALUE - 1), Long.class)).isEqualTo(Long.valueOf(Integer.MAX_VALUE)); - - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf((short) -1), Long.class)).isEqualTo(Long.valueOf(Integer.valueOf(-1))); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf((short) 0), Long.class)).isEqualTo(Long.valueOf(Integer.valueOf(0))); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf((short) 1), Long.class)).isEqualTo(Long.valueOf(Integer.valueOf(1))); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf(Short.MAX_VALUE), Long.class)).isEqualTo(Long.valueOf(Short.MAX_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf((short) (Short.MAX_VALUE + 1)), Long.class)).isEqualTo(Long.valueOf(Short.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf(Short.MIN_VALUE), Long.class)).isEqualTo(Long.valueOf(Short.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Short.valueOf((short) (Short.MIN_VALUE - 1)), Long.class)).isEqualTo(Long.valueOf(Short.MAX_VALUE)); - - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) -1), Long.class)).isEqualTo(Long.valueOf(Integer.valueOf(-1))); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) 0), Long.class)).isEqualTo(Long.valueOf(Integer.valueOf(0))); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) 1), Long.class)).isEqualTo(Long.valueOf(Integer.valueOf(1))); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf(Byte.MAX_VALUE), Long.class)).isEqualTo(Long.valueOf(Byte.MAX_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MAX_VALUE + 1)), Long.class)).isEqualTo(Long.valueOf(Byte.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf(Byte.MIN_VALUE), Long.class)).isEqualTo(Long.valueOf(Byte.MIN_VALUE)); - assertThat(NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MIN_VALUE - 1)), Long.class)).isEqualTo(Long.valueOf(Byte.MAX_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass((long) -1, Long.class)).isEqualTo(Long.valueOf(-1)); + assertThat(NumberUtils.convertNumberToTargetClass(0L, Long.class)).isEqualTo(Long.valueOf(0)); + assertThat(NumberUtils.convertNumberToTargetClass(1L, Long.class)).isEqualTo(Long.valueOf(1)); + assertThat(NumberUtils.convertNumberToTargetClass(Long.MAX_VALUE, Long.class)).isEqualTo(Long.valueOf(Long.MAX_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Long.MAX_VALUE + 1, Long.class)).isEqualTo(Long.valueOf(Long.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Long.MIN_VALUE, Long.class)).isEqualTo(Long.valueOf(Long.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Long.MIN_VALUE - 1, Long.class)).isEqualTo(Long.valueOf(Long.MAX_VALUE)); + + assertThat(NumberUtils.convertNumberToTargetClass(-1, Long.class)).isEqualTo(Long.valueOf(-1)); + assertThat(NumberUtils.convertNumberToTargetClass(0, Long.class)).isEqualTo(Long.valueOf(0)); + assertThat(NumberUtils.convertNumberToTargetClass(1, Long.class)).isEqualTo(Long.valueOf(1)); + assertThat(NumberUtils.convertNumberToTargetClass(Integer.MAX_VALUE, Long.class)).isEqualTo(Long.valueOf(Integer.MAX_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Integer.MAX_VALUE + 1, Long.class)).isEqualTo(Long.valueOf(Integer.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Integer.MIN_VALUE, Long.class)).isEqualTo(Long.valueOf(Integer.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Integer.MIN_VALUE - 1, Long.class)).isEqualTo(Long.valueOf(Integer.MAX_VALUE)); + + assertThat(NumberUtils.convertNumberToTargetClass((short) -1, Long.class)).isEqualTo(Long.valueOf(-1)); + assertThat(NumberUtils.convertNumberToTargetClass((short) 0, Long.class)).isEqualTo(Long.valueOf(0)); + assertThat(NumberUtils.convertNumberToTargetClass((short) 1, Long.class)).isEqualTo(Long.valueOf(1)); + assertThat(NumberUtils.convertNumberToTargetClass(Short.MAX_VALUE, Long.class)).isEqualTo(Long.valueOf(Short.MAX_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass((short) (Short.MAX_VALUE + 1), Long.class)).isEqualTo(Long.valueOf(Short.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Short.MIN_VALUE, Long.class)).isEqualTo(Long.valueOf(Short.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass((short) (Short.MIN_VALUE - 1), Long.class)).isEqualTo(Long.valueOf(Short.MAX_VALUE)); + + assertThat(NumberUtils.convertNumberToTargetClass((byte) -1, Long.class)).isEqualTo(Long.valueOf(-1)); + assertThat(NumberUtils.convertNumberToTargetClass((byte) 0, Long.class)).isEqualTo(Long.valueOf(0)); + assertThat(NumberUtils.convertNumberToTargetClass((byte) 1, Long.class)).isEqualTo(Long.valueOf(1)); + assertThat(NumberUtils.convertNumberToTargetClass(Byte.MAX_VALUE, Long.class)).isEqualTo(Long.valueOf(Byte.MAX_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass((byte) (Byte.MAX_VALUE + 1), Long.class)).isEqualTo(Long.valueOf(Byte.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass(Byte.MIN_VALUE, Long.class)).isEqualTo(Long.valueOf(Byte.MIN_VALUE)); + assertThat(NumberUtils.convertNumberToTargetClass((byte) (Byte.MIN_VALUE - 1), Long.class)).isEqualTo(Long.valueOf(Byte.MAX_VALUE)); assertToNumberOverflow(BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE), Long.class); assertToNumberOverflow(BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE), Long.class); diff --git a/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java index 3013e658ce86..84d61ecea362 100644 --- a/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java @@ -151,14 +151,14 @@ void toObjectArray() { void toObjectArrayWithNull() { Object[] objects = ObjectUtils.toObjectArray(null); assertThat(objects).isNotNull(); - assertThat(objects.length).isEqualTo(0); + assertThat(objects).isEmpty(); } @Test void toObjectArrayWithEmptyPrimitiveArray() { Object[] objects = ObjectUtils.toObjectArray(new byte[] {}); assertThat(objects).isNotNull(); - assertThat(objects).hasSize(0); + assertThat(objects).isEmpty(); } @Test @@ -703,7 +703,7 @@ void nullSafeToStringWithPlainOldString() { @Test void nullSafeToStringWithObjectArray() { - Object[] array = {"Han", Long.valueOf(43)}; + Object[] array = {"Han", 43L}; assertThat(ObjectUtils.nullSafeToString(array)).isEqualTo("{Han, 43}"); } diff --git a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java index bbb05b743a86..5d7886745538 100644 --- a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java @@ -305,13 +305,13 @@ void getUniqueDeclaredMethods_withCovariantReturnType() throws Exception { class Parent { @SuppressWarnings("unused") public Number m1() { - return Integer.valueOf(42); + return 42; } } class Leaf extends Parent { @Override public Integer m1() { - return Integer.valueOf(42); + return 42; } } Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(Leaf.class); diff --git a/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java b/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java index dbf110874102..0893412146d2 100644 --- a/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java @@ -98,7 +98,7 @@ void trimAllWhitespace() { } @Test - @Deprecated + @SuppressWarnings("deprecation") void trimLeadingWhitespace() { assertThat(StringUtils.trimLeadingWhitespace(null)).isNull(); assertThat(StringUtils.trimLeadingWhitespace("")).isEqualTo(""); @@ -114,7 +114,7 @@ void trimLeadingWhitespace() { } @Test - @Deprecated + @SuppressWarnings("deprecation") void trimTrailingWhitespace() { assertThat(StringUtils.trimTrailingWhitespace(null)).isNull(); assertThat(StringUtils.trimTrailingWhitespace("")).isEqualTo(""); @@ -441,7 +441,7 @@ void concatenateStringArrays() { String[] input1 = new String[] {"myString2"}; String[] input2 = new String[] {"myString1", "myString2"}; String[] result = StringUtils.concatenateStringArrays(input1, input2); - assertThat(result.length).isEqualTo(3); + assertThat(result).hasSize(3); assertThat(result[0]).isEqualTo("myString2"); assertThat(result[1]).isEqualTo("myString1"); assertThat(result[2]).isEqualTo("myString2"); @@ -490,21 +490,21 @@ void splitArrayElementsIntoPropertiesAndDeletedChars() { @Test void tokenizeToStringArray() { String[] sa = StringUtils.tokenizeToStringArray("a,b , ,c", ","); - assertThat(sa.length).isEqualTo(3); + assertThat(sa).hasSize(3); assertThat(sa[0].equals("a") && sa[1].equals("b") && sa[2].equals("c")).as("components are correct").isTrue(); } @Test void tokenizeToStringArrayWithNotIgnoreEmptyTokens() { String[] sa = StringUtils.tokenizeToStringArray("a,b , ,c", ",", true, false); - assertThat(sa.length).isEqualTo(4); + assertThat(sa).hasSize(4); assertThat(sa[0].equals("a") && sa[1].equals("b") && sa[2].isEmpty() && sa[3].equals("c")).as("components are correct").isTrue(); } @Test void tokenizeToStringArrayWithNotTrimTokens() { String[] sa = StringUtils.tokenizeToStringArray("a,b ,c", ",", false, true); - assertThat(sa.length).isEqualTo(3); + assertThat(sa).hasSize(3); assertThat(sa[0].equals("a") && sa[1].equals("b ") && sa[2].equals("c")).as("components are correct").isTrue(); } @@ -525,7 +525,7 @@ void commaDelimitedListToStringArrayWithEmptyStringProducesEmptyArray() { @Test void delimitedListToStringArrayWithComma() { String[] sa = StringUtils.delimitedListToStringArray("a,b", ","); - assertThat(sa.length).isEqualTo(2); + assertThat(sa).hasSize(2); assertThat(sa[0]).isEqualTo("a"); assertThat(sa[1]).isEqualTo("b"); } @@ -533,7 +533,7 @@ void delimitedListToStringArrayWithComma() { @Test void delimitedListToStringArrayWithSemicolon() { String[] sa = StringUtils.delimitedListToStringArray("a;b", ";"); - assertThat(sa.length).isEqualTo(2); + assertThat(sa).hasSize(2); assertThat(sa[0]).isEqualTo("a"); assertThat(sa[1]).isEqualTo("b"); } @@ -541,7 +541,7 @@ void delimitedListToStringArrayWithSemicolon() { @Test void delimitedListToStringArrayWithEmptyDelimiter() { String[] sa = StringUtils.delimitedListToStringArray("a,b", ""); - assertThat(sa.length).isEqualTo(3); + assertThat(sa).hasSize(3); assertThat(sa[0]).isEqualTo("a"); assertThat(sa[1]).isEqualTo(","); assertThat(sa[2]).isEqualTo("b"); @@ -550,7 +550,7 @@ void delimitedListToStringArrayWithEmptyDelimiter() { @Test void delimitedListToStringArrayWithNullDelimiter() { String[] sa = StringUtils.delimitedListToStringArray("a,b", null); - assertThat(sa.length).isEqualTo(1); + assertThat(sa).hasSize(1); assertThat(sa[0]).isEqualTo("a,b"); } diff --git a/spring-core/src/test/java/org/springframework/util/UnmodifiableMultiValueMapTests.java b/spring-core/src/test/java/org/springframework/util/UnmodifiableMultiValueMapTests.java index a489ccf41106..7573793f9570 100644 --- a/spring-core/src/test/java/org/springframework/util/UnmodifiableMultiValueMapTests.java +++ b/spring-core/src/test/java/org/springframework/util/UnmodifiableMultiValueMapTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -47,7 +47,7 @@ void delegation() { UnmodifiableMultiValueMap map = new UnmodifiableMultiValueMap<>(mock); given(mock.size()).willReturn(1); - assertThat(map.size()).isEqualTo(1); + assertThat(map).hasSize(1); given(mock.isEmpty()).willReturn(false); assertThat(map.isEmpty()).isFalse(); @@ -107,7 +107,7 @@ void entrySetDelegation() { Set>> set = new UnmodifiableMultiValueMap<>(mockMap).entrySet(); given(mockSet.size()).willReturn(1); - assertThat(set.size()).isEqualTo(1); + assertThat(set).hasSize(1); given(mockSet.isEmpty()).willReturn(false); assertThat(set.isEmpty()).isFalse(); @@ -149,7 +149,7 @@ void valuesDelegation() { Collection> values = new UnmodifiableMultiValueMap<>(mockMap).values(); given(mockValues.size()).willReturn(1); - assertThat(values.size()).isEqualTo(1); + assertThat(values).hasSize(1); given(mockValues.isEmpty()).willReturn(false); assertThat(values.isEmpty()).isFalse(); diff --git a/spring-core/src/test/kotlin/org/springframework/aot/hint/KotlinBindingReflectionHintsRegistrarTests.kt b/spring-core/src/test/kotlin/org/springframework/aot/hint/KotlinBindingReflectionHintsRegistrarTests.kt index cc00a89d354c..f4d0ff82561b 100644 --- a/spring-core/src/test/kotlin/org/springframework/aot/hint/KotlinBindingReflectionHintsRegistrarTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/aot/hint/KotlinBindingReflectionHintsRegistrarTests.kt @@ -20,6 +20,7 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.ThrowingConsumer import org.junit.jupiter.api.Test import org.springframework.aot.hint.predicate.RuntimeHintsPredicates +import java.lang.reflect.Method /** * Tests for Kotlin support in [BindingReflectionHintsRegistrar]. @@ -67,6 +68,16 @@ class KotlinBindingReflectionHintsRegistrarTests { assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleDataClass::class.java, "component1")).accepts(hints) assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleDataClass::class.java, "copy")).accepts(hints) assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleDataClass::class.java, "getName")).accepts(hints) + val copyDefault: Method = SampleDataClass::class.java.getMethod("copy\$default", SampleDataClass::class.java, + String::class.java , Int::class.java, Object::class.java) + assertThat(RuntimeHintsPredicates.reflection().onMethod(copyDefault)).accepts(hints) + } + + @Test + fun `Register reflection hints on declared methods for Kotlin class`() { + bindingRegistrar.registerReflectionHints(hints.reflection(), SampleClass::class.java) + assertThat(RuntimeHintsPredicates.reflection().onType(SampleClass::class.java) + .withMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS)).accepts(hints) } } @@ -74,3 +85,5 @@ class KotlinBindingReflectionHintsRegistrarTests { class SampleSerializableClass(val name: String) data class SampleDataClass(val name: String) + +class SampleClass(val name: String) diff --git a/spring-core/src/testFixtures/java/org/springframework/core/testfixture/io/buffer/AbstractLeakCheckingTests.java b/spring-core/src/testFixtures/java/org/springframework/core/testfixture/io/buffer/AbstractLeakCheckingTests.java index 348f5dbbecbe..99525ced55c5 100644 --- a/spring-core/src/testFixtures/java/org/springframework/core/testfixture/io/buffer/AbstractLeakCheckingTests.java +++ b/spring-core/src/testFixtures/java/org/springframework/core/testfixture/io/buffer/AbstractLeakCheckingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-expression/src/main/java/org/springframework/expression/ExpressionException.java b/spring-expression/src/main/java/org/springframework/expression/ExpressionException.java index 1b8d222a84af..4fb1fe8b4225 100644 --- a/spring-expression/src/main/java/org/springframework/expression/ExpressionException.java +++ b/spring-expression/src/main/java/org/springframework/expression/ExpressionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java index c9a68578ae05..8ea9f0c76f6b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java @@ -22,6 +22,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; import org.springframework.asm.MethodVisitor; import org.springframework.core.convert.TypeDescriptor; @@ -46,10 +47,15 @@ * Represents the invocation of a constructor. Either a constructor on a regular type or * construction of an array. When an array is constructed, an initializer can be specified. * - *

Examples:
- * new String('hello world')
- * new int[]{1,2,3,4}
- * new int[3] new int[3]{1,2,3} + *

Examples

+ *
    + *
  • new example.Foo()
  • + *
  • new String('hello world')
  • + *
  • new int[] {1,2,3,4}
  • + *
  • new String[] {'abc','xyz'}
  • + *
  • new int[5]
  • + *
  • new int[3][4]
  • + *
* * @author Andy Clement * @author Juergen Hoeller @@ -68,7 +74,7 @@ public class ConstructorReference extends SpelNodeImpl { private final boolean isArrayConstructor; @Nullable - private SpelNodeImpl[] dimensions; + private final SpelNodeImpl[] dimensions; // TODO is this caching safe - passing the expression around will mean this executor is also being passed around /** The cached executor that may be reused on subsequent evaluations. */ @@ -83,6 +89,7 @@ public class ConstructorReference extends SpelNodeImpl { public ConstructorReference(int startPos, int endPos, SpelNodeImpl... arguments) { super(startPos, endPos, arguments); this.isArrayConstructor = false; + this.dimensions = null; } /** @@ -214,16 +221,33 @@ private ConstructorExecutor findExecutorForConstructor(String typeName, @Override public String toStringAST() { StringBuilder sb = new StringBuilder("new "); - int index = 0; - sb.append(getChild(index++).toStringAST()); - sb.append('('); - for (int i = index; i < getChildCount(); i++) { - if (i > index) { - sb.append(','); + sb.append(getChild(0).toStringAST()); // constructor or array type + + // Arrays + if (this.isArrayConstructor) { + if (hasInitializer()) { + // new int[] {1, 2, 3, 4, 5}, etc. + InlineList initializer = (InlineList) getChild(1); + sb.append("[] ").append(initializer.toStringAST()); + } + else { + // new int[3], new java.lang.String[3][4], etc. + for (SpelNodeImpl dimension : this.dimensions) { + sb.append('[').append(dimension.toStringAST()).append(']'); + } } - sb.append(getChild(i).toStringAST()); } - sb.append(')'); + // Constructors + else { + // new String('hello'), new org.example.Person('Jane', 32), etc. + StringJoiner sj = new StringJoiner(",", "(", ")"); + int count = getChildCount(); + for (int i = 1; i < count; i++) { + sj.add(getChild(i).toStringAST()); + } + sb.append(sj.toString()); + } + return sb.toString(); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java index 818f43fca647..55e2267c4ce2 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -32,6 +32,7 @@ * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class Elvis extends SpelNodeImpl { @@ -64,7 +65,7 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep @Override public String toStringAST() { - return getChild(0).toStringAST() + " ?: " + getChild(1).toStringAST(); + return "(" + getChild(0).toStringAST() + " ?: " + getChild(1).toStringAST() + ")"; } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java index 041773538647..892f8bab860d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -70,14 +70,14 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep if (value == TypedValue.NULL) { throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_NOT_DEFINED, this.name); } - if (!(value.getValue() instanceof Method)) { + if (!(value.getValue() instanceof Method function)) { // Possibly a static Java method registered as a function throw new SpelEvaluationException( SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, this.name, value.getClass()); } try { - return executeFunctionJLRMethod(state, (Method) value.getValue()); + return executeFunctionJLRMethod(state, function); } catch (SpelEvaluationException ex) { ex.setPosition(getStartPosition()); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index 7a9d277ac027..a12c11df85e6 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -165,11 +165,11 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException this.indexedType = IndexedType.ARRAY; return new ArrayIndexingValueRef(state.getTypeConverter(), target, idx, targetDescriptor); } - else if (target instanceof Collection) { + else if (target instanceof Collection collection) { if (target instanceof List) { this.indexedType = IndexedType.LIST; } - return new CollectionIndexingValueRef((Collection) target, idx, targetDescriptor, + return new CollectionIndexingValueRef(collection, idx, targetDescriptor, state.getTypeConverter(), state.getConfiguration().isAutoGrowCollections(), state.getConfiguration().getMaximumAutoGrowSize()); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index 1ecb6187001a..8931591e54f6 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -134,7 +134,7 @@ private TypedValue getValueInternal(EvaluationContext evaluationContext, // either there was no accessor or it no longer existed executorToUse = findAccessorForMethod(argumentTypes, value, evaluationContext); this.cachedExecutor = new CachedMethodExecutor( - executorToUse, (value instanceof Class ? (Class) value : null), targetType, argumentTypes); + executorToUse, (value instanceof Class clazz ? clazz : null), targetType, argumentTypes); try { return executorToUse.execute(evaluationContext, value, arguments); } @@ -216,7 +216,7 @@ private MethodExecutor findAccessorForMethod(List argumentTypes, String method = FormatHelper.formatMethodForMessage(this.name, argumentTypes); String className = FormatHelper.formatClassNameForMessage( - targetObject instanceof Class ? ((Class) targetObject) : targetObject.getClass()); + targetObject instanceof Class clazz ? clazz : targetObject.getClass()); if (accessException != null) { throw new SpelEvaluationException( getStartPosition(), accessException, SpelMessage.PROBLEM_LOCATING_METHOD, method, className); @@ -233,8 +233,8 @@ private MethodExecutor findAccessorForMethod(List argumentTypes, private void throwSimpleExceptionIfPossible(Object value, AccessException ex) { if (ex.getCause() instanceof InvocationTargetException) { Throwable rootCause = ex.getCause().getCause(); - if (rootCause instanceof RuntimeException) { - throw (RuntimeException) rootCause; + if (rootCause instanceof RuntimeException runtimeException) { + throw runtimeException; } throw new ExpressionInvocationTargetException(getStartPosition(), "A problem occurred when trying to execute method '" + this.name + @@ -244,8 +244,8 @@ private void throwSimpleExceptionIfPossible(Object value, AccessException ex) { private void updateExitTypeDescriptor() { CachedMethodExecutor executorToCheck = this.cachedExecutor; - if (executorToCheck != null && executorToCheck.get() instanceof ReflectiveMethodExecutor) { - Method method = ((ReflectiveMethodExecutor) executorToCheck.get()).getMethod(); + if (executorToCheck != null && executorToCheck.get() instanceof ReflectiveMethodExecutor reflectiveMethodExecutor) { + Method method = reflectiveMethodExecutor.getMethod(); String descriptor = CodeFlow.toDescriptor(method.getReturnType()); if (this.nullSafe && CodeFlow.isPrimitive(descriptor)) { this.originalPrimitiveExitTypeDescriptor = descriptor; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java index e5f94adecea5..12733435a90d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorBetween.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -53,12 +53,11 @@ public OperatorBetween(int startPos, int endPos, SpelNodeImpl... operands) { public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { Object left = getLeftOperand().getValueInternal(state).getValue(); Object right = getRightOperand().getValueInternal(state).getValue(); - if (!(right instanceof List) || ((List) right).size() != 2) { + if (!(right instanceof List list) || list.size() != 2) { throw new SpelEvaluationException(getRightOperand().getStartPosition(), SpelMessage.BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST); } - List list = (List) right; Object low = list.get(0); Object high = list.get(1); TypeComparator comp = state.getTypeComparator(); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java index 3cd663d873bc..36dfc4c4366b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -62,12 +62,11 @@ public BooleanTypedValue getValueInternal(ExpressionState state) throws Evaluati Object leftValue = left.getValue(); Object rightValue = right.getValue(); BooleanTypedValue result; - if (!(rightValue instanceof Class)) { + if (!(rightValue instanceof Class rightClass)) { throw new SpelEvaluationException(getRightOperand().getStartPosition(), SpelMessage.INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND, (rightValue == null ? "null" : rightValue.getClass().getName())); } - Class rightClass = (Class) rightValue; if (leftValue == null) { result = BooleanTypedValue.FALSE; // null is not an instanceof anything } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java index 1639a9e4416d..b9a9c2cc0b9d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Projection.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -70,8 +70,7 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException // has two fields 'key' and 'value' that refer to the map entries key // and value, and they can be referenced in the operation // eg. {'a':'y','b':'n'}.![value=='y'?key:null]" == ['a', null] - if (operand instanceof Map) { - Map mapData = (Map) operand; + if (operand instanceof Map mapData) { List result = new ArrayList<>(); for (Map.Entry entry : mapData.entrySet()) { try { @@ -88,8 +87,8 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException } if (operand instanceof Iterable || operandIsArray) { - Iterable data = (operand instanceof Iterable ? - (Iterable) operand : Arrays.asList(ObjectUtils.toObjectArray(operand))); + Iterable data = (operand instanceof Iterable iterable ? + iterable : Arrays.asList(ObjectUtils.toObjectArray(operand))); List result = new ArrayList<>(); Class arrayElementType = null; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java index 9daa3b94aa78..72f9aa6f0e88 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Selection.java @@ -87,8 +87,7 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException Object operand = op.getValue(); SpelNodeImpl selectionCriteria = this.children[0]; - if (operand instanceof Map) { - Map mapdata = (Map) operand; + if (operand instanceof Map mapdata) { // TODO don't lose generic info for the new map Map result = new HashMap<>(); Object lastKey = null; @@ -99,8 +98,8 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException state.pushActiveContextObject(kvPair); state.enterScope(); Object val = selectionCriteria.getValueInternal(state).getValue(); - if (val instanceof Boolean) { - if ((Boolean) val) { + if (val instanceof Boolean b) { + if (b) { if (this.variant == FIRST) { result.put(entry.getKey(), entry.getValue()); return new ValueRef.TypedValueHolderValueRef(new TypedValue(result), this); @@ -135,8 +134,8 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException } if (operand instanceof Iterable || ObjectUtils.isArray(operand)) { - Iterable data = (operand instanceof Iterable ? - (Iterable) operand : Arrays.asList(ObjectUtils.toObjectArray(operand))); + Iterable data = (operand instanceof Iterable iterable ? + iterable : Arrays.asList(ObjectUtils.toObjectArray(operand))); List result = new ArrayList<>(); int index = 0; @@ -145,8 +144,8 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException state.pushActiveContextObject(new TypedValue(element)); state.enterScope("index", index); Object val = selectionCriteria.getValueInternal(state).getValue(); - if (val instanceof Boolean) { - if ((Boolean) val) { + if (val instanceof Boolean b) { + if (b) { if (this.variant == FIRST) { return new ValueRef.TypedValueHolderValueRef(new TypedValue(element), this); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java index 3976fbf3425d..c3514ab1cdd9 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -144,7 +144,7 @@ public Class getObjectClass(@Nullable Object obj) { if (obj == null) { return null; } - return (obj instanceof Class ? ((Class) obj) : obj.getClass()); + return (obj instanceof Class clazz ? clazz : obj.getClass()); } @Override @@ -207,8 +207,7 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) { String[] paramDescriptors = null; boolean isVarargs = false; - if (member instanceof Constructor) { - Constructor ctor = (Constructor) member; + if (member instanceof Constructor ctor) { paramDescriptors = CodeFlow.toDescriptors(ctor.getParameterTypes()); isVarargs = ctor.isVarArgs(); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java index 3fc939c2b5a7..5bc43729ac83 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -26,6 +26,7 @@ * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class StringLiteral extends Literal { @@ -36,9 +37,19 @@ public class StringLiteral extends Literal { public StringLiteral(String payload, int startPos, int endPos, String value) { super(payload, startPos, endPos); + // The original enclosing quote character for the string literal: ' or ". + char quoteCharacter = value.charAt(0); + + // Remove enclosing quotes String valueWithinQuotes = value.substring(1, value.length() - 1); - valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'"); - valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\""); + + // Replace escaped internal quote characters + if (quoteCharacter == '\'') { + valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'"); + } + else { + valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\""); + } this.value = new TypedValue(valueWithinQuotes); this.exitTypeDescriptor = "Ljava/lang/String"; @@ -52,7 +63,9 @@ public TypedValue getLiteralValue() { @Override public String toString() { - return "'" + getLiteralValue().getValue() + "'"; + String ast = String.valueOf(getLiteralValue().getValue()); + ast = StringUtils.replace(ast, "'", "''"); + return "'" + ast + "'"; } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java index 2889f57942a9..f536ec89240b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -32,6 +32,7 @@ * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class Ternary extends SpelNodeImpl { @@ -62,7 +63,7 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep @Override public String toStringAST() { - return getChild(0).toStringAST() + " ? " + getChild(1).toStringAST() + " : " + getChild(2).toStringAST(); + return "(" + getChild(0).toStringAST() + " ? " + getChild(1).toStringAST() + " : " + getChild(2).toStringAST() + ")"; } private void computeExitTypeDescriptor() { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java index a431d3055a55..695d82c1b36f 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java @@ -252,7 +252,7 @@ public static SpelCompiler getCompiler(@Nullable ClassLoader classLoader) { * {@code false} otherwise */ public static boolean compile(Expression expression) { - return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression()); + return (expression instanceof SpelExpression spelExpression && spelExpression.compileExpression()); } /** @@ -261,8 +261,8 @@ public static boolean compile(Expression expression) { * @param expression the expression */ public static void revertToInterpreted(Expression expression) { - if (expression instanceof SpelExpression) { - ((SpelExpression) expression).revertToInterpreted(); + if (expression instanceof SpelExpression spelExpression) { + spelExpression.revertToInterpreted(); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java index 660fb23ddc11..f4ea32211f17 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java @@ -249,7 +249,7 @@ public T getValue(@Nullable Object rootObject, @Nullable Class expectedRe @Override @Nullable public Object getValue(EvaluationContext context) throws EvaluationException { - Assert.notNull(context, "EvaluationContext is required"); + Assert.notNull(context, "EvaluationContext must not be null"); CompiledExpression compiledAst = this.compiledAst; if (compiledAst != null) { @@ -279,7 +279,7 @@ public Object getValue(EvaluationContext context) throws EvaluationException { @Override @Nullable public T getValue(EvaluationContext context, @Nullable Class expectedResultType) throws EvaluationException { - Assert.notNull(context, "EvaluationContext is required"); + Assert.notNull(context, "EvaluationContext must not be null"); CompiledExpression compiledAst = this.compiledAst; if (compiledAst != null) { @@ -314,7 +314,7 @@ public T getValue(EvaluationContext context, @Nullable Class expectedResu @Override @Nullable public Object getValue(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { - Assert.notNull(context, "EvaluationContext is required"); + Assert.notNull(context, "EvaluationContext must not be null"); CompiledExpression compiledAst = this.compiledAst; if (compiledAst != null) { @@ -346,7 +346,7 @@ public Object getValue(EvaluationContext context, @Nullable Object rootObject) t public T getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class expectedResultType) throws EvaluationException { - Assert.notNull(context, "EvaluationContext is required"); + Assert.notNull(context, "EvaluationContext must not be null"); CompiledExpression compiledAst = this.compiledAst; if (compiledAst != null) { @@ -393,7 +393,7 @@ public Class getValueType(@Nullable Object rootObject) throws EvaluationExcep @Override @Nullable public Class getValueType(EvaluationContext context) throws EvaluationException { - Assert.notNull(context, "EvaluationContext is required"); + Assert.notNull(context, "EvaluationContext must not be null"); ExpressionState expressionState = new ExpressionState(context, this.configuration); TypeDescriptor typeDescriptor = this.ast.getValueInternal(expressionState).getTypeDescriptor(); return (typeDescriptor != null ? typeDescriptor.getType() : null); @@ -424,7 +424,7 @@ public TypeDescriptor getValueTypeDescriptor(@Nullable Object rootObject) throws @Override @Nullable public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException { - Assert.notNull(context, "EvaluationContext is required"); + Assert.notNull(context, "EvaluationContext must not be null"); ExpressionState expressionState = new ExpressionState(context, this.configuration); return this.ast.getValueInternal(expressionState).getTypeDescriptor(); } @@ -434,7 +434,7 @@ public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws E public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { - Assert.notNull(context, "EvaluationContext is required"); + Assert.notNull(context, "EvaluationContext must not be null"); ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); return this.ast.getValueInternal(expressionState).getTypeDescriptor(); } @@ -447,13 +447,13 @@ public boolean isWritable(@Nullable Object rootObject) throws EvaluationExceptio @Override public boolean isWritable(EvaluationContext context) throws EvaluationException { - Assert.notNull(context, "EvaluationContext is required"); + Assert.notNull(context, "EvaluationContext must not be null"); return this.ast.isWritable(new ExpressionState(context, this.configuration)); } @Override public boolean isWritable(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException { - Assert.notNull(context, "EvaluationContext is required"); + Assert.notNull(context, "EvaluationContext must not be null"); return this.ast.isWritable(new ExpressionState(context, toTypedValue(rootObject), this.configuration)); } @@ -465,7 +465,7 @@ public void setValue(@Nullable Object rootObject, @Nullable Object value) throws @Override public void setValue(EvaluationContext context, @Nullable Object value) throws EvaluationException { - Assert.notNull(context, "EvaluationContext is required"); + Assert.notNull(context, "EvaluationContext must not be null"); this.ast.setValue(new ExpressionState(context, this.configuration), value); } @@ -473,7 +473,7 @@ public void setValue(EvaluationContext context, @Nullable Object value) throws E public void setValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Object value) throws EvaluationException { - Assert.notNull(context, "EvaluationContext is required"); + Assert.notNull(context, "EvaluationContext must not be null"); this.ast.setValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration), value); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java index 86889dca73e2..b4c634d34812 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java @@ -114,7 +114,7 @@ public MethodExecutor resolve(EvaluationContext context, Object targetObject, St try { TypeConverter typeConverter = context.getTypeConverter(); - Class type = (targetObject instanceof Class ? (Class) targetObject : targetObject.getClass()); + Class type = (targetObject instanceof Class clazz ? clazz : targetObject.getClass()); ArrayList methods = new ArrayList<>(getMethods(type, targetObject)); // If a filter is registered for this type, call it diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java index 0e133522f621..dee0d3259ed1 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -177,7 +177,7 @@ protected void evaluateAndCheckError(String expression, Class expectedReturnT assertThat(ex.getMessageCode()).isEqualTo(expectedMessage); if (!ObjectUtils.isEmpty(otherProperties)) { // first one is expected position of the error within the string - int pos = ((Integer) otherProperties[0]).intValue(); + int pos = (Integer) otherProperties[0]; assertThat(ex.getPosition()).as("position").isEqualTo(pos); if (otherProperties.length > 1) { // Check inserts match @@ -207,7 +207,7 @@ protected void parseAndCheckError(String expression, SpelMessage expectedMessage assertThat(ex.getMessageCode()).isEqualTo(expectedMessage); if (otherProperties != null && otherProperties.length != 0) { // first one is expected position of the error within the string - int pos = ((Integer) otherProperties[0]).intValue(); + int pos = (Integer) otherProperties[0]; assertThat(pos).as("reported position").isEqualTo(pos); if (otherProperties.length > 1) { // Check inserts match diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java index b925832d10e0..e1a84d991f3f 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java @@ -156,19 +156,19 @@ public void testAddingConstructorResolvers() { // reflective constructor accessor is the only one by default List constructorResolvers = ctx.getConstructorResolvers(); - assertThat(constructorResolvers.size()).isEqualTo(1); + assertThat(constructorResolvers).hasSize(1); ConstructorResolver dummy = new DummyConstructorResolver(); ctx.addConstructorResolver(dummy); - assertThat(ctx.getConstructorResolvers().size()).isEqualTo(2); + assertThat(ctx.getConstructorResolvers()).hasSize(2); List copy = new ArrayList<>(ctx.getConstructorResolvers()); assertThat(ctx.removeConstructorResolver(dummy)).isTrue(); assertThat(ctx.removeConstructorResolver(dummy)).isFalse(); - assertThat(ctx.getConstructorResolvers().size()).isEqualTo(1); + assertThat(ctx.getConstructorResolvers()).hasSize(1); ctx.setConstructorResolvers(copy); - assertThat(ctx.getConstructorResolvers().size()).isEqualTo(2); + assertThat(ctx.getConstructorResolvers()).hasSize(2); } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index febb1f4412c2..5e721e396c98 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -70,14 +70,14 @@ void createListsOnAttemptToIndexNull01() throws EvaluationException, ParseExcept assertThat(o).isEqualTo(""); o = parser.parseExpression("list[3]").getValue(new StandardEvaluationContext(testClass)); assertThat(o).isEqualTo(""); - assertThat(testClass.list.size()).isEqualTo(4); + assertThat(testClass.list).hasSize(4); assertThatExceptionOfType(EvaluationException.class).isThrownBy(() -> parser.parseExpression("list2[3]").getValue(new StandardEvaluationContext(testClass))); o = parser.parseExpression("foo[3]").getValue(new StandardEvaluationContext(testClass)); assertThat(o).isEqualTo(""); - assertThat(testClass.getFoo().size()).isEqualTo(4); + assertThat(testClass.getFoo()).hasSize(4); } @Test @@ -116,6 +116,10 @@ void createObjectsOnAttemptToReferenceNull() { void elvisOperator() { evaluate("'Andy'?:'Dave'", "Andy", String.class); evaluate("null?:'Dave'", "Dave", String.class); + evaluate("3?:1", 3, Integer.class); + evaluate("(2*3)?:1*10", 6, Integer.class); + evaluate("null?:2*10", 20, Integer.class); + evaluate("(null?:1)*10", 10, Integer.class); } @Test @@ -315,7 +319,7 @@ void collectionGrowingViaIndexer() { ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); Expression e = parser.parseExpression("listOfStrings[++index3]='def'"); e.getValue(ctx); - assertThat(instance.listOfStrings.size()).isEqualTo(2); + assertThat(instance.listOfStrings).hasSize(2); assertThat(instance.listOfStrings.get(1)).isEqualTo("def"); // Check reference beyond end of collection @@ -347,19 +351,68 @@ void limitCollectionGrowing() { SpelExpressionParser parser = new SpelExpressionParser( new SpelParserConfiguration(true, true, 3)); Expression e = parser.parseExpression("foo[2]"); e.setValue(ctx, "2"); - assertThat(instance.getFoo().size()).isEqualTo(3); + assertThat(instance.getFoo()).hasSize(3); e = parser.parseExpression("foo[3]"); try { e.setValue(ctx, "3"); } catch (SpelEvaluationException see) { assertThat(see.getMessageCode()).isEqualTo(SpelMessage.UNABLE_TO_GROW_COLLECTION); - assertThat(instance.getFoo().size()).isEqualTo(3); + assertThat(instance.getFoo()).hasSize(3); } } } + @Nested + class StringLiterals { + + @Test + void insideSingleQuotes() { + evaluate("'hello'", "hello", String.class); + evaluate("'hello world'", "hello world", String.class); + } + + @Test + void insideDoubleQuotes() { + evaluate("\"hello\"", "hello", String.class); + evaluate("\"hello world\"", "hello world", String.class); + } + + @Test + void singleQuotesInsideSingleQuotes() { + evaluate("'Tony''s Pizza'", "Tony's Pizza", String.class); + evaluate("'big ''''pizza'''' parlor'", "big ''pizza'' parlor", String.class); + } + + @Test + void doubleQuotesInsideDoubleQuotes() { + evaluate("\"big \"\"pizza\"\" parlor\"", "big \"pizza\" parlor", String.class); + evaluate("\"big \"\"\"\"pizza\"\"\"\" parlor\"", "big \"\"pizza\"\" parlor", String.class); + } + + @Test + void singleQuotesInsideDoubleQuotes() { + evaluate("\"Tony's Pizza\"", "Tony's Pizza", String.class); + evaluate("\"big ''pizza'' parlor\"", "big ''pizza'' parlor", String.class); + } + + @Test + void doubleQuotesInsideSingleQuotes() { + evaluate("'big \"pizza\" parlor'", "big \"pizza\" parlor", String.class); + evaluate("'two double \"\" quotes'", "two double \"\" quotes", String.class); + } + + @Test + void inCompoundExpressions() { + evaluate("'123''4' == '123''4'", true, Boolean.class); + evaluate(""" + "123""4" == "123""4"\ + """, true, Boolean.class); + } + + } + @Nested class RelationalOperatorTests { @@ -624,6 +677,24 @@ void ternaryOperator05() { evaluate("2>4?(3>2?true:false):(5<3?true:false)", false, Boolean.class); } + @Test + void ternaryOperator06() { + evaluate("3?:#var=5", 3, Integer.class); + evaluate("null?:#var=5", 5, Integer.class); + evaluate("2>4?(3>2?true:false):(5<3?true:false)", false, Boolean.class); + } + + @Test + void ternaryExpressionWithImplicitGrouping() { + evaluate("4 % 2 == 0 ? 2 : 3 * 10", 2, Integer.class); + evaluate("4 % 2 == 1 ? 2 : 3 * 10", 30, Integer.class); + } + + @Test + void ternaryExpressionWithExplicitGrouping() { + evaluate("((4 % 2 == 0) ? 2 : 1) * 10", 20, Integer.class); + } + @Test void ternaryOperatorWithNullValue() { assertThatExceptionOfType(EvaluationException.class).isThrownBy( diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/LiteralExpressionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/LiteralExpressionTests.java index 4c5990237886..544aca05ffaa 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/LiteralExpressionTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/LiteralExpressionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -29,52 +29,70 @@ /** * @author Andy Clement */ -public class LiteralExpressionTests { +class LiteralExpressionTests { + + private final LiteralExpression lEx = new LiteralExpression("somevalue"); + + + @Test + void getValue() throws Exception { + assertThat(lEx.getValue()).isEqualTo("somevalue"); + assertThat(lEx.getValue(String.class)).isEqualTo("somevalue"); + assertThat(lEx.getValue(new Rooty())).isEqualTo("somevalue"); + assertThat(lEx.getValue(new Rooty(), String.class)).isEqualTo("somevalue"); + } @Test - public void testGetValue() throws Exception { - LiteralExpression lEx = new LiteralExpression("somevalue"); - assertThat(lEx.getValue()).isInstanceOf(String.class).isEqualTo("somevalue"); - assertThat(lEx.getValue(String.class)).isInstanceOf(String.class).isEqualTo("somevalue"); + void getValueWithSuppliedEvaluationContext() throws Exception { EvaluationContext ctx = new StandardEvaluationContext(); - assertThat(lEx.getValue(ctx)).isInstanceOf(String.class).isEqualTo("somevalue"); - assertThat(lEx.getValue(ctx, String.class)).isInstanceOf(String.class).isEqualTo("somevalue"); - assertThat(lEx.getValue(new Rooty())).isInstanceOf(String.class).isEqualTo("somevalue"); - assertThat(lEx.getValue(new Rooty(), String.class)).isInstanceOf(String.class).isEqualTo("somevalue"); - assertThat(lEx.getValue(ctx, new Rooty())).isInstanceOf(String.class).isEqualTo("somevalue"); - assertThat(lEx.getValue(ctx, new Rooty(),String.class)).isInstanceOf(String.class).isEqualTo("somevalue"); + assertThat(lEx.getValue(ctx)).isEqualTo("somevalue"); + assertThat(lEx.getValue(ctx, String.class)).isEqualTo("somevalue"); + assertThat(lEx.getValue(ctx, new Rooty())).isEqualTo("somevalue"); + assertThat(lEx.getValue(ctx, new Rooty(), String.class)).isEqualTo("somevalue"); + } + + @Test + void getExpressionString() { assertThat(lEx.getExpressionString()).isEqualTo("somevalue"); + } + + @Test + void isWritable() throws Exception { assertThat(lEx.isWritable(new StandardEvaluationContext())).isFalse(); assertThat(lEx.isWritable(new Rooty())).isFalse(); assertThat(lEx.isWritable(new StandardEvaluationContext(), new Rooty())).isFalse(); } - static class Rooty {} - @Test - public void testSetValue() { - assertThatExceptionOfType(EvaluationException.class).isThrownBy(() -> - new LiteralExpression("somevalue").setValue(new StandardEvaluationContext(), "flibble")) + void setValue() { + assertThatExceptionOfType(EvaluationException.class) + .isThrownBy(() -> lEx.setValue(new StandardEvaluationContext(), "flibble")) .satisfies(ex -> assertThat(ex.getExpressionString()).isEqualTo("somevalue")); - assertThatExceptionOfType(EvaluationException.class).isThrownBy(() -> - new LiteralExpression("somevalue").setValue(new Rooty(), "flibble")) + assertThatExceptionOfType(EvaluationException.class) + .isThrownBy(() -> lEx.setValue(new Rooty(), "flibble")) .satisfies(ex -> assertThat(ex.getExpressionString()).isEqualTo("somevalue")); - assertThatExceptionOfType(EvaluationException.class).isThrownBy(() -> - new LiteralExpression("somevalue").setValue(new StandardEvaluationContext(), new Rooty(), "flibble")) + assertThatExceptionOfType(EvaluationException.class) + .isThrownBy(() -> lEx.setValue(new StandardEvaluationContext(), new Rooty(), "flibble")) .satisfies(ex -> assertThat(ex.getExpressionString()).isEqualTo("somevalue")); } @Test - public void testGetValueType() throws Exception { - LiteralExpression lEx = new LiteralExpression("somevalue"); + void getValueType() throws Exception { assertThat(lEx.getValueType()).isEqualTo(String.class); assertThat(lEx.getValueType(new StandardEvaluationContext())).isEqualTo(String.class); assertThat(lEx.getValueType(new Rooty())).isEqualTo(String.class); assertThat(lEx.getValueType(new StandardEvaluationContext(), new Rooty())).isEqualTo(String.class); + } + + @Test + void getValueTypeDescriptor() throws Exception { assertThat(lEx.getValueTypeDescriptor().getType()).isEqualTo(String.class); assertThat(lEx.getValueTypeDescriptor(new StandardEvaluationContext()).getType()).isEqualTo(String.class); assertThat(lEx.getValueTypeDescriptor(new Rooty()).getType()).isEqualTo(String.class); assertThat(lEx.getValueTypeDescriptor(new StandardEvaluationContext(), new Rooty()).getType()).isEqualTo(String.class); } + + static class Rooty {} + } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java index 722b385f21b1..7b59007195e2 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java @@ -220,19 +220,19 @@ public void testAddingMethodResolvers() { // reflective method accessor is the only one by default List methodResolvers = ctx.getMethodResolvers(); - assertThat(methodResolvers.size()).isEqualTo(1); + assertThat(methodResolvers).hasSize(1); MethodResolver dummy = new DummyMethodResolver(); ctx.addMethodResolver(dummy); - assertThat(ctx.getMethodResolvers().size()).isEqualTo(2); + assertThat(ctx.getMethodResolvers()).hasSize(2); List copy = new ArrayList<>(ctx.getMethodResolvers()); assertThat(ctx.removeMethodResolver(dummy)).isTrue(); assertThat(ctx.removeMethodResolver(dummy)).isFalse(); - assertThat(ctx.getMethodResolvers().size()).isEqualTo(1); + assertThat(ctx.getMethodResolvers()).hasSize(1); ctx.setMethodResolvers(copy); - assertThat(ctx.getMethodResolvers().size()).isEqualTo(2); + assertThat(ctx.getMethodResolvers()).hasSize(2); } @Test diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java index f065fd8cff60..652dcdc00222 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,8 @@ package org.springframework.expression.spel; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.expression.spel.standard.SpelExpression; @@ -24,415 +26,536 @@ import static org.assertj.core.api.Assertions.assertThat; /** - * Parse some expressions and check we get the AST we expect. Rather than inspecting each node in the AST, we ask it to - * write itself to a string form and check that is as expected. + * Parse some expressions and check we get the AST we expect. + * + *

Rather than inspecting each node in the AST, we ask it to write itself to + * a string form and check that is as expected. * * @author Andy Clement + * @author Sam Brannen */ -public class ParsingTests { - - private SpelExpressionParser parser = new SpelExpressionParser(); - - // literals - @Test - public void testLiteralBoolean01() { - parseCheck("false"); - } - - @Test - public void testLiteralLong01() { - parseCheck("37L", "37"); - } - - @Test - public void testLiteralBoolean02() { - parseCheck("true"); - } - - @Test - public void testLiteralBoolean03() { - parseCheck("!true"); - } - - @Test - public void testLiteralInteger01() { - parseCheck("1"); - } - - @Test - public void testLiteralInteger02() { - parseCheck("1415"); - } - - @Test - public void testLiteralString01() { - parseCheck("'hello'"); - } - - @Test - public void testLiteralString02() { - parseCheck("'joe bloggs'"); - } - - @Test - public void testLiteralString03() { - parseCheck("'Tony''s Pizza'", "'Tony's Pizza'"); - } - - @Test - public void testLiteralReal01() { - parseCheck("6.0221415E+23", "6.0221415E23"); - } - - @Test - public void testLiteralHex01() { - parseCheck("0x7FFFFFFF", "2147483647"); - } - - @Test - public void testLiteralDate01() { - parseCheck("date('1974/08/24')"); - } - - @Test - public void testLiteralDate02() { - parseCheck("date('19740824T131030','yyyyMMddTHHmmss')"); - } - - @Test - public void testLiteralNull01() { - parseCheck("null"); - } - - // boolean operators - @Test - public void testBooleanOperatorsOr01() { - parseCheck("false or false", "(false or false)"); - } - - @Test - public void testBooleanOperatorsOr02() { - parseCheck("false or true", "(false or true)"); - } - - @Test - public void testBooleanOperatorsOr03() { - parseCheck("true or false", "(true or false)"); - } - - @Test - public void testBooleanOperatorsOr04() { - parseCheck("true or false", "(true or false)"); - } - - @Test - public void testBooleanOperatorsMix01() { - parseCheck("false or true and false", "(false or (true and false))"); - } - - // relational operators - @Test - public void testRelOperatorsGT01() { - parseCheck("3>6", "(3 > 6)"); - } - - @Test - public void testRelOperatorsLT01() { - parseCheck("3<6", "(3 < 6)"); - } - - @Test - public void testRelOperatorsLE01() { - parseCheck("3<=6", "(3 <= 6)"); - } - - @Test - public void testRelOperatorsGE01() { - parseCheck("3>=6", "(3 >= 6)"); - } - - @Test - public void testRelOperatorsGE02() { - parseCheck("3>=3", "(3 >= 3)"); - } - - @Test - public void testElvis() { - parseCheck("3?:1", "3 ?: 1"); - } - - // public void testRelOperatorsIn01() { - // parseCheck("3 in {1,2,3,4,5}", "(3 in {1,2,3,4,5})"); - // } - // - // public void testRelOperatorsBetween01() { - // parseCheck("1 between {1, 5}", "(1 between {1,5})"); - // } - - // public void testRelOperatorsBetween02() { - // parseCheck("'efg' between {'abc', 'xyz'}", "('efg' between {'abc','xyz'})"); - // }// true - - @Test - public void testRelOperatorsIs01() { - parseCheck("'xyz' instanceof int", "('xyz' instanceof int)"); - }// false - - // public void testRelOperatorsIs02() { - // parseCheck("{1, 2, 3, 4, 5} instanceof List", "({1,2,3,4,5} instanceof List)"); - // }// true - - @Test - public void testRelOperatorsMatches01() { - parseCheck("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "('5.0067' matches '^-?\\d+(\\.\\d{2})?$')"); - }// false - - @Test - public void testRelOperatorsMatches02() { - parseCheck("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "('5.00' matches '^-?\\d+(\\.\\d{2})?$')"); - }// true - - // mathematical operators - @Test - public void testMathOperatorsAdd01() { - parseCheck("2+4", "(2 + 4)"); - } - - @Test - public void testMathOperatorsAdd02() { - parseCheck("'a'+'b'", "('a' + 'b')"); +class ParsingTests { + + private final SpelExpressionParser parser = new SpelExpressionParser(); + + + @Nested + class Miscellaneous { + + @Test + void literalNull() { + parseCheck("null"); + } + + @Test + void literalDate01() { + parseCheck("date('1974/08/24')"); + } + + @Test + void literalDate02() { + parseCheck("date('19740824T131030','yyyyMMddTHHmmss')"); + } + + @Test + void mixedOperators() { + parseCheck("true and 5>3", "(true and (5 > 3))"); + } + + @Test + void assignmentToVariables() { + parseCheck("#var1='value1'"); + } + + @Test + void collectionProcessorsCountStringArray() { + parseCheck("new String[] {'abc','def','xyz'}.count()"); + } + + @Test + void collectionProcessorsCountIntArray() { + parseCheck("new int[] {1,2,3}.count()"); + } + + @Test + void collectionProcessorsMax() { + parseCheck("new int[] {1,2,3}.max()"); + } + + @Test + void collectionProcessorsMin() { + parseCheck("new int[] {1,2,3}.min()"); + } + + @Test + void collectionProcessorsAverage() { + parseCheck("new int[] {1,2,3}.average()"); + } + + @Test + void collectionProcessorsSort() { + parseCheck("new int[] {3,2,1}.sort()"); + } + + @Test + void collectionProcessorsNonNull() { + parseCheck("{'a','b',null,'d',null}.nonNull()"); + } + + @Test + void collectionProcessorsDistinct() { + parseCheck("{'a','b','a','d','e'}.distinct()"); + } + + @Disabled("Unsupported syntax/feature") + @Test + void lambdaMax() { + parseCheck("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))", + "(#max={|x,y| ($x > $y) ? $x : $y };#max(5,25))"); + } + + @Disabled("Unsupported syntax/feature") + @Test + void lambdaFactorial() { + parseCheck("(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))", + "(#fact={|n| ($n <= 1) ? 1 : ($n * #fact(($n - 1))) };#fact(5))"); + } + + @Disabled("Unsupported syntax/feature") + @Test + void projection() { + parseCheck("{1,2,3,4,5,6,7,8,9,10}.!{#isEven()}"); + } + + @Disabled("Unsupported syntax/feature") + @Test + void selection() { + parseCheck("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}", + "{1,2,3,4,5,6,7,8,9,10}.?{(#isEven(#this) == 'y')}"); + } + + @Disabled("Unsupported syntax/feature") + @Test + void selectionFirst() { + parseCheck("{1,2,3,4,5,6,7,8,9,10}.^{#isEven(#this) == 'y'}", + "{1,2,3,4,5,6,7,8,9,10}.^{(#isEven(#this) == 'y')}"); + } + + @Disabled("Unsupported syntax/feature") + @Test + void selectionLast() { + parseCheck("{1,2,3,4,5,6,7,8,9,10}.${#isEven(#this) == 'y'}", + "{1,2,3,4,5,6,7,8,9,10}.${(#isEven(#this) == 'y')}"); + } + } + + @Nested + class LiteralBooleans { + + @Test + void literalBooleanFalse() { + parseCheck("false"); + } + + @Test + void literalBooleanTrue() { + parseCheck("true"); + } + + @Test + void literalBooleanNotTrue() { + parseCheck("!true"); + parseCheck("not true", "!true"); + } + } + + @Nested + class LiteralNumbers { + + @Test + void literalLong() { + parseCheck("37L", "37"); + } + + @Test + void literalIntegers() { + parseCheck("1"); + parseCheck("1415"); + } + + @Test + void literalReal() { + parseCheck("6.0221415E+23", "6.0221415E23"); + } + + @Test + void literalHex() { + parseCheck("0x7FFFFFFF", "2147483647"); + } + } + + @Nested + class LiteralStrings { + + @Test + void insideSingleQuotes() { + parseCheck("'hello'"); + parseCheck("'hello world'"); + } + + @Test + void insideDoubleQuotes() { + parseCheck("\"hello\"", "'hello'"); + parseCheck("\"hello world\"", "'hello world'"); + } + + @Test + void singleQuotesInsideSingleQuotes() { + parseCheck("'Tony''s Pizza'"); + parseCheck("'big ''''pizza'''' parlor'"); + } + + @Test + void doubleQuotesInsideDoubleQuotes() { + parseCheck("\"big \"\"pizza\"\" parlor\"", "'big \"pizza\" parlor'"); + parseCheck("\"big \"\"\"\"pizza\"\"\"\" parlor\"", "'big \"\"pizza\"\" parlor'"); + } + + @Test + void singleQuotesInsideDoubleQuotes() { + parseCheck("\"Tony's Pizza\"", "'Tony''s Pizza'"); + parseCheck("\"big ''pizza'' parlor\"", "'big ''''pizza'''' parlor'"); + } + + @Test + void doubleQuotesInsideSingleQuotes() { + parseCheck("'big \"pizza\" parlor'"); + parseCheck("'two double \"\" quotes'"); + } + + @Test + void inCompoundExpressions() { + parseCheck("'123''4' == '123''4'", "('123''4' == '123''4')"); + parseCheck("('123''4'=='123''4')", "('123''4' == '123''4')"); + parseCheck( + """ + "123""4" == "123""4"\ + """, + """ + ('123"4' == '123"4')\ + """); + } + } + + @Nested + class BooleanOperators { + + @Test + void booleanOperatorsOr01() { + parseCheck("false or false", "(false or false)"); + } + + @Test + void booleanOperatorsOr02() { + parseCheck("false or true", "(false or true)"); + } + + @Test + void booleanOperatorsOr03() { + parseCheck("true or false", "(true or false)"); + } + + @Test + void booleanOperatorsOr04() { + parseCheck("true or false", "(true or false)"); + } + + @Test + void booleanOperatorsMix() { + parseCheck("false or true and false", "(false or (true and false))"); + } + } + + @Nested + class RelationalOperators { + + @Test + void relOperatorsGT() { + parseCheck("3>6", "(3 > 6)"); + } + + @Test + void relOperatorsLT() { + parseCheck("3<6", "(3 < 6)"); + } + + @Test + void relOperatorsLE() { + parseCheck("3<=6", "(3 <= 6)"); + } + + @Test + void relOperatorsGE01() { + parseCheck("3>=6", "(3 >= 6)"); + } + + @Test + void relOperatorsGE02() { + parseCheck("3>=3", "(3 >= 3)"); + } + + @Disabled("Unsupported syntax/feature") + @Test + void relOperatorsIn() { + parseCheck("3 in {1,2,3,4,5}", "(3 in {1,2,3,4,5})"); + } + + @Test + void relOperatorsBetweenNumbers() { + parseCheck("1 between {1, 5}", "(1 between {1,5})"); + } + + @Test + void relOperatorsBetweenStrings() { + parseCheck("'efg' between {'abc', 'xyz'}", "('efg' between {'abc','xyz'})"); + } + + @Test + void relOperatorsInstanceOfInt() { + parseCheck("'xyz' instanceof int", "('xyz' instanceof int)"); + } + + @Test + void relOperatorsInstanceOfList() { + parseCheck("{1, 2, 3, 4, 5} instanceof List", "({1,2,3,4,5} instanceof List)"); + } + + @Test + void relOperatorsMatches() { + parseCheck("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "('5.0067' matches '^-?\\d+(\\.\\d{2})?$')"); + parseCheck("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "('5.00' matches '^-?\\d+(\\.\\d{2})?$')"); + } + } + + @Nested + class MathematicalOperators { + + @Test + void mathOperatorsAddIntegers() { + parseCheck("2+4", "(2 + 4)"); + } + + @Test + void mathOperatorsAddStrings() { + parseCheck("'a'+'b'", "('a' + 'b')"); + } + + @Test + void mathOperatorsAddMultipleStrings() { + parseCheck("'hello'+' '+'world'", "(('hello' + ' ') + 'world')"); + } + + @Test + void mathOperatorsSubtract() { + parseCheck("5-4", "(5 - 4)"); + } + + @Test + void mathOperatorsMultiply() { + parseCheck("7*4", "(7 * 4)"); + } + + @Test + void mathOperatorsDivide() { + parseCheck("8/4", "(8 / 4)"); + } + + @Test + void mathOperatorModulus() { + parseCheck("7 % 4", "(7 % 4)"); + } + } + + @Nested + class References { + + @Test + void references() { + parseCheck("@foo"); + parseCheck("@'foo.bar'"); + parseCheck("@\"foo.bar.goo\"" , "@'foo.bar.goo'"); + parseCheck("@$$foo"); + } + } + + @Nested + class Properties { + + @Test + void propertiesSingle() { + parseCheck("name"); + } + + @Test + void propertiesDouble() { + parseCheck("placeofbirth.CitY"); + } + + @Test + void propertiesMultiple() { + parseCheck("a.b.c.d.e"); + } + } + + @Nested + class InlineCollections { + + @Test + void inlineListOfIntegers() { + parseCheck("{1,2,3,4}"); + parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}"); + } + + @Test + void inlineListOfStrings() { + parseCheck("{'abc','xyz'}", "{'abc','xyz'}"); + parseCheck("{\"abc\", 'xyz'}", "{'abc','xyz'}"); + } + + @Test + void inlineMapStringToObject() { + parseCheck("{'key1':'Value 1','today':DateTime.Today}"); + } + + @Test + void inlineMapIntegerToString() { + parseCheck("{1:'January',2:'February',3:'March'}"); + } + } + + @Nested + class MethodsConstructorsAndArrays { + + @Test + void methods() { + parseCheck("echo()"); + parseCheck("echo(12)"); + parseCheck("echo(name)"); + parseCheck("echo('Jane')"); + parseCheck("echo('Jane',32)"); + parseCheck("echo('Jane', 32)", "echo('Jane',32)"); + parseCheck("age.doubleItAndAdd(12)"); + } + + @Test + void constructorWithNoArguments() { + parseCheck("new Foo()"); + parseCheck("new example.Foo()"); + } + + @Test + void constructorWithOneArgument() { + parseCheck("new String('hello')"); + parseCheck("new String( 'hello' )", "new String('hello')"); + parseCheck("new String(\"hello\" )", "new String('hello')"); + } + + @Test + void constructorWithMultipleArguments() { + parseCheck("new example.Person('Jane',32,true)"); + parseCheck("new example.Person('Jane', 32, true)", "new example.Person('Jane',32,true)"); + parseCheck("new example.Person('Jane', 2 * 16, true)", "new example.Person('Jane',(2 * 16),true)"); + } + + @Test + void arrayConstructionWithOneDimensionalReferenceType() { + parseCheck("new String[3]"); + } + + @Test + void arrayConstructionWithOneDimensionalFullyQualifiedReferenceType() { + parseCheck("new java.lang.String[3]"); + } + + @Test + void arrayConstructionWithOneDimensionalPrimitiveType() { + parseCheck("new int[3]"); + } + + @Test + void arrayConstructionWithMultiDimensionalReferenceType() { + parseCheck("new Float[3][4]"); + } + + @Test + void arrayConstructionWithMultiDimensionalPrimitiveType() { + parseCheck("new int[3][4]"); + } + + @Test + void arrayConstructionWithOneDimensionalReferenceTypeWithInitializer() { + parseCheck("new String[] {'abc','xyz'}"); + parseCheck("new String[] {'abc', 'xyz'}", "new String[] {'abc','xyz'}"); + } + + @Test + void arrayConstructionWithOneDimensionalPrimitiveTypeWithInitializer() { + parseCheck("new int[] {1,2,3,4,5}"); + parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}"); + } + } + + @Nested + class VariablesAndFunctions { + + @Test + void variables() { + parseCheck("#foo"); + } + + @Test + void functions() { + parseCheck("#fn(1,2,3)"); + parseCheck("#fn('hello')"); + } + } + + @Nested + class ElvisAndTernaryOperators { + + @Test + void elvis() { + parseCheck("3?:1", "(3 ?: 1)"); + parseCheck("(2*3)?:1*10", "((2 * 3) ?: (1 * 10))"); + parseCheck("((2*3)?:1)*10", "(((2 * 3) ?: 1) * 10)"); + } + + @Test + void ternary() { + parseCheck("1>2?3:4", "((1 > 2) ? 3 : 4)"); + parseCheck("(a ? 1 : 0) * 10", "((a ? 1 : 0) * 10)"); + parseCheck("(a?1:0)*10", "((a ? 1 : 0) * 10)"); + parseCheck("(4 % 2 == 0 ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)"); + parseCheck("((4 % 2 == 0) ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)"); + parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'", + "(({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd')"); + } + } + + @Nested + class TypeReferences { + + @Test + void typeReferences01() { + parseCheck("T(java.lang.String)"); + } + + @Test + void typeReferences02() { + parseCheck("T(String)"); + } } - @Test - public void testMathOperatorsAdd03() { - parseCheck("'hello'+' '+'world'", "(('hello' + ' ') + 'world')"); - } - - @Test - public void testMathOperatorsSubtract01() { - parseCheck("5-4", "(5 - 4)"); - } - - @Test - public void testMathOperatorsMultiply01() { - parseCheck("7*4", "(7 * 4)"); - } - - @Test - public void testMathOperatorsDivide01() { - parseCheck("8/4", "(8 / 4)"); - } - - @Test - public void testMathOperatorModulus01() { - parseCheck("7 % 4", "(7 % 4)"); - } - - // mixed operators - @Test - public void testMixedOperators01() { - parseCheck("true and 5>3", "(true and (5 > 3))"); - } - - // collection processors - // public void testCollectionProcessorsCount01() { - // parseCheck("new String[] {'abc','def','xyz'}.count()"); - // } - - // public void testCollectionProcessorsCount02() { - // parseCheck("new int[] {1,2,3}.count()"); - // } - // - // public void testCollectionProcessorsMax01() { - // parseCheck("new int[] {1,2,3}.max()"); - // } - // - // public void testCollectionProcessorsMin01() { - // parseCheck("new int[] {1,2,3}.min()"); - // } - // - // public void testCollectionProcessorsAverage01() { - // parseCheck("new int[] {1,2,3}.average()"); - // } - // - // public void testCollectionProcessorsSort01() { - // parseCheck("new int[] {3,2,1}.sort()"); - // } - // - // public void testCollectionProcessorsNonNull01() { - // parseCheck("{'a','b',null,'d',null}.nonNull()"); - // } - // - // public void testCollectionProcessorsDistinct01() { - // parseCheck("{'a','b','a','d','e'}.distinct()"); - // } - - // references - @Test - public void testReferences01() { - parseCheck("@foo"); - parseCheck("@'foo.bar'"); - parseCheck("@\"foo.bar.goo\"","@'foo.bar.goo'"); - } - - @Test - public void testReferences03() { - parseCheck("@$$foo"); - } - - // properties - @Test - public void testProperties01() { - parseCheck("name"); - } - - @Test - public void testProperties02() { - parseCheck("placeofbirth.CitY"); - } - - @Test - public void testProperties03() { - parseCheck("a.b.c.d.e"); - } - - // inline list creation - @Test - public void testInlineListCreation01() { - parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}"); - } - - @Test - public void testInlineListCreation02() { - parseCheck("{'abc','xyz'}", "{'abc','xyz'}"); - } - - // inline map creation - @Test - public void testInlineMapCreation01() { - parseCheck("{'key1':'Value 1','today':DateTime.Today}"); - } - - @Test - public void testInlineMapCreation02() { - parseCheck("{1:'January',2:'February',3:'March'}"); - } - - // methods - @Test - public void testMethods01() { - parseCheck("echo(12)"); - } - - @Test - public void testMethods02() { - parseCheck("echo(name)"); - } - - @Test - public void testMethods03() { - parseCheck("age.doubleItAndAdd(12)"); - } - - // constructors - @Test - public void testConstructors01() { - parseCheck("new String('hello')"); - } - - // public void testConstructors02() { - // parseCheck("new String[3]"); - // } - - // array construction - // public void testArrayConstruction01() { - // parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}"); - // } - // - // public void testArrayConstruction02() { - // parseCheck("new String[] {'abc','xyz'}", "new String[] {'abc','xyz'}"); - // } - - // variables and functions - @Test - public void testVariables01() { - parseCheck("#foo"); - } - - @Test - public void testFunctions01() { - parseCheck("#fn(1,2,3)"); - } - - @Test - public void testFunctions02() { - parseCheck("#fn('hello')"); - } - - // projections and selections - // public void testProjections01() { - // parseCheck("{1,2,3,4,5,6,7,8,9,10}.!{#isEven()}"); - // } - - // public void testSelections01() { - // parseCheck("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}", - // "{1,2,3,4,5,6,7,8,9,10}.?{(#isEven(#this) == 'y')}"); - // } - - // public void testSelectionsFirst01() { - // parseCheck("{1,2,3,4,5,6,7,8,9,10}.^{#isEven(#this) == 'y'}", - // "{1,2,3,4,5,6,7,8,9,10}.^{(#isEven(#this) == 'y')}"); - // } - - // public void testSelectionsLast01() { - // parseCheck("{1,2,3,4,5,6,7,8,9,10}.${#isEven(#this) == 'y'}", - // "{1,2,3,4,5,6,7,8,9,10}.${(#isEven(#this) == 'y')}"); - // } - - // assignment - @Test - public void testAssignmentToVariables01() { - parseCheck("#var1='value1'"); - } - - - // ternary operator - - @Test - public void testTernaryOperator01() { - parseCheck("1>2?3:4","(1 > 2) ? 3 : 4"); - } - - // public void testTernaryOperator01() { - // parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'", - // "({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd'"); - // } - - // - // public void testLambdaMax() { - // parseCheck("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))", "(#max={|x,y| ($x > $y) ? $x : $y };#max(5,25))"); - // } - // - // public void testLambdaFactorial() { - // parseCheck("(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))", - // "(#fact={|n| ($n <= 1) ? 1 : ($n * #fact(($n - 1))) };#fact(5))"); - // } // 120 - - // Type references - @Test - public void testTypeReferences01() { - parseCheck("T(java.lang.String)"); - } - - @Test - public void testTypeReferences02() { - parseCheck("T(String)"); - } - - @Test - public void testInlineList1() { - parseCheck("{1,2,3,4}"); - } /** * Parse the supplied expression and then create a string representation of the resultant AST, it should be the same @@ -440,7 +563,7 @@ public void testInlineList1() { * * @param expression the expression to parse *and* the expected value of the string form of the resultant AST */ - public void parseCheck(String expression) { + private void parseCheck(String expression) { parseCheck(expression, expression); } @@ -451,7 +574,7 @@ public void parseCheck(String expression) { * @param expression the expression to parse * @param expectedStringFormOfAST the expected string form of the AST */ - public void parseCheck(String expression, String expectedStringFormOfAST) { + private void parseCheck(String expression, String expectedStringFormOfAST) { SpelExpression e = parser.parseRaw(expression); assertThat(e).isNotNull(); assertThat(e.toStringAST()).isEqualTo(expectedStringFormOfAST); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java index c7ce3cad9f55..bfd2b1d2f3f6 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SelectionAndProjectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -189,7 +189,7 @@ void selectFirstItemInMap() { Expression exp = parser.parseExpression("colors.^[key.startsWith('b')]"); Map colorsMap = (Map) exp.getValue(context); - assertThat(colorsMap.size()).isEqualTo(1); + assertThat(colorsMap).hasSize(1); assertThat(colorsMap.keySet().iterator().next()).isEqualTo("beige"); } @@ -201,7 +201,7 @@ void selectLastItemInMap() { Expression exp = parser.parseExpression("colors.$[key.startsWith('b')]"); Map colorsMap = (Map) exp.getValue(context); - assertThat(colorsMap.size()).isEqualTo(1); + assertThat(colorsMap).hasSize(1); assertThat(colorsMap.keySet().iterator().next()).isEqualTo("brown"); } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java index da9458521a4f..137c5d447157 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java @@ -6203,7 +6203,7 @@ public class Reg { public Reg(int v) { this._value = v; - this._valueL = Long.valueOf(v); + this._valueL = (long) v; this._valueD = (double) v; this._valueF = (float) v; } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java index 280b452007b0..a571c50e4590 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -445,7 +445,7 @@ void selection() throws Exception { StandardEvaluationContext societyContext = new StandardEvaluationContext(); societyContext.setRootObject(new IEEE()); List list = (List) parser.parseExpression("Members2.?[nationality == 'Serbian']").getValue(societyContext); - assertThat(list.size()).isEqualTo(1); + assertThat(list).hasSize(1); assertThat(list.get(0).getName()).isEqualTo("Nikola Tesla"); } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java index b6b48c9d7a2c..d7ba60c704bb 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java @@ -209,7 +209,7 @@ void NPE_SPR5673() { checkTemplateParsingError("abc${ } }", "No expression defined within delimiter '${}' at character 3"); checkTemplateParsingError("abc$[ } ]", DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT, "Found closing '}' at position 6 without an opening '{'"); - checkTemplateParsing("abc ${\"def''g}hi\"} jkl", "abc def'g}hi jkl"); + checkTemplateParsing("abc ${\"def''g}hi\"} jkl", "abc def''g}hi jkl"); checkTemplateParsing("abc ${'def''g}hi'} jkl", "abc def'g}hi jkl"); checkTemplateParsing("}", "}"); checkTemplateParsing("${'hello'} world", "hello world"); @@ -637,7 +637,7 @@ public int getX(Number i) { ConversionPriority1 target = new ConversionPriority1(); MethodExecutor me = new ReflectiveMethodResolver(true).resolve(emptyEvalContext, target, "getX", args); // MethodInvoker chooses getX(int i) when passing Integer - final int actual = (Integer) me.execute(emptyEvalContext, target, Integer.valueOf(42)).getValue(); + final int actual = (Integer) me.execute(emptyEvalContext, target, 42).getValue(); // Compiler chooses getX(Number i) when passing Integer final int compiler = target.getX(INTEGER); // Fails! @@ -646,7 +646,7 @@ public int getX(Number i) { ConversionPriority2 target2 = new ConversionPriority2(); MethodExecutor me2 = new ReflectiveMethodResolver(true).resolve(emptyEvalContext, target2, "getX", args); // MethodInvoker chooses getX(int i) when passing Integer - int actual2 = (Integer) me2.execute(emptyEvalContext, target2, Integer.valueOf(42)).getValue(); + int actual2 = (Integer) me2.execute(emptyEvalContext, target2, 42).getValue(); // Compiler chooses getX(Number i) when passing Integer int compiler2 = target2.getX(INTEGER); // Fails! diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpringExpressionTestSuite.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpringExpressionTestSuite.java new file mode 100644 index 000000000000..eca319fecbac --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpringExpressionTestSuite.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2022 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.expression.spel; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +/** + * JUnit Platform based test suite for tests in the spring-expression module. + * + *

This suite is only intended to be used manually within an IDE. + * + * @author Sam Brannen + */ +@Suite +@SelectPackages("org.springframework.expression.spel") +@IncludeClassNamePatterns(".*Tests?$") +class SpringExpressionTestSuite { +} diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/StandardTypeLocatorTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/StandardTypeLocatorTests.java index bdd022d80c77..e4ad837f6107 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/StandardTypeLocatorTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/StandardTypeLocatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -40,7 +40,7 @@ void testImports() throws EvaluationException { assertThat(locator.findType("java.lang.String")).isEqualTo(String.class); List prefixes = locator.getImportPrefixes(); - assertThat(prefixes.size()).isEqualTo(1); + assertThat(prefixes).hasSize(1); assertThat(prefixes.contains("java.lang")).isTrue(); assertThat(prefixes.contains("java.util")).isFalse(); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/TemplateExpressionParsingTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/TemplateExpressionParsingTests.java index 49face8bf7d8..b810aed4abb9 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/TemplateExpressionParsingTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/TemplateExpressionParsingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -166,7 +166,7 @@ public void testNestedExpressions() throws Exception { assertThat(ex.getClass()).isEqualTo(CompositeStringExpression.class); CompositeStringExpression cse = (CompositeStringExpression)ex; Expression[] exprs = cse.getExpressions(); - assertThat(exprs.length).isEqualTo(3); + assertThat(exprs).hasSize(3); assertThat(exprs[1].getExpressionString()).isEqualTo("listOfNumbersUpToTen.$[#root.listOfNumbersUpToTen.$[#this%2==1]==3]"); s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class); assertThat(s).isEqualTo("hello world"); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ast/FormatHelperTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ast/FormatHelperTests.java index 2ecdc9eccb45..56ababdb63c8 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ast/FormatHelperTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ast/FormatHelperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -37,7 +37,7 @@ public void formatMethodWithSingleArgumentForMessage() { @Test public void formatMethodWithMultipleArgumentsForMessage() { - String message = FormatHelper.formatMethodForMessage("foo", Arrays.asList(TypeDescriptor.forObject("a string"), TypeDescriptor.forObject(Integer.valueOf(5)))); + String message = FormatHelper.formatMethodForMessage("foo", Arrays.asList(TypeDescriptor.forObject("a string"), TypeDescriptor.forObject(5))); assertThat(message).isEqualTo("foo(java.lang.String,java.lang.Integer)"); } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ast/OpPlusTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ast/OpPlusTests.java index ef6cb02b7ff5..15420e0fd457 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ast/OpPlusTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ast/OpPlusTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -108,7 +108,7 @@ public void test_binaryPlusWithNumberOperands() { assertThat(value.getTypeDescriptor().getObjectType()).isEqualTo(Double.class); assertThat(value.getTypeDescriptor().getType()).isEqualTo(Double.class); - assertThat(value.getValue()).isEqualTo(Double.valueOf(123.0 + 456.0)); + assertThat(value.getValue()).isEqualTo(123.0 + 456.0); } { @@ -119,7 +119,7 @@ public void test_binaryPlusWithNumberOperands() { assertThat(value.getTypeDescriptor().getObjectType()).isEqualTo(Long.class); assertThat(value.getTypeDescriptor().getType()).isEqualTo(Long.class); - assertThat(value.getValue()).isEqualTo(Long.valueOf(123L + 456L)); + assertThat(value.getValue()).isEqualTo(123L + 456L); } { @@ -130,7 +130,7 @@ public void test_binaryPlusWithNumberOperands() { assertThat(value.getTypeDescriptor().getObjectType()).isEqualTo(Integer.class); assertThat(value.getTypeDescriptor().getType()).isEqualTo(Integer.class); - assertThat(value.getValue()).isEqualTo(Integer.valueOf(123 + 456)); + assertThat(value.getValue()).isEqualTo(123 + 456); } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/support/ReflectionHelperTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/support/ReflectionHelperTests.java index c651d41d47b0..5b839c708420 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/support/ReflectionHelperTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/support/ReflectionHelperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -163,7 +163,7 @@ public void testReflectionHelperCompareArguments_Varargs_ExactMatching() { checkMatch2(new Class[] {Integer.class, String[].class}, new Class[] {String.class, String[].class}, tc, ArgumentsMatchKind.REQUIRES_CONVERSION); // Passing (Integer, Sub, String[]) on call to (String, Super, String[]) is exact match - checkMatch2(new Class[] {Integer.class, Sub.class, String[].class}, new Class[] {String.class,Super .class, String[].class}, tc, ArgumentsMatchKind.REQUIRES_CONVERSION); + checkMatch2(new Class[] {Integer.class, Sub.class, String[].class}, new Class[] {String.class, Super.class, String[].class}, tc, ArgumentsMatchKind.REQUIRES_CONVERSION); // Passing (String) on call to (String[]) is exact match checkMatch2(new Class[] {String.class}, new Class[] {String[].class}, tc, ArgumentsMatchKind.EXACT); @@ -253,11 +253,11 @@ public void testSetupArguments() { Object[] newArray = ReflectionHelper.setupArgumentsForVarargsInvocation( new Class[] {String[].class}, "a", "b", "c"); - assertThat(newArray.length).isEqualTo(1); + assertThat(newArray).hasSize(1); Object firstParam = newArray[0]; assertThat(firstParam.getClass().getComponentType()).isEqualTo(String.class); Object[] firstParamArray = (Object[]) firstParam; - assertThat(firstParamArray.length).isEqualTo(3); + assertThat(firstParamArray).hasSize(3); assertThat(firstParamArray[0]).isEqualTo("a"); assertThat(firstParamArray[1]).isEqualTo("b"); assertThat(firstParamArray[2]).isEqualTo("c"); @@ -298,7 +298,7 @@ public void testReflectivePropertyAccessor() throws Exception { assertThat(rpa.read(ctx, t, "property3").getValue()).isEqualTo("doodoo"); // Access through is method - assertThat(rpa .read(ctx, t, "field3").getValue()).isEqualTo(0); + assertThat(rpa.read(ctx, t, "field3").getValue()).isEqualTo(0); assertThat(rpa.read(ctx, t, "property4").getValue()).isEqualTo(false); assertThat(rpa.canRead(ctx, t, "property4")).isTrue(); @@ -412,7 +412,7 @@ else if (expectedMatchKind == ArgumentsMatchKind.REQUIRES_CONVERSION) { } private void checkArguments(Object[] args, Object... expected) { - assertThat(args.length).isEqualTo(expected.length); + assertThat(args).hasSize(expected.length); for (int i = 0; i < expected.length; i++) { checkArgument(expected[i],args[i]); } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/support/StandardComponentsTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/support/StandardComponentsTests.java index 62bd171e0b7f..e5b3dc8c85d9 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/support/StandardComponentsTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/support/StandardComponentsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -59,13 +59,13 @@ public void testStandardOperatorOverloader() throws EvaluationException { public void testStandardTypeLocator() { StandardTypeLocator tl = new StandardTypeLocator(); List prefixes = tl.getImportPrefixes(); - assertThat(prefixes.size()).isEqualTo(1); + assertThat(prefixes).hasSize(1); tl.registerImport("java.util"); prefixes = tl.getImportPrefixes(); - assertThat(prefixes.size()).isEqualTo(2); + assertThat(prefixes).hasSize(2); tl.removeImport("java.util"); prefixes = tl.getImportPrefixes(); - assertThat(prefixes.size()).isEqualTo(1); + assertThat(prefixes).hasSize(1); } @Test diff --git a/spring-jcl/src/main/java/org/apache/commons/logging/LogAdapter.java b/spring-jcl/src/main/java/org/apache/commons/logging/LogAdapter.java index 8ae19384f5be..74dbfbedca1a 100644 --- a/spring-jcl/src/main/java/org/apache/commons/logging/LogAdapter.java +++ b/spring-jcl/src/main/java/org/apache/commons/logging/LogAdapter.java @@ -17,6 +17,7 @@ package org.apache.commons.logging; import java.io.Serializable; +import java.util.function.Function; import java.util.logging.LogRecord; import org.apache.logging.log4j.Level; @@ -32,45 +33,52 @@ * Detects the presence of Log4j 2.x / SLF4J, falling back to {@code java.util.logging}. * * @author Juergen Hoeller + * @author Sebastien Deleuze * @since 5.1 */ final class LogAdapter { - private static final String LOG4J_SPI = "org.apache.logging.log4j.spi.ExtendedLogger"; + private static final boolean log4jSpiPresent = isPresent("org.apache.logging.log4j.spi.ExtendedLogger"); - private static final String LOG4J_SLF4J_PROVIDER = "org.apache.logging.slf4j.SLF4JProvider"; + private static final boolean log4jSlf4jProviderPresent = isPresent("org.apache.logging.slf4j.SLF4JProvider"); - private static final String SLF4J_SPI = "org.slf4j.spi.LocationAwareLogger"; + private static final boolean slf4jSpiPresent = isPresent("org.slf4j.spi.LocationAwareLogger"); - private static final String SLF4J_API = "org.slf4j.Logger"; + private static final boolean slf4jApiPresent = isPresent("org.slf4j.Logger"); - private static final LogApi logApi; + private static final Function createLog; static { - if (isPresent(LOG4J_SPI)) { - if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) { + if (log4jSpiPresent) { + if (log4jSlf4jProviderPresent && slf4jSpiPresent) { // log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI; // however, we still prefer Log4j over the plain SLF4J API since // the latter does not have location awareness support. - logApi = LogApi.SLF4J_LAL; + createLog = Slf4jAdapter::createLocationAwareLog; } else { // Use Log4j 2.x directly, including location awareness support - logApi = LogApi.LOG4J; + createLog = Log4jAdapter::createLog; } } - else if (isPresent(SLF4J_SPI)) { + else if (slf4jSpiPresent) { // Full SLF4J SPI including location awareness support - logApi = LogApi.SLF4J_LAL; + createLog = Slf4jAdapter::createLocationAwareLog; } - else if (isPresent(SLF4J_API)) { + else if (slf4jApiPresent) { // Minimal SLF4J API without location awareness support - logApi = LogApi.SLF4J; + createLog = Slf4jAdapter::createLog; } else { // java.util.logging as default - logApi = LogApi.JUL; + // Defensively use lazy-initializing adapter class here as well since the + // java.logging module is not present by default on JDK 9. We are requiring + // its presence if neither Log4j nor SLF4J is available; however, in the + // case of Log4j or SLF4J, we are trying to prevent early initialization + // of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly + // trying to parse the bytecode for all the cases of this switch clause. + createLog = JavaUtilAdapter::createLog; } } @@ -84,19 +92,7 @@ private LogAdapter() { * @param name the logger name */ public static Log createLog(String name) { - return switch (logApi) { - case LOG4J -> Log4jAdapter.createLog(name); - case SLF4J_LAL -> Slf4jAdapter.createLocationAwareLog(name); - case SLF4J -> Slf4jAdapter.createLog(name); - default -> - // Defensively use lazy-initializing adapter class here as well since the - // java.logging module is not present by default on JDK 9. We are requiring - // its presence if neither Log4j nor SLF4J is available; however, in the - // case of Log4j or SLF4J, we are trying to prevent early initialization - // of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly - // trying to parse the bytecode for all the cases of this switch clause. - JavaUtilAdapter.createLog(name); - }; + return createLog.apply(name); } private static boolean isPresent(String className) { @@ -104,15 +100,13 @@ private static boolean isPresent(String className) { Class.forName(className, false, LogAdapter.class.getClassLoader()); return true; } - catch (ClassNotFoundException ex) { + catch (Throwable ex) { + // Typically ClassNotFoundException or NoClassDefFoundError... return false; } } - private enum LogApi {LOG4J, SLF4J_LAL, SLF4J, JUL} - - private static class Log4jAdapter { public static Log createLog(String name) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java index f539a938265a..756ba8b82438 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -655,8 +655,8 @@ private T execute(PreparedStatementCreator psc, PreparedStatementCallback catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. - if (psc instanceof ParameterDisposer) { - ((ParameterDisposer) psc).cleanupParameters(); + if (psc instanceof ParameterDisposer parameterDisposer) { + parameterDisposer.cleanupParameters(); } String sql = getSql(psc); psc = null; @@ -668,8 +668,8 @@ private T execute(PreparedStatementCreator psc, PreparedStatementCallback } finally { if (closeResources) { - if (psc instanceof ParameterDisposer) { - ((ParameterDisposer) psc).cleanupParameters(); + if (psc instanceof ParameterDisposer parameterDisposer) { + parameterDisposer.cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con, getDataSource()); @@ -724,8 +724,8 @@ public T doInPreparedStatement(PreparedStatement ps) throws SQLException { } finally { JdbcUtils.closeResultSet(rs); - if (pss instanceof ParameterDisposer) { - ((ParameterDisposer) pss).cleanupParameters(); + if (pss instanceof ParameterDisposer parameterDisposer) { + parameterDisposer.cleanupParameters(); } } } @@ -839,8 +839,8 @@ public Stream queryForStream(PreparedStatementCreator psc, @Nullable Prep Connection con = ps.getConnection(); return new ResultSetSpliterator<>(rs, rowMapper).stream().onClose(() -> { JdbcUtils.closeResultSet(rs); - if (pss instanceof ParameterDisposer) { - ((ParameterDisposer) pss).cleanupParameters(); + if (pss instanceof ParameterDisposer parameterDisposer) { + parameterDisposer.cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con, getDataSource()); @@ -969,8 +969,8 @@ protected int update(final PreparedStatementCreator psc, @Nullable final Prepare return rows; } finally { - if (pss instanceof ParameterDisposer) { - ((ParameterDisposer) pss).cleanupParameters(); + if (pss instanceof ParameterDisposer parameterDisposer) { + parameterDisposer.cleanupParameters(); } } }, true)); @@ -1035,8 +1035,7 @@ public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) thr try { int batchSize = pss.getBatchSize(); InterruptibleBatchPreparedStatementSetter ipss = - (pss instanceof InterruptibleBatchPreparedStatementSetter ? - (InterruptibleBatchPreparedStatementSetter) pss : null); + (pss instanceof InterruptibleBatchPreparedStatementSetter ibpss ? ibpss : null); if (JdbcUtils.supportsBatchUpdates(ps.getConnection())) { for (int i = 0; i < batchSize; i++) { pss.setValues(ps, i); @@ -1064,8 +1063,8 @@ public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) thr } } finally { - if (pss instanceof ParameterDisposer) { - ((ParameterDisposer) pss).cleanupParameters(); + if (pss instanceof ParameterDisposer parameterDisposer) { + parameterDisposer.cleanupParameters(); } } }); @@ -1154,8 +1153,8 @@ public int[][] batchUpdate(String sql, final Collection batchArgs, final return result1; } finally { - if (pss instanceof ParameterDisposer) { - ((ParameterDisposer) pss).cleanupParameters(); + if (pss instanceof ParameterDisposer parameterDisposer) { + parameterDisposer.cleanupParameters(); } } }); @@ -1193,8 +1192,8 @@ public T execute(CallableStatementCreator csc, CallableStatementCallback catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. - if (csc instanceof ParameterDisposer) { - ((ParameterDisposer) csc).cleanupParameters(); + if (csc instanceof ParameterDisposer parameterDisposer) { + parameterDisposer.cleanupParameters(); } String sql = getSql(csc); csc = null; @@ -1205,8 +1204,8 @@ public T execute(CallableStatementCreator csc, CallableStatementCallback throw translateException("CallableStatementCallback", sql, ex); } finally { - if (csc instanceof ParameterDisposer) { - ((ParameterDisposer) csc).cleanupParameters(); + if (csc instanceof ParameterDisposer parameterDisposer) { + parameterDisposer.cleanupParameters(); } JdbcUtils.closeStatement(cs); DataSourceUtils.releaseConnection(con, getDataSource()); @@ -1345,14 +1344,14 @@ protected Map extractOutputParameters(CallableStatement cs, List } else { Object out = cs.getObject(sqlColIndex); - if (out instanceof ResultSet) { + if (out instanceof ResultSet resultSet) { if (outParam.isResultSetSupported()) { - results.putAll(processResultSet((ResultSet) out, outParam)); + results.putAll(processResultSet(resultSet, outParam)); } else { String rsName = outParam.getName(); SqlReturnResultSet rsParam = new SqlReturnResultSet(rsName, getColumnMapRowMapper()); - results.putAll(processResultSet((ResultSet) out, rsParam)); + results.putAll(processResultSet(resultSet, rsParam)); if (logger.isTraceEnabled()) { logger.trace("Added default SqlReturnResultSet parameter named '" + rsName + "'"); } @@ -1543,18 +1542,13 @@ protected DataAccessException translateException(String task, @Nullable String s /** * Determine SQL from potential provider object. - * @param sqlProvider object which is potentially an SqlProvider + * @param obj object which is potentially an SqlProvider * @return the SQL string, or {@code null} if not known * @see SqlProvider */ @Nullable - private static String getSql(Object sqlProvider) { - if (sqlProvider instanceof SqlProvider) { - return ((SqlProvider) sqlProvider).getSql(); - } - else { - return null; - } + private static String getSql(Object obj) { + return (obj instanceof SqlProvider sqlProvider ? sqlProvider.getSql() : null); } private static T result(@Nullable T result) { @@ -1613,8 +1607,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl // If return value is a JDBC Statement, apply statement settings // (fetch size, max rows, transaction timeout). - if (retVal instanceof Statement) { - applyStatementSettings(((Statement) retVal)); + if (retVal instanceof Statement statement) { + applyStatementSettings(statement); } return retVal; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java index b40703dad045..e01abfaeb16a 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -206,8 +206,8 @@ public PreparedStatementCreatorImpl(String actualSql, List parameters) { Set names = new HashSet<>(); for (int i = 0; i < parameters.size(); i++) { Object param = parameters.get(i); - if (param instanceof SqlParameterValue) { - names.add(((SqlParameterValue) param).getName()); + if (param instanceof SqlParameterValue sqlParameterValue) { + names.add(sqlParameterValue.getName()); } else { names.add("Parameter #" + i); @@ -252,9 +252,9 @@ public void setValues(PreparedStatement ps) throws SQLException { SqlParameter declaredParameter; // SqlParameterValue overrides declared parameter meta-data, in particular for // independence from the declared parameter position in case of named parameters. - if (in instanceof SqlParameterValue paramValue) { - in = paramValue.getValue(); - declaredParameter = paramValue; + if (in instanceof SqlParameterValue sqlParameterValue) { + in = sqlParameterValue.getValue(); + declaredParameter = sqlParameterValue; } else { if (declaredParameters.size() <= i) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java index 25cbddcb11f4..70eb055bf5e7 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -80,7 +80,7 @@ public RowMapperResultSetExtractor(RowMapper rowMapper) { * (just used for optimized collection handling) */ public RowMapperResultSetExtractor(RowMapper rowMapper, int rowsExpected) { - Assert.notNull(rowMapper, "RowMapper is required"); + Assert.notNull(rowMapper, "RowMapper must not be null"); this.rowMapper = rowMapper; this.rowsExpected = rowsExpected; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java index 3b762906d807..b6669a867091 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -193,9 +193,9 @@ protected Object convertValueToRequiredType(Object value, Class requiredType) return value.toString(); } else if (Number.class.isAssignableFrom(requiredType)) { - if (value instanceof Number) { + if (value instanceof Number number) { // Convert original Number to target Number class. - return NumberUtils.convertNumberToTargetClass(((Number) value), (Class) requiredType); + return NumberUtils.convertNumberToTargetClass(number, (Class) requiredType); } else { // Convert stringified value to target Number class. diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataContext.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataContext.java index e55ee9621da0..c9ef473dd5cd 100755 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataContext.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -569,8 +569,8 @@ else if (logger.isInfoEnabled()) { if (callParameterName == null) { if (logger.isDebugEnabled()) { Object value = parameterValue; - if (value instanceof SqlParameterValue) { - value = ((SqlParameterValue) value).getValue(); + if (value instanceof SqlParameterValue sqlParameterValue) { + value = sqlParameterValue.getValue(); } if (value != null) { logger.debug("Unable to locate the corresponding IN or IN-OUT parameter for \"" + diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/AbstractSqlParameterSource.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/AbstractSqlParameterSource.java index 5d044e499c81..a68f7899ca9c 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/AbstractSqlParameterSource.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/AbstractSqlParameterSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -105,8 +105,8 @@ public String toString() { StringJoiner result = new StringJoiner(", ", getClass().getSimpleName() + " {", "}"); for (String parameterName : parameterNames) { Object value = getValue(parameterName); - if (value instanceof SqlParameterValue) { - value = ((SqlParameterValue) value).getValue(); + if (value instanceof SqlParameterValue sqlParameterValue) { + value = sqlParameterValue.getValue(); } String typeName = getTypeName(parameterName); if (typeName == null) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java index 5159ed16cd2c..57e02e3595d8 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java index cf8280ba18f8..d1cb75e02d8f 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java @@ -291,11 +291,11 @@ public static String substituteNamedParameters(ParsedSql parsedSql, @Nullable Sq actualSql.append(originalSql, lastIndex, startIndex); if (paramSource != null && paramSource.hasValue(paramName)) { Object value = paramSource.getValue(paramName); - if (value instanceof SqlParameterValue) { - value = ((SqlParameterValue) value).getValue(); + if (value instanceof SqlParameterValue sqlParameterValue) { + value = sqlParameterValue.getValue(); } - if (value instanceof Iterable) { - Iterator entryIter = ((Iterable) value).iterator(); + if (value instanceof Iterable iterable) { + Iterator entryIter = iterable.iterator(); int k = 0; while (entryIter.hasNext()) { if (k > 0) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/JdbcDaoSupport.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/JdbcDaoSupport.java index 522b96d733cc..f9a5ceb934a4 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/JdbcDaoSupport.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/JdbcDaoSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlLobValue.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlLobValue.java index 44f027d2770e..621921436a08 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlLobValue.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/SqlLobValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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,15 +30,15 @@ /** * Object to represent an SQL BLOB/CLOB value parameter. BLOBs can either be an - * InputStream or a byte array. CLOBs can be in the form of a Reader, InputStream + * InputStream or a byte array. CLOBs can be in the form of a Reader, InputStream, * or String. Each CLOB/BLOB value will be stored together with its length. - * The type is based on which constructor is used. Objects of this class are - * immutable except for the LobCreator reference. Use them and discard them. + * The type is based on which constructor is used. Instances of this class are + * stateful and immutable: use them and discard them. * - *

This class holds a reference to a LocCreator that must be closed after the - * update has completed. This is done via a call to the closeLobCreator method. - * All handling of the LobCreator is done by the framework classes that use it - - * no need to set or close the LobCreator for end users of this class. + *

This class holds a reference to a {@link LobCreator} that must be closed after + * the update has completed. This is done via a call to the {@link #cleanup()} method. + * All handling of the {@code LobCreator} is done by the framework classes that use it - + * no need to set or close the {@code LobCreator} for end users of this class. * *

A usage example: * @@ -72,8 +72,7 @@ public class SqlLobValue implements DisposableSqlTypeValue { private final int length; /** - * This contains a reference to the LobCreator - so we can close it - * once the update is done. + * Reference to the LobCreator - so we can close it once the update is done. */ private final LobCreator lobCreator; @@ -178,11 +177,11 @@ public void setTypeValue(PreparedStatement ps, int paramIndex, int sqlType, @Nul if (this.content instanceof byte[] || this.content == null) { this.lobCreator.setBlobAsBytes(ps, paramIndex, (byte[]) this.content); } - else if (this.content instanceof String) { - this.lobCreator.setBlobAsBytes(ps, paramIndex, ((String) this.content).getBytes()); + else if (this.content instanceof String string) { + this.lobCreator.setBlobAsBytes(ps, paramIndex, string.getBytes()); } - else if (this.content instanceof InputStream) { - this.lobCreator.setBlobAsBinaryStream(ps, paramIndex, (InputStream) this.content, this.length); + else if (this.content instanceof InputStream inputStream) { + this.lobCreator.setBlobAsBinaryStream(ps, paramIndex, inputStream, this.length); } else { throw new IllegalArgumentException( @@ -193,11 +192,11 @@ else if (sqlType == Types.CLOB) { if (this.content instanceof String || this.content == null) { this.lobCreator.setClobAsString(ps, paramIndex, (String) this.content); } - else if (this.content instanceof InputStream) { - this.lobCreator.setClobAsAsciiStream(ps, paramIndex, (InputStream) this.content, this.length); + else if (this.content instanceof InputStream inputStream) { + this.lobCreator.setClobAsAsciiStream(ps, paramIndex, inputStream, this.length); } - else if (this.content instanceof Reader) { - this.lobCreator.setClobAsCharacterStream(ps, paramIndex, (Reader) this.content, this.length); + else if (this.content instanceof Reader reader) { + this.lobCreator.setClobAsCharacterStream(ps, paramIndex, reader, this.length); } else { throw new IllegalArgumentException( diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHolder.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHolder.java index 4f3d1a785a51..4d13484aa840 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHolder.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -157,7 +157,7 @@ protected void setConnection(@Nullable Connection connection) { * @see #released() */ public Connection getConnection() { - Assert.notNull(this.connectionHandle, "Active Connection is required"); + Assert.state(this.connectionHandle != null, "Active Connection is required"); if (this.currentConnection == null) { this.currentConnection = this.connectionHandle.getConnection(); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceTransactionManager.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceTransactionManager.java index 1e249b27777b..5091abead331 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceTransactionManager.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceTransactionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -162,11 +162,11 @@ public DataSourceTransactionManager(DataSource dataSource) { * @see org.springframework.transaction.jta.JtaTransactionManager */ public void setDataSource(@Nullable DataSource dataSource) { - if (dataSource instanceof TransactionAwareDataSourceProxy) { + if (dataSource instanceof TransactionAwareDataSourceProxy tadsp) { // If we got a TransactionAwareDataSourceProxy, we need to perform transactions // for its underlying target DataSource, else data access code won't see // properly exposed transactions (i.e. transactions for the target DataSource). - this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource(); + this.dataSource = tadsp.getTargetDataSource(); } else { this.dataSource = dataSource; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java index 2b213e0d578b..f730ea14320e 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java @@ -438,8 +438,8 @@ private static boolean connectionEquals(ConnectionHolder conHolder, Connection p */ public static Connection getTargetConnection(Connection con) { Connection conToUse = con; - while (conToUse instanceof ConnectionProxy) { - conToUse = ((ConnectionProxy) conToUse).getTargetConnection(); + while (conToUse instanceof ConnectionProxy connectionProxy) { + conToUse = connectionProxy.getTargetConnection(); } return conToUse; } @@ -455,9 +455,9 @@ public static Connection getTargetConnection(Connection con) { private static int getConnectionSynchronizationOrder(DataSource dataSource) { int order = CONNECTION_SYNCHRONIZATION_ORDER; DataSource currDs = dataSource; - while (currDs instanceof DelegatingDataSource) { + while (currDs instanceof DelegatingDataSource delegatingDataSource) { order--; - currDs = ((DelegatingDataSource) currDs).getTargetDataSource(); + currDs = delegatingDataSource.getTargetDataSource(); } return order; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/SimpleDriverDataSource.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/SimpleDriverDataSource.java index 55b75e02bf73..99a00687e470 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/SimpleDriverDataSource.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/SimpleDriverDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -136,8 +136,8 @@ public Driver getDriver() { @Override protected Connection getConnectionFromDriver(Properties props) throws SQLException { Driver driver = getDriver(); + Assert.state(driver != null, "Driver has not been set"); String url = getUrl(); - Assert.notNull(driver, "Driver must not be null"); if (logger.isDebugEnabled()) { logger.debug("Creating new JDBC Driver Connection to [" + url + "]"); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java index 9edaca7857a4..b2d18673830b 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -239,8 +239,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl // If return value is a Statement, apply transaction timeout. // Applies to createStatement, prepareStatement, prepareCall. - if (retVal instanceof Statement) { - DataSourceUtils.applyTransactionTimeout((Statement) retVal, this.targetDataSource); + if (retVal instanceof Statement statement) { + DataSourceUtils.applyTransactionTimeout(statement, this.targetDataSource); } return retVal; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactory.java index e979a07f9553..abdbfe26af32 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactory.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -216,9 +216,9 @@ protected void initDatabase() { protected void shutdownDatabase() { if (this.dataSource != null) { if (logger.isInfoEnabled()) { - if (this.dataSource instanceof SimpleDriverDataSource) { + if (this.dataSource instanceof SimpleDriverDataSource simpleDriverDataSource) { logger.info(String.format("Shutting down embedded database: url='%s'", - ((SimpleDriverDataSource) this.dataSource).getUrl())); + simpleDriverDataSource.getUrl())); } else { logger.info(String.format("Shutting down embedded database '%s'", this.databaseName)); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java index f9ac77a9cd9d..a6d0d1ace6ef 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java index 43819de0b3fc..b51f01eca1dc 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -299,8 +299,8 @@ public static void executeSqlScript(Connection connection, EncodedResource resou } } catch (Exception ex) { - if (ex instanceof ScriptException) { - throw (ScriptException) ex; + if (ex instanceof ScriptException scriptException) { + throw scriptException; } throw new UncategorizedScriptException( "Failed to execute database script from resource [" + resource + "]", ex); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlFunction.java b/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlFunction.java index 4a3cb1d8409d..59408ac658a4 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlFunction.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -161,10 +161,10 @@ public int run(int parameter) { */ public int run(Object... parameters) { Object obj = super.findObject(parameters); - if (!(obj instanceof Number)) { + if (!(obj instanceof Number number)) { throw new TypeMismatchDataAccessException("Could not convert result object [" + obj + "] to int"); } - return ((Number) obj).intValue(); + return number.intValue(); } /** diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java index ecfa2d23dd4c..fb42d833a4f2 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java @@ -213,10 +213,10 @@ else if (requiredType.isEnum()) { if (obj instanceof String) { return obj; } - else if (obj instanceof Number) { + else if (obj instanceof Number number) { // Defensively convert any Number to an Integer (as needed by our // ConversionService's IntegerToEnumConverterFactory) for use as index - return NumberUtils.convertNumberToTargetClass((Number) obj, Integer.class); + return NumberUtils.convertNumberToTargetClass(number, Integer.class); } else { // e.g. on Postgres: getObject returns a PGObject but we need a String @@ -405,8 +405,8 @@ public static T extractDatabaseMetaData(DataSource dataSource, final String "Could not access DatabaseMetaData method '" + metaDataMethodName + "'", ex); } catch (InvocationTargetException ex) { - if (ex.getTargetException() instanceof SQLException) { - throw (SQLException) ex.getTargetException(); + if (ex.getTargetException() instanceof SQLException sqlException) { + throw sqlException; } throw new MetaDataAccessException( "Invocation of DatabaseMetaData method '" + metaDataMethodName + "' failed", ex); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java index 02943bdebf3a..a0622bfdcafa 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java @@ -177,6 +177,7 @@ public SQLErrorCodes getSqlErrorCodes() { } + @SuppressWarnings("deprecation") @Override @Nullable protected DataAccessException doTranslate(String task, @Nullable String sql, SQLException ex) { @@ -216,8 +217,8 @@ protected DataAccessException doTranslate(String task, @Nullable String sql, SQL // Try to find SQLException with actual error code, looping through the causes. // E.g. applicable to java.sql.DataTruncation as of JDK 1.6. SQLException current = sqlEx; - while (current.getErrorCode() == 0 && current.getCause() instanceof SQLException) { - current = (SQLException) current.getCause(); + while (current.getErrorCode() == 0 && current.getCause() instanceof SQLException sqlException) { + current = sqlException; } errorCode = Integer.toString(current.getErrorCode()); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java index 7e86c84fce43..56f227bf1f57 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java @@ -30,12 +30,14 @@ import java.sql.SQLTransientConnectionException; import java.sql.SQLTransientException; -import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.dao.CannotAcquireLockException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.PermissionDeniedDataAccessException; +import org.springframework.dao.PessimisticLockingFailureException; import org.springframework.dao.QueryTimeoutException; import org.springframework.dao.RecoverableDataAccessException; import org.springframework.dao.TransientDataAccessResourceException; @@ -69,10 +71,13 @@ protected DataAccessException doTranslate(String task, @Nullable String sql, SQL if (ex instanceof SQLTransientConnectionException) { return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); } - else if (ex instanceof SQLTransactionRollbackException) { - return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex); + if (ex instanceof SQLTransactionRollbackException) { + if ("40001".equals(ex.getSQLState())) { + return new CannotAcquireLockException(buildMessage(task, sql, ex), ex); + } + return new PessimisticLockingFailureException(buildMessage(task, sql, ex), ex); } - else if (ex instanceof SQLTimeoutException) { + if (ex instanceof SQLTimeoutException) { return new QueryTimeoutException(buildMessage(task, sql, ex), ex); } } @@ -80,19 +85,22 @@ else if (ex instanceof SQLNonTransientException) { if (ex instanceof SQLNonTransientConnectionException) { return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex); } - else if (ex instanceof SQLDataException) { + if (ex instanceof SQLDataException) { return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); } - else if (ex instanceof SQLIntegrityConstraintViolationException) { + if (ex instanceof SQLIntegrityConstraintViolationException) { + if ("23505".equals(ex.getSQLState())) { + return new DuplicateKeyException(buildMessage(task, sql, ex), ex); + } return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); } - else if (ex instanceof SQLInvalidAuthorizationSpecException) { + if (ex instanceof SQLInvalidAuthorizationSpecException) { return new PermissionDeniedDataAccessException(buildMessage(task, sql, ex), ex); } - else if (ex instanceof SQLSyntaxErrorException) { + if (ex instanceof SQLSyntaxErrorException) { return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex); } - else if (ex instanceof SQLFeatureNotSupportedException) { + if (ex instanceof SQLFeatureNotSupportedException) { return new InvalidDataAccessApiUsageException(buildMessage(task, sql, ex), ex); } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java index 45859935cd0c..897a8321e122 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -17,13 +17,14 @@ package org.springframework.jdbc.support; import java.sql.SQLException; -import java.util.HashSet; import java.util.Set; -import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.dao.CannotAcquireLockException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.dao.PessimisticLockingFailureException; import org.springframework.dao.QueryTimeoutException; import org.springframework.dao.TransientDataAccessResourceException; import org.springframework.jdbc.BadSqlGrammarException; @@ -46,45 +47,42 @@ */ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLExceptionTranslator { - private static final Set BAD_SQL_GRAMMAR_CODES = new HashSet<>(8); - - private static final Set DATA_INTEGRITY_VIOLATION_CODES = new HashSet<>(8); - - private static final Set DATA_ACCESS_RESOURCE_FAILURE_CODES = new HashSet<>(8); - - private static final Set TRANSIENT_DATA_ACCESS_RESOURCE_CODES = new HashSet<>(8); - - private static final Set CONCURRENCY_FAILURE_CODES = new HashSet<>(4); - - - static { - BAD_SQL_GRAMMAR_CODES.add("07"); // Dynamic SQL error - BAD_SQL_GRAMMAR_CODES.add("21"); // Cardinality violation - BAD_SQL_GRAMMAR_CODES.add("2A"); // Syntax error direct SQL - BAD_SQL_GRAMMAR_CODES.add("37"); // Syntax error dynamic SQL - BAD_SQL_GRAMMAR_CODES.add("42"); // General SQL syntax error - BAD_SQL_GRAMMAR_CODES.add("65"); // Oracle: unknown identifier - - DATA_INTEGRITY_VIOLATION_CODES.add("01"); // Data truncation - DATA_INTEGRITY_VIOLATION_CODES.add("02"); // No data found - DATA_INTEGRITY_VIOLATION_CODES.add("22"); // Value out of range - DATA_INTEGRITY_VIOLATION_CODES.add("23"); // Integrity constraint violation - DATA_INTEGRITY_VIOLATION_CODES.add("27"); // Triggered data change violation - DATA_INTEGRITY_VIOLATION_CODES.add("44"); // With check violation - - DATA_ACCESS_RESOURCE_FAILURE_CODES.add("08"); // Connection exception - DATA_ACCESS_RESOURCE_FAILURE_CODES.add("53"); // PostgreSQL: insufficient resources (e.g. disk full) - DATA_ACCESS_RESOURCE_FAILURE_CODES.add("54"); // PostgreSQL: program limit exceeded (e.g. statement too complex) - DATA_ACCESS_RESOURCE_FAILURE_CODES.add("57"); // DB2: out-of-memory exception / database not started - DATA_ACCESS_RESOURCE_FAILURE_CODES.add("58"); // DB2: unexpected system error - - TRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("JW"); // Sybase: internal I/O error - TRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("JZ"); // Sybase: unexpected I/O error - TRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("S1"); // DB2: communication failure - - CONCURRENCY_FAILURE_CODES.add("40"); // Transaction rollback - CONCURRENCY_FAILURE_CODES.add("61"); // Oracle: deadlock - } + private static final Set BAD_SQL_GRAMMAR_CODES = Set.of( + "07", // Dynamic SQL error + "21", // Cardinality violation + "2A", // Syntax error direct SQL + "37", // Syntax error dynamic SQL + "42", // General SQL syntax error + "65" // Oracle: unknown identifier + ); + + private static final Set DATA_INTEGRITY_VIOLATION_CODES = Set.of( + "01", // Data truncation + "02", // No data found + "22", // Value out of range + "23", // Integrity constraint violation + "27", // Triggered data change violation + "44" // With check violation + ); + + private static final Set DATA_ACCESS_RESOURCE_FAILURE_CODES = Set.of( + "08", // Connection exception + "53", // PostgreSQL: insufficient resources (e.g. disk full) + "54", // PostgreSQL: program limit exceeded (e.g. statement too complex) + "57", // DB2: out-of-memory exception / database not started + "58" // DB2: unexpected system error + ); + + private static final Set TRANSIENT_DATA_ACCESS_RESOURCE_CODES = Set.of( + "JW", // Sybase: internal I/O error + "JZ", // Sybase: unexpected I/O error + "S1" // DB2: communication failure + ); + + private static final Set PESSIMISTIC_LOCKING_FAILURE_CODES = Set.of( + "40", // Transaction rollback + "61" // Oracle: deadlock + ); @Override @@ -101,6 +99,9 @@ protected DataAccessException doTranslate(String task, @Nullable String sql, SQL return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex); } else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) { + if ("23505".equals(sqlState)) { + return new DuplicateKeyException(buildMessage(task, sql, ex), ex); + } return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); } else if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) { @@ -109,8 +110,11 @@ else if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) { else if (TRANSIENT_DATA_ACCESS_RESOURCE_CODES.contains(classCode)) { return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); } - else if (CONCURRENCY_FAILURE_CODES.contains(classCode)) { - return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex); + else if (PESSIMISTIC_LOCKING_FAILURE_CODES.contains(classCode)) { + if ("40001".equals(sqlState)) { + return new CannotAcquireLockException(buildMessage(task, sql, ex), ex); + } + return new PessimisticLockingFailureException(buildMessage(task, sql, ex), ex); } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/SqlServerSequenceMaxValueIncrementer.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/SqlServerSequenceMaxValueIncrementer.java new file mode 100644 index 000000000000..079405cab553 --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/SqlServerSequenceMaxValueIncrementer.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2022 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.jdbc.support.incrementer; + +import javax.sql.DataSource; + +/** + * {@link DataFieldMaxValueIncrementer} that retrieves the next value of a given + * SQL Server sequence. + * + * @author Mahmoud Ben Hassine + * @since 6.0 + */ +public class SqlServerSequenceMaxValueIncrementer extends AbstractSequenceMaxValueIncrementer { + + /** + * Default constructor for bean property style usage. + * @see #setDataSource + * @see #setIncrementerName + */ + public SqlServerSequenceMaxValueIncrementer() { + } + + /** + * Convenience constructor. + * @param dataSource the DataSource to use + * @param incrementerName the name of the sequence to use + */ + public SqlServerSequenceMaxValueIncrementer(DataSource dataSource, String incrementerName) { + super(dataSource, incrementerName); + } + + @Override + protected String getSequenceQuery() { + return "select next value for " + getIncrementerName(); + } + +} diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/PassThroughBlob.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/PassThroughBlob.java index 1db65528f84b..4a304abea0cb 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/PassThroughBlob.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/PassThroughBlob.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/PassThroughClob.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/PassThroughClob.java index 452d2d47857b..b2c82d4007be 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/PassThroughClob.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/lob/PassThroughClob.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/rowset/ResultSetWrappingSqlRowSet.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/rowset/ResultSetWrappingSqlRowSet.java index eaca52ef4b08..393e38514d79 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/rowset/ResultSetWrappingSqlRowSet.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/rowset/ResultSetWrappingSqlRowSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/config/InitializeDatabaseIntegrationTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/config/InitializeDatabaseIntegrationTests.java index e3464094f652..bd6e2efcdb53 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/config/InitializeDatabaseIntegrationTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/config/InitializeDatabaseIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -110,7 +110,7 @@ public void testCacheInitialization() throws Exception { context = new ClassPathXmlApplicationContext("org/springframework/jdbc/config/jdbc-initialize-cache-config.xml"); assertCorrectSetup(context.getBean("dataSource", DataSource.class)); CacheData cache = context.getBean(CacheData.class); - assertThat(cache.getCachedData().size()).isEqualTo(1); + assertThat(cache.getCachedData()).hasSize(1); } private void assertCorrectSetup(DataSource dataSource) { diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/DataClassRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/DataClassRowMapperTests.java index 9bacf3ae3822..9a87d2c67758 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/DataClassRowMapperTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/DataClassRowMapperTests.java @@ -41,7 +41,7 @@ public void testStaticQueryWithDataClass() throws Exception { List result = mock.getJdbcTemplate().query( "select name, age, birth_date, balance from people", new DataClassRowMapper<>(ConstructorPerson.class)); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); verifyPerson(result.get(0)); mock.verifyClosed(); @@ -53,7 +53,7 @@ public void testStaticQueryWithDataClassAndGenerics() throws Exception { List result = mock.getJdbcTemplate().query( "select name, age, birth_date, balance from people", new DataClassRowMapper<>(ConstructorPersonWithGenerics.class)); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); ConstructorPersonWithGenerics person = result.get(0); assertThat(person.name()).isEqualTo("Bubba"); assertThat(person.age()).isEqualTo(22L); @@ -69,7 +69,7 @@ public void testStaticQueryWithDataClassAndSetters() throws Exception { List result = mock.getJdbcTemplate().query( "select name, age, birthdate, balance from people", new DataClassRowMapper<>(ConstructorPersonWithSetters.class)); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); ConstructorPersonWithSetters person = result.get(0); assertThat(person.name()).isEqualTo("BUBBA"); assertThat(person.age()).isEqualTo(22L); @@ -85,7 +85,7 @@ public void testStaticQueryWithDataRecord() throws Exception { List result = mock.getJdbcTemplate().query( "select name, age, birth_date, balance from people", new DataClassRowMapper<>(RecordPerson.class)); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); verifyPerson(result.get(0)); mock.verifyClosed(); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java index 1470e9d6c977..8f0cf9d8cafd 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -238,7 +238,7 @@ public void testQueryForInt() throws Exception { String sql = "SELECT AGE FROM CUSTMR WHERE ID = 3"; given(this.resultSet.next()).willReturn(true, false); given(this.resultSet.getInt(1)).willReturn(22); - int i = this.template.queryForObject(sql, Integer.class).intValue(); + int i = this.template.queryForObject(sql, Integer.class); assertThat(i).as("Return of an int").isEqualTo(22); verify(this.resultSet).close(); verify(this.statement).close(); @@ -260,7 +260,7 @@ public void testQueryForLong() throws Exception { String sql = "SELECT AGE FROM CUSTMR WHERE ID = 3"; given(this.resultSet.next()).willReturn(true, false); given(this.resultSet.getLong(1)).willReturn(87L); - long l = this.template.queryForObject(sql, Long.class).longValue(); + long l = this.template.queryForObject(sql, Long.class); assertThat(l).as("Return of a long").isEqualTo(87); verify(this.resultSet).close(); verify(this.statement).close(); @@ -395,7 +395,7 @@ public void testQueryForIntWithArgs() throws Exception { String sql = "SELECT AGE FROM CUSTMR WHERE ID = ?"; given(this.resultSet.next()).willReturn(true, false); given(this.resultSet.getInt(1)).willReturn(22); - int i = this.template.queryForObject(sql, Integer.class, 3).intValue(); + int i = this.template.queryForObject(sql, Integer.class, 3); assertThat(i).as("Return of an int").isEqualTo(22); verify(this.preparedStatement).setObject(1, 3); verify(this.resultSet).close(); @@ -407,7 +407,7 @@ public void testQueryForLongWithArgs() throws Exception { String sql = "SELECT AGE FROM CUSTMR WHERE ID = ?"; given(this.resultSet.next()).willReturn(true, false); given(this.resultSet.getLong(1)).willReturn(87L); - long l = this.template.queryForObject(sql, Long.class, 3).longValue(); + long l = this.template.queryForObject(sql, Long.class, 3); assertThat(l).as("Return of a long").isEqualTo(87); verify(this.preparedStatement).setObject(1, 3); verify(this.resultSet).close(); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java index 85fe6fe64596..3e85f82634bc 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java @@ -214,13 +214,13 @@ public String[] getStrings() { JdbcTemplate template = new JdbcTemplate(); template.setDataSource(this.dataSource); if (fetchSize != null) { - template.setFetchSize(fetchSize.intValue()); + template.setFetchSize(fetchSize); } if (maxRows != null) { - template.setMaxRows(maxRows.intValue()); + template.setMaxRows(maxRows); } if (queryTimeout != null) { - template.setQueryTimeout(queryTimeout.intValue()); + template.setQueryTimeout(queryTimeout); } jdbcTemplateCallback.doInJdbcTemplate(template, sql, sh); @@ -232,13 +232,13 @@ public String[] getStrings() { } if (fetchSize != null) { - verify(this.preparedStatement).setFetchSize(fetchSize.intValue()); + verify(this.preparedStatement).setFetchSize(fetchSize); } if (maxRows != null) { - verify(this.preparedStatement).setMaxRows(maxRows.intValue()); + verify(this.preparedStatement).setMaxRows(maxRows); } if (queryTimeout != null) { - verify(this.preparedStatement).setQueryTimeout(queryTimeout.intValue()); + verify(this.preparedStatement).setQueryTimeout(queryTimeout); } if (argument != null) { verify(this.preparedStatement).setObject(1, argument); @@ -362,10 +362,10 @@ public void testSqlUpdateWithArguments() throws Exception { given(this.preparedStatement.executeUpdate()).willReturn(rowsAffected); int actualRowsAffected = this.template.update(sql, - 4, new SqlParameterValue(Types.NUMERIC, 2, Float.valueOf(1.4142f))); + 4, new SqlParameterValue(Types.NUMERIC, 2, 1.4142f)); assertThat(actualRowsAffected == rowsAffected).as("Actual rows affected is correct").isTrue(); verify(this.preparedStatement).setObject(1, 4); - verify(this.preparedStatement).setObject(2, Float.valueOf(1.4142f), Types.NUMERIC, 2); + verify(this.preparedStatement).setObject(2, 1.4142f, Types.NUMERIC, 2); verify(this.preparedStatement).close(); verify(this.connection).close(); } @@ -759,7 +759,7 @@ public void testBatchUpdateWithCollectionOfObjects() throws Exception { given(this.preparedStatement.executeBatch()).willReturn(rowsAffected1, rowsAffected2); mockDatabaseMetaData(true); - ParameterizedPreparedStatementSetter setter = (ps, argument) -> ps.setInt(1, argument.intValue()); + ParameterizedPreparedStatementSetter setter = (ps, argument) -> ps.setInt(1, argument); JdbcTemplate template = new JdbcTemplate(this.dataSource, false); int[][] actualRowsAffected = template.batchUpdate(sql, ids, 2, setter); @@ -1082,7 +1082,7 @@ public void testEquallyNamedColumn() throws SQLException { given(this.resultSet.getObject(2)).willReturn("second value"); Map map = this.template.queryForMap("my query"); - assertThat(map.size()).isEqualTo(1); + assertThat(map).hasSize(1); assertThat(map.get("x")).isEqualTo("first value"); } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/RowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/RowMapperTests.java index 22896c2d1a24..3f55c8983a86 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/RowMapperTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/RowMapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -83,7 +83,7 @@ public void verifyClosed() throws Exception { @AfterEach public void verifyResults() { assertThat(result).isNotNull(); - assertThat(result.size()).isEqualTo(2); + assertThat(result).hasSize(2); TestBean testBean1 = result.get(0); TestBean testBean2 = result.get(1); assertThat(testBean1.getName()).isEqualTo("tb1"); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java index 31fa105d0059..88e9b799c1dd 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -283,7 +283,7 @@ public void testQueryWithRowCallbackHandler() throws SQLException { customers.add(cust); }); - assertThat(customers.size()).isEqualTo(1); + assertThat(customers).hasSize(1); assertThat(customers.get(0).getId() == 1).as("Customer id was assigned correctly").isTrue(); assertThat(customers.get(0).getForename().equals("rod")).as("Customer forename was assigned correctly").isTrue(); verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS_PARSED); @@ -308,7 +308,7 @@ public void testQueryWithRowCallbackHandlerNoParameters() throws SQLException { customers.add(cust); }); - assertThat(customers.size()).isEqualTo(1); + assertThat(customers).hasSize(1); assertThat(customers.get(0).getId() == 1).as("Customer id was assigned correctly").isTrue(); assertThat(customers.get(0).getForename().equals("rod")).as("Customer forename was assigned correctly").isTrue(); verify(connection).prepareStatement(SELECT_NO_PARAMETERS); @@ -333,7 +333,7 @@ public void testQueryWithRowMapper() throws SQLException { return cust; }); - assertThat(customers.size()).isEqualTo(1); + assertThat(customers).hasSize(1); assertThat(customers.get(0).getId() == 1).as("Customer id was assigned correctly").isTrue(); assertThat(customers.get(0).getForename().equals("rod")).as("Customer forename was assigned correctly").isTrue(); verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS_PARSED); @@ -358,7 +358,7 @@ public void testQueryWithRowMapperNoParameters() throws SQLException { return cust; }); - assertThat(customers.size()).isEqualTo(1); + assertThat(customers).hasSize(1); assertThat(customers.get(0).getId() == 1).as("Customer id was assigned correctly").isTrue(); assertThat(customers.get(0).getForename().equals("rod")).as("Customer forename was assigned correctly").isTrue(); verify(connection).prepareStatement(SELECT_NO_PARAMETERS); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java index 1a2c61469908..68f6876d62ba 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -265,7 +265,7 @@ public void testQueryForIntWithParamMap() throws Exception { MapSqlParameterSource params = new MapSqlParameterSource(); params.addValue("id", 3); - int i = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID = :id", params, Integer.class).intValue(); + int i = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID = :id", params, Integer.class); assertThat(i).as("Return of an int").isEqualTo(22); verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); @@ -279,7 +279,7 @@ public void testQueryForLongWithParamBean() throws Exception { given(resultSet.getLong(1)).willReturn(87L); BeanPropertySqlParameterSource params = new BeanPropertySqlParameterSource(new ParameterBean(3)); - long l = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID = :id", params, Long.class).longValue(); + long l = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID = :id", params, Long.class); assertThat(l).as("Return of a long").isEqualTo(87); verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); @@ -293,7 +293,7 @@ public void testQueryForLongWithParamBeanWithCollection() throws Exception { given(resultSet.getLong(1)).willReturn(87L); BeanPropertySqlParameterSource params = new BeanPropertySqlParameterSource(new ParameterCollectionBean(3, 5)); - long l = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID IN (:ids)", params, Long.class).longValue(); + long l = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID IN (:ids)", params, Long.class); assertThat(l).as("Return of a long").isEqualTo(87); verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID IN (?, ?)"); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java index 0c10b1c30114..23c4e6e838c2 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterUtilsTests.java @@ -161,7 +161,7 @@ public void parseSqlContainingComments() { paramMap.addValue("b", "b"); paramMap.addValue("c", "c"); Object[] params = NamedParameterUtils.buildValueArray(psql1, paramMap, null); - assertThat(params.length).isEqualTo(4); + assertThat(params).hasSize(4); assertThat(params[0]).isEqualTo("a"); assertThat(params[1]).isEqualTo("b"); assertThat(params[2]).isEqualTo("c"); @@ -224,7 +224,7 @@ public void parseSqlStatementWithEscapedColon() { String sql = "select '0\\:0' as a, foo from bar where baz < DATE(:p1 23\\:59\\:59) and baz = :p2"; ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); - assertThat(parsedSql.getParameterNames().size()).isEqualTo(2); + assertThat(parsedSql.getParameterNames()).hasSize(2); assertThat(parsedSql.getParameterNames().get(0)).isEqualTo("p1"); assertThat(parsedSql.getParameterNames().get(1)).isEqualTo("p2"); String finalSql = NamedParameterUtils.substituteNamedParameters(parsedSql, null); @@ -237,7 +237,7 @@ public void parseSqlStatementWithBracketDelimitedParameterNames() { String sql = "select foo from bar where baz = b:{p1}:{p2}z"; ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); - assertThat(parsedSql.getParameterNames().size()).isEqualTo(2); + assertThat(parsedSql.getParameterNames()).hasSize(2); assertThat(parsedSql.getParameterNames().get(0)).isEqualTo("p1"); assertThat(parsedSql.getParameterNames().get(1)).isEqualTo("p2"); String finalSql = NamedParameterUtils.substituteNamedParameters(parsedSql, null); @@ -249,7 +249,7 @@ public void parseSqlStatementWithEmptyBracketsOrBracketsInQuotes() { String expectedSql = "select foo from bar where baz = b:{}z"; String sql = "select foo from bar where baz = b:{}z"; ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); - assertThat(parsedSql.getParameterNames().size()).isEqualTo(0); + assertThat(parsedSql.getParameterNames()).isEmpty(); String finalSql = NamedParameterUtils.substituteNamedParameters(parsedSql, null); assertThat(finalSql).isEqualTo(expectedSql); @@ -257,7 +257,7 @@ public void parseSqlStatementWithEmptyBracketsOrBracketsInQuotes() { String sql2 = "select foo from bar where baz = 'b:{p1}z'"; ParsedSql parsedSql2 = NamedParameterUtils.parseSqlStatement(sql2); - assertThat(parsedSql2.getParameterNames().size()).isEqualTo(0); + assertThat(parsedSql2.getParameterNames()).isEmpty(); String finalSql2 = NamedParameterUtils.substituteNamedParameters(parsedSql2, null); assertThat(finalSql2).isEqualTo(expectedSql2); } @@ -268,7 +268,7 @@ public void parseSqlStatementWithSingleLetterInBrackets() { String sql = "select foo from bar where baz = b:{p}z"; ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); - assertThat(parsedSql.getParameterNames().size()).isEqualTo(1); + assertThat(parsedSql.getParameterNames()).hasSize(1); assertThat(parsedSql.getParameterNames().get(0)).isEqualTo("p"); String finalSql = NamedParameterUtils.substituteNamedParameters(parsedSql, null); assertThat(finalSql).isEqualTo(expectedSql); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/object/BatchSqlUpdateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/object/BatchSqlUpdateTests.java index 6ea55b5ff31a..010add2dc955 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/object/BatchSqlUpdateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/object/BatchSqlUpdateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -77,11 +77,11 @@ private void doTestBatchUpdate(boolean flushThroughBatchSize) throws Exception { if (flushThroughBatchSize) { assertThat(update.getQueueCount()).isEqualTo(0); - assertThat(update.getRowsAffected().length).isEqualTo(2); + assertThat(update.getRowsAffected()).hasSize(2); } else { assertThat(update.getQueueCount()).isEqualTo(2); - assertThat(update.getRowsAffected().length).isEqualTo(0); + assertThat(update.getRowsAffected()).isEmpty(); } int[] actualRowsAffected = update.flush(); @@ -102,7 +102,7 @@ private void doTestBatchUpdate(boolean flushThroughBatchSize) throws Exception { assertThat(actualRowsAffected[1]).isEqualTo(rowsAffected[1]); update.reset(); - assertThat(update.getRowsAffected().length).isEqualTo(0); + assertThat(update.getRowsAffected()).isEmpty(); verify(preparedStatement).setObject(1, ids[0], Types.INTEGER); verify(preparedStatement).setObject(1, ids[1], Types.INTEGER); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/object/RdbmsOperationTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/object/RdbmsOperationTests.java index aff13d43a7b7..ef6b09019257 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/object/RdbmsOperationTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/object/RdbmsOperationTests.java @@ -167,7 +167,7 @@ public void parametersSetWithList() { new SqlParameter("two", Types.NUMERIC)}); operation.afterPropertiesSet(); operation.validateParameters(new Object[] { 1, "2" }); - assertThat(operation.getDeclaredParameters().size()).isEqualTo(2); + assertThat(operation.getDeclaredParameters()).hasSize(2); } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/object/SqlUpdateTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/object/SqlUpdateTests.java index 852d8555a7c9..6b1f0ab3a550 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/object/SqlUpdateTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/object/SqlUpdateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -224,7 +224,7 @@ public void testUpdateAndGeneratedKeys() throws SQLException { int rowsAffected = pc.run("rod", generatedKeyHolder); assertThat(rowsAffected).isEqualTo(1); - assertThat(generatedKeyHolder.getKeyList().size()).isEqualTo(1); + assertThat(generatedKeyHolder.getKeyList()).hasSize(1); assertThat(generatedKeyHolder.getKey().intValue()).isEqualTo(11); verify(preparedStatement).setString(1, "rod"); verify(resultSet).close(); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java index 02441425f81f..e946eb2fe2c6 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/object/StoredProcedureTests.java @@ -317,7 +317,7 @@ public void testStoredProcedureWithUndeclaredResults() throws Exception { assertThat(rs1).containsExactly("Foo", "Bar"); List rs2 = (List) res.get("#result-set-2"); - assertThat(rs2.size()).isEqualTo(1); + assertThat(rs2).hasSize(1); Object o2 = rs2.get(0); assertThat(o2).as("wron type returned for result set 2").isInstanceOf(Map.class); Map m2 = (Map) o2; @@ -362,7 +362,7 @@ public void testStoredProcedureSkippingUndeclaredResults() throws Exception { assertThat(res.size()).as("incorrect number of returns").isEqualTo(1); List rs1 = (List) res.get("rs"); - assertThat(rs1.size()).isEqualTo(2); + assertThat(rs1).hasSize(2); assertThat(rs1.get(0)).isEqualTo("Foo"); assertThat(rs1.get(1)).isEqualTo("Bar"); verify(resultSet).close(); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodesFactoryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodesFactoryTests.java index 7ebf7267551a..cd3d78c3a423 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodesFactoryTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLErrorCodesFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -185,7 +185,7 @@ protected Resource loadResource(String path) { // Should have loaded without error TestSQLErrorCodesFactory sf = new TestSQLErrorCodesFactory(); assertThat(sf.getErrorCodes("XX").getBadSqlGrammarCodes().length == 0).isTrue(); - assertThat(sf.getErrorCodes("Oracle").getBadSqlGrammarCodes().length).isEqualTo(2); + assertThat(sf.getErrorCodes("Oracle").getBadSqlGrammarCodes()).hasSize(2); assertThat(sf.getErrorCodes("Oracle").getBadSqlGrammarCodes()[0]).isEqualTo("1"); assertThat(sf.getErrorCodes("Oracle").getBadSqlGrammarCodes()[1]).isEqualTo("2"); } @@ -206,7 +206,7 @@ protected Resource loadResource(String path) { // Should have failed to load without error TestSQLErrorCodesFactory sf = new TestSQLErrorCodesFactory(); assertThat(sf.getErrorCodes("XX").getBadSqlGrammarCodes().length == 0).isTrue(); - assertThat(sf.getErrorCodes("Oracle").getBadSqlGrammarCodes().length).isEqualTo(0); + assertThat(sf.getErrorCodes("Oracle").getBadSqlGrammarCodes()).isEmpty(); } /** @@ -226,11 +226,11 @@ protected Resource loadResource(String path) { // Should have loaded without error TestSQLErrorCodesFactory sf = new TestSQLErrorCodesFactory(); - assertThat(sf.getErrorCodes("Oracle").getCustomTranslations().length).isEqualTo(1); + assertThat(sf.getErrorCodes("Oracle").getCustomTranslations()).hasSize(1); CustomSQLErrorCodesTranslation translation = sf.getErrorCodes("Oracle").getCustomTranslations()[0]; assertThat(translation.getExceptionClass()).isEqualTo(CustomErrorCodeException.class); - assertThat(translation.getErrorCodes().length).isEqualTo(1); + assertThat(translation.getErrorCodes()).hasSize(1); } @Test @@ -371,8 +371,8 @@ protected Resource loadResource(String path) { } private void assertIsEmpty(SQLErrorCodes sec) { - assertThat(sec.getBadSqlGrammarCodes().length).isEqualTo(0); - assertThat(sec.getDataIntegrityViolationCodes().length).isEqualTo(0); + assertThat(sec.getBadSqlGrammarCodes()).isEmpty(); + assertThat(sec.getDataIntegrityViolationCodes()).isEmpty(); } } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionCustomTranslatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionCustomTranslatorTests.java index 8d46c0f578d4..0e1228df7821 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionCustomTranslatorTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionCustomTranslatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 @@ package org.springframework.jdbc.support; +import java.sql.SQLDataException; import java.sql.SQLException; import org.junit.jupiter.api.Test; @@ -37,8 +38,8 @@ public class SQLExceptionCustomTranslatorTests { private static SQLErrorCodes ERROR_CODES = new SQLErrorCodes(); static { - ERROR_CODES.setBadSqlGrammarCodes(new String[] { "1" }); - ERROR_CODES.setDataAccessResourceFailureCodes(new String[] { "2" }); + ERROR_CODES.setBadSqlGrammarCodes("1"); + ERROR_CODES.setDataAccessResourceFailureCodes("2"); ERROR_CODES.setCustomSqlExceptionTranslatorClass(CustomSqlExceptionTranslator.class); } @@ -47,7 +48,7 @@ public class SQLExceptionCustomTranslatorTests { @Test public void badSqlGrammarException() { - SQLException badSqlGrammarExceptionEx = SQLExceptionSubclassFactory.newSQLDataException("", "", 1); + SQLException badSqlGrammarExceptionEx = new SQLDataException("", "", 1); DataAccessException dae = sext.translate("task", "SQL", badSqlGrammarExceptionEx); assertThat(dae.getCause()).isEqualTo(badSqlGrammarExceptionEx); assertThat(dae).isInstanceOf(BadSqlGrammarException.class); @@ -55,7 +56,7 @@ public void badSqlGrammarException() { @Test public void dataAccessResourceException() { - SQLException dataAccessResourceEx = SQLExceptionSubclassFactory.newSQLDataException("", "", 2); + SQLException dataAccessResourceEx = new SQLDataException("", "", 2); DataAccessException dae = sext.translate("task", "SQL", dataAccessResourceEx); assertThat(dae.getCause()).isEqualTo(dataAccessResourceEx); assertThat(dae).isInstanceOf(TransientDataAccessResourceException.class); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionSubclassFactory.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionSubclassFactory.java deleted file mode 100644 index a172139de1e5..000000000000 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionSubclassFactory.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2002-2022 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.jdbc.support; - -import java.sql.SQLDataException; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.sql.SQLIntegrityConstraintViolationException; -import java.sql.SQLInvalidAuthorizationSpecException; -import java.sql.SQLNonTransientConnectionException; -import java.sql.SQLRecoverableException; -import java.sql.SQLSyntaxErrorException; -import java.sql.SQLTimeoutException; -import java.sql.SQLTransactionRollbackException; -import java.sql.SQLTransientConnectionException; - -/** - * Class to generate {@link SQLException} subclasses for testing purposes. - * - * @author Thomas Risberg - */ -public class SQLExceptionSubclassFactory { - - public static SQLException newSQLDataException(String reason, String SQLState, int vendorCode) { - return new SQLDataException(reason, SQLState, vendorCode); - } - - public static SQLException newSQLFeatureNotSupportedException(String reason, String SQLState, int vendorCode) { - return new SQLFeatureNotSupportedException(reason, SQLState, vendorCode); - } - - public static SQLException newSQLIntegrityConstraintViolationException(String reason, String SQLState, int vendorCode) { - return new SQLIntegrityConstraintViolationException(reason, SQLState, vendorCode); - } - - public static SQLException newSQLInvalidAuthorizationSpecException(String reason, String SQLState, int vendorCode) { - return new SQLInvalidAuthorizationSpecException(reason, SQLState, vendorCode); - } - - public static SQLException newSQLNonTransientConnectionException(String reason, String SQLState, int vendorCode) { - return new SQLNonTransientConnectionException(reason, SQLState, vendorCode); - } - - public static SQLException newSQLSyntaxErrorException(String reason, String SQLState, int vendorCode) { - return new SQLSyntaxErrorException(reason, SQLState, vendorCode); - } - - public static SQLException newSQLTransactionRollbackException(String reason, String SQLState, int vendorCode) { - return new SQLTransactionRollbackException(reason, SQLState, vendorCode); - } - - public static SQLException newSQLTransientConnectionException(String reason, String SQLState, int vendorCode) { - return new SQLTransientConnectionException(reason, SQLState, vendorCode); - } - - public static SQLException newSQLTimeoutException(String reason, String SQLState, int vendorCode) { - return new SQLTimeoutException(reason, SQLState, vendorCode); - } - - public static SQLException newSQLRecoverableException(String reason, String SQLState, int vendorCode) { - return new SQLRecoverableException(reason, SQLState, vendorCode); - } - -} diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslatorTests.java index 0be4d4b0d8d4..fc403e265a88 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslatorTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,15 +16,28 @@ package org.springframework.jdbc.support; +import java.sql.SQLDataException; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLIntegrityConstraintViolationException; +import java.sql.SQLInvalidAuthorizationSpecException; +import java.sql.SQLNonTransientConnectionException; +import java.sql.SQLRecoverableException; +import java.sql.SQLSyntaxErrorException; +import java.sql.SQLTimeoutException; +import java.sql.SQLTransactionRollbackException; +import java.sql.SQLTransientConnectionException; import org.junit.jupiter.api.Test; -import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.dao.CannotAcquireLockException; +import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.PermissionDeniedDataAccessException; +import org.springframework.dao.PessimisticLockingFailureException; import org.springframework.dao.QueryTimeoutException; import org.springframework.dao.RecoverableDataAccessException; import org.springframework.dao.TransientDataAccessResourceException; @@ -34,78 +47,43 @@ /** * @author Thomas Risberg + * @author Juergen Hoeller */ public class SQLExceptionSubclassTranslatorTests { - private static SQLErrorCodes ERROR_CODES = new SQLErrorCodes(); - - static { - ERROR_CODES.setBadSqlGrammarCodes("1"); + @Test + public void exceptionClassTranslation() { + doTest(new SQLDataException("", "", 0), DataIntegrityViolationException.class); + doTest(new SQLFeatureNotSupportedException("", "", 0), InvalidDataAccessApiUsageException.class); + doTest(new SQLIntegrityConstraintViolationException("", "", 0), DataIntegrityViolationException.class); + doTest(new SQLIntegrityConstraintViolationException("", "23505", 0), DuplicateKeyException.class); + doTest(new SQLInvalidAuthorizationSpecException("", "", 0), PermissionDeniedDataAccessException.class); + doTest(new SQLNonTransientConnectionException("", "", 0), DataAccessResourceFailureException.class); + doTest(new SQLRecoverableException("", "", 0), RecoverableDataAccessException.class); + doTest(new SQLSyntaxErrorException("", "", 0), BadSqlGrammarException.class); + doTest(new SQLTimeoutException("", "", 0), QueryTimeoutException.class); + doTest(new SQLTransactionRollbackException("", "", 0), PessimisticLockingFailureException.class); + doTest(new SQLTransactionRollbackException("", "40001", 0), CannotAcquireLockException.class); + doTest(new SQLTransientConnectionException("", "", 0), TransientDataAccessResourceException.class); } - @Test - public void errorCodeTranslation() { - SQLExceptionTranslator sext = new SQLErrorCodeSQLExceptionTranslator(ERROR_CODES); - - SQLException dataIntegrityViolationEx = SQLExceptionSubclassFactory.newSQLDataException("", "", 0); - DataIntegrityViolationException divex = (DataIntegrityViolationException) sext.translate("task", "SQL", dataIntegrityViolationEx); - assertThat(divex.getCause()).isEqualTo(dataIntegrityViolationEx); - - SQLException featureNotSupEx = SQLExceptionSubclassFactory.newSQLFeatureNotSupportedException("", "", 0); - InvalidDataAccessApiUsageException idaex = (InvalidDataAccessApiUsageException) sext.translate("task", "SQL", featureNotSupEx); - assertThat(idaex.getCause()).isEqualTo(featureNotSupEx); - - SQLException dataIntegrityViolationEx2 = SQLExceptionSubclassFactory.newSQLIntegrityConstraintViolationException("", "", 0); - DataIntegrityViolationException divex2 = (DataIntegrityViolationException) sext.translate("task", "SQL", dataIntegrityViolationEx2); - assertThat(divex2.getCause()).isEqualTo(dataIntegrityViolationEx2); - - SQLException permissionDeniedEx = SQLExceptionSubclassFactory.newSQLInvalidAuthorizationSpecException("", "", 0); - PermissionDeniedDataAccessException pdaex = (PermissionDeniedDataAccessException) sext.translate("task", "SQL", permissionDeniedEx); - assertThat(pdaex.getCause()).isEqualTo(permissionDeniedEx); - - SQLException dataAccessResourceEx = SQLExceptionSubclassFactory.newSQLNonTransientConnectionException("", "", 0); - DataAccessResourceFailureException darex = (DataAccessResourceFailureException) sext.translate("task", "SQL", dataAccessResourceEx); - assertThat(darex.getCause()).isEqualTo(dataAccessResourceEx); - - SQLException badSqlEx2 = SQLExceptionSubclassFactory.newSQLSyntaxErrorException("", "", 0); - BadSqlGrammarException bsgex2 = (BadSqlGrammarException) sext.translate("task", "SQL2", badSqlEx2); - assertThat(bsgex2.getSql()).isEqualTo("SQL2"); - assertThat((Object) bsgex2.getSQLException()).isEqualTo(badSqlEx2); - - SQLException tranRollbackEx = SQLExceptionSubclassFactory.newSQLTransactionRollbackException("", "", 0); - ConcurrencyFailureException cfex = (ConcurrencyFailureException) sext.translate("task", "SQL", tranRollbackEx); - assertThat(cfex.getCause()).isEqualTo(tranRollbackEx); - - SQLException transientConnEx = SQLExceptionSubclassFactory.newSQLTransientConnectionException("", "", 0); - TransientDataAccessResourceException tdarex = (TransientDataAccessResourceException) sext.translate("task", "SQL", transientConnEx); - assertThat(tdarex.getCause()).isEqualTo(transientConnEx); - - SQLException transientConnEx2 = SQLExceptionSubclassFactory.newSQLTimeoutException("", "", 0); - QueryTimeoutException tdarex2 = (QueryTimeoutException) sext.translate("task", "SQL", transientConnEx2); - assertThat(tdarex2.getCause()).isEqualTo(transientConnEx2); - - SQLException recoverableEx = SQLExceptionSubclassFactory.newSQLRecoverableException("", "", 0); - RecoverableDataAccessException rdaex2 = (RecoverableDataAccessException) sext.translate("task", "SQL", recoverableEx); - assertThat(rdaex2.getCause()).isEqualTo(recoverableEx); - - // Test classic error code translation. We should move there next if the exception we pass in is not one - // of the new subclasses. - SQLException sexEct = new SQLException("", "", 1); - BadSqlGrammarException bsgEct = (BadSqlGrammarException) sext.translate("task", "SQL-ECT", sexEct); - assertThat(bsgEct.getSql()).isEqualTo("SQL-ECT"); - assertThat((Object) bsgEct.getSQLException()).isEqualTo(sexEct); - + public void fallbackStateTranslation() { // Test fallback. We assume that no database will ever return this error code, // but 07xxx will be bad grammar picked up by the fallback SQLState translator - SQLException sexFbt = new SQLException("", "07xxx", 666666666); - BadSqlGrammarException bsgFbt = (BadSqlGrammarException) sext.translate("task", "SQL-FBT", sexFbt); - assertThat(bsgFbt.getSql()).isEqualTo("SQL-FBT"); - assertThat((Object) bsgFbt.getSQLException()).isEqualTo(sexFbt); + doTest(new SQLException("", "07xxx", 666666666), BadSqlGrammarException.class); // and 08xxx will be data resource failure (non-transient) picked up by the fallback SQLState translator - SQLException sexFbt2 = new SQLException("", "08xxx", 666666666); - DataAccessResourceFailureException darfFbt = (DataAccessResourceFailureException) sext.translate("task", "SQL-FBT2", sexFbt2); - assertThat(darfFbt.getCause()).isEqualTo(sexFbt2); + doTest(new SQLException("", "08xxx", 666666666), DataAccessResourceFailureException.class); + } + + + private void doTest(SQLException ex, Class dataAccessExceptionType) { + SQLExceptionTranslator translator = new SQLExceptionSubclassTranslator(); + DataAccessException dax = translator.translate("task", "SQL", ex); + + assertThat(dax).as("Specific translation must not result in null").isNotNull(); + assertThat(dax).as("Wrong DataAccessException type returned").isExactlyInstanceOf(dataAccessExceptionType); + assertThat(dax.getCause()).as("The exact same original SQLException must be preserved").isSameAs(ex); } } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateExceptionTranslatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateExceptionTranslatorTests.java deleted file mode 100644 index 608e0b5d3312..000000000000 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateExceptionTranslatorTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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. - * 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.jdbc.support; - -import java.sql.SQLException; - -import org.junit.jupiter.api.Test; - -import org.springframework.jdbc.BadSqlGrammarException; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rod Johnson - * @since 13-Jan-03 - */ -public class SQLStateExceptionTranslatorTests { - - private static final String sql = "SELECT FOO FROM BAR"; - - private final SQLStateSQLExceptionTranslator trans = new SQLStateSQLExceptionTranslator(); - - // ALSO CHECK CHAIN of SQLExceptions!? - // also allow chain of translators? default if can't do specific? - - @Test - public void badSqlGrammar() { - SQLException sex = new SQLException("Message", "42001", 1); - try { - throw this.trans.translate("task", sql, sex); - } - catch (BadSqlGrammarException ex) { - // OK - assertThat(sql.equals(ex.getSql())).as("SQL is correct").isTrue(); - assertThat(sex.equals(ex.getSQLException())).as("Exception matches").isTrue(); - } - } - - @Test - public void invalidSqlStateCode() { - SQLException sex = new SQLException("Message", "NO SUCH CODE", 1); - assertThat(this.trans.translate("task", sql, sex)).isNull(); - } - - /** - * PostgreSQL can return null. - * SAP DB can apparently return empty SQL code. - * Bug 729170 - */ - @Test - public void malformedSqlStateCodes() { - SQLException sex = new SQLException("Message", null, 1); - assertThat(this.trans.translate("task", sql, sex)).isNull(); - - sex = new SQLException("Message", "", 1); - assertThat(this.trans.translate("task", sql, sex)).isNull(); - - // One char's not allowed - sex = new SQLException("Message", "I", 1); - assertThat(this.trans.translate("task", sql, sex)).isNull(); - } - -} diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslatorTests.java index 98baf1ab7f9d..b667d3fae2ec 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslatorTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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,12 +20,15 @@ import org.junit.jupiter.api.Test; -import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.dao.CannotAcquireLockException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.dao.PessimisticLockingFailureException; import org.springframework.dao.TransientDataAccessResourceException; import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.lang.Nullable; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -37,58 +40,83 @@ */ public class SQLStateSQLExceptionTranslatorTests { - private static final String REASON = "The game is afoot!"; - - private static final String TASK = "Counting sheep... yawn."; - - private static final String SQL = "select count(0) from t_sheep where over_fence = ... yawn... 1"; - - @Test - public void testTranslateNullException() { + public void translateNullException() { assertThatIllegalArgumentException().isThrownBy(() -> new SQLStateSQLExceptionTranslator().translate("", "", null)); } @Test - public void testTranslateBadSqlGrammar() { + public void translateBadSqlGrammar() { doTest("07", BadSqlGrammarException.class); } @Test - public void testTranslateDataIntegrityViolation() { + public void translateDataIntegrityViolation() { doTest("23", DataIntegrityViolationException.class); } @Test - public void testTranslateDataAccessResourceFailure() { + public void translateDuplicateKey() { + doTest("23505", DuplicateKeyException.class); + } + + @Test + public void translateDataAccessResourceFailure() { doTest("53", DataAccessResourceFailureException.class); } @Test - public void testTranslateTransientDataAccessResourceFailure() { + public void translateTransientDataAccessResourceFailure() { doTest("S1", TransientDataAccessResourceException.class); } @Test - public void testTranslateConcurrencyFailure() { - doTest("40", ConcurrencyFailureException.class); + public void translatePessimisticLockingFailure() { + doTest("40", PessimisticLockingFailureException.class); + } + + @Test + public void translateCannotAcquireLock() { + doTest("40001", CannotAcquireLockException.class); } @Test - public void testTranslateUncategorized() { - assertThat(new SQLStateSQLExceptionTranslator().translate("", "", new SQLException(REASON, "00000000"))).isNull(); + public void translateUncategorized() { + doTest("00000000", null); } + @Test + public void invalidSqlStateCode() { + doTest("NO SUCH CODE", null); + } - private void doTest(String sqlState, Class dataAccessExceptionType) { - SQLException ex = new SQLException(REASON, sqlState); + /** + * PostgreSQL can return null. + * SAP DB can apparently return empty SQL code. + * Bug 729170 + */ + @Test + public void malformedSqlStateCodes() { + doTest(null, null); + doTest("", null); + doTest("I", null); + } + + + private void doTest(@Nullable String sqlState, @Nullable Class dataAccessExceptionType) { SQLExceptionTranslator translator = new SQLStateSQLExceptionTranslator(); - DataAccessException dax = translator.translate(TASK, SQL, ex); - assertThat(dax).as("Specific translation must not result in a null DataAccessException being returned.").isNotNull(); - assertThat(dax.getClass()).as("Wrong DataAccessException type returned as the result of the translation").isEqualTo(dataAccessExceptionType); - assertThat(dax.getCause()).as("The original SQLException must be preserved in the translated DataAccessException").isNotNull(); - assertThat(dax.getCause()).as("The exact same original SQLException must be preserved in the translated DataAccessException").isSameAs(ex); + SQLException ex = new SQLException("reason", sqlState); + DataAccessException dax = translator.translate("task", "SQL", ex); + + if (dataAccessExceptionType == null) { + assertThat(dax).as("Expected translation to null").isNull(); + return; + } + + assertThat(dax).as("Specific translation must not result in null").isNotNull(); + assertThat(dax).as("Wrong DataAccessException type returned").isExactlyInstanceOf(dataAccessExceptionType); + assertThat(dax.getCause()).as("The exact same original SQLException must be preserved").isSameAs(ex); } } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/DataFieldMaxValueIncrementerTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/incrementer/DataFieldMaxValueIncrementerTests.java similarity index 90% rename from spring-jdbc/src/test/java/org/springframework/jdbc/support/DataFieldMaxValueIncrementerTests.java rename to spring-jdbc/src/test/java/org/springframework/jdbc/support/incrementer/DataFieldMaxValueIncrementerTests.java index 57b919f64889..6e99fe8b34ab 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/DataFieldMaxValueIncrementerTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/incrementer/DataFieldMaxValueIncrementerTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.jdbc.support; +package org.springframework.jdbc.support.incrementer; import java.sql.Connection; import java.sql.ResultSet; @@ -25,14 +25,6 @@ import org.junit.jupiter.api.Test; -import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; -import org.springframework.jdbc.support.incrementer.HanaSequenceMaxValueIncrementer; -import org.springframework.jdbc.support.incrementer.HsqlMaxValueIncrementer; -import org.springframework.jdbc.support.incrementer.MariaDBSequenceMaxValueIncrementer; -import org.springframework.jdbc.support.incrementer.MySQLMaxValueIncrementer; -import org.springframework.jdbc.support.incrementer.OracleSequenceMaxValueIncrementer; -import org.springframework.jdbc.support.incrementer.PostgresSequenceMaxValueIncrementer; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -234,4 +226,26 @@ void postgresSequenceMaxValueIncrementer() throws SQLException { verify(connection, times(2)).close(); } + @Test + void sqlServerSequenceMaxValueIncrementer() throws SQLException { + given(dataSource.getConnection()).willReturn(connection); + given(connection.createStatement()).willReturn(statement); + given(statement.executeQuery("select next value for myseq")).willReturn(resultSet); + given(resultSet.next()).willReturn(true); + given(resultSet.getLong(1)).willReturn(10L, 12L); + + SqlServerSequenceMaxValueIncrementer incrementer = new SqlServerSequenceMaxValueIncrementer(); + incrementer.setDataSource(dataSource); + incrementer.setIncrementerName("myseq"); + incrementer.setPaddingLength(5); + incrementer.afterPropertiesSet(); + + assertThat(incrementer.nextStringValue()).isEqualTo("00010"); + assertThat(incrementer.nextIntValue()).isEqualTo(12); + + verify(resultSet, times(2)).close(); + verify(statement, times(2)).close(); + verify(connection, times(2)).close(); + } + } diff --git a/spring-jms/src/main/java/org/springframework/jms/core/support/JmsGatewaySupport.java b/spring-jms/src/main/java/org/springframework/jms/core/support/JmsGatewaySupport.java index ba05ba60816c..627e5fc311b1 100644 --- a/spring-jms/src/main/java/org/springframework/jms/core/support/JmsGatewaySupport.java +++ b/spring-jms/src/main/java/org/springframework/jms/core/support/JmsGatewaySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java index 0cd9bf2bd8e6..3ca176ef05b0 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java +++ b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -303,8 +303,8 @@ protected void handleResult(Object result, Message request, @Nullable Session se * @see #setMessageConverter */ protected Message buildMessage(Session session, Object result) throws JMSException { - Object content = preProcessResponse(result instanceof JmsResponse - ? ((JmsResponse) result).getResponse() : result); + Object content = preProcessResponse(result instanceof JmsResponse jmsResponse ? + jmsResponse.getResponse() : result); MessageConverter converter = getMessageConverter(); if (converter != null) { @@ -316,11 +316,11 @@ protected Message buildMessage(Session session, Object result) throws JMSExcepti } } - if (!(content instanceof Message)) { + if (!(content instanceof Message message)) { throw new MessageConversionException( "No MessageConverter specified - cannot handle message [" + content + "]"); } - return (Message) content; + return message; } /** @@ -355,8 +355,7 @@ protected void postProcessResponse(Message request, Message response) throws JMS private Destination getResponseDestination(Message request, Message response, Session session, Object result) throws JMSException { - if (result instanceof JmsResponse) { - JmsResponse jmsResponse = (JmsResponse) result; + if (result instanceof JmsResponse jmsResponse) { Destination destination = jmsResponse.resolveDestination(getDestinationResolver(), session); if (destination != null) { return destination; @@ -408,8 +407,8 @@ protected Destination getResponseDestination(Message request, Message response, */ @Nullable protected Destination resolveDefaultResponseDestination(Session session) throws JMSException { - if (this.defaultResponseDestination instanceof Destination) { - return (Destination) this.defaultResponseDestination; + if (this.defaultResponseDestination instanceof Destination destination) { + return destination; } if (this.defaultResponseDestination instanceof DestinationNameHolder nameHolder) { return getDestinationResolver().resolveDestinationName(session, nameHolder.name, nameHolder.isTopic); @@ -471,11 +470,11 @@ public Object fromMessage(jakarta.jms.Message message) throws JMSException, Mess @Override protected Object extractPayload(Message message) throws JMSException { Object payload = extractMessage(message); - if (message instanceof BytesMessage) { + if (message instanceof BytesMessage bytesMessage) { try { // In case the BytesMessage is going to be received as a user argument: // reset it, otherwise it would appear empty to such processing code... - ((BytesMessage) message).reset(); + bytesMessage.reset(); } catch (JMSException ex) { // Continue since the BytesMessage typically won't be used any further. @@ -493,8 +492,8 @@ protected Message createMessageForPayload(Object payload, Session session, @Null if (converter == null) { throw new IllegalStateException("No message converter, cannot handle '" + payload + "'"); } - if (converter instanceof SmartMessageConverter) { - return ((SmartMessageConverter) converter).toMessage(payload, session, conversionHint); + if (converter instanceof SmartMessageConverter smartMessageConverter) { + return smartMessageConverter.toMessage(payload, session, conversionHint); } return converter.toMessage(payload, session); @@ -538,8 +537,8 @@ public Object getPayload() { @SuppressWarnings("rawtypes") private Object unwrapPayload() throws JMSException { Object payload = extractPayload(this.message); - if (payload instanceof org.springframework.messaging.Message) { - return ((org.springframework.messaging.Message) payload).getPayload(); + if (payload instanceof org.springframework.messaging.Message springMessage) { + return springMessage.getPayload(); } return payload; } diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/JmsResponse.java b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/JmsResponse.java index 2fdaca138416..0bd717501649 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/JmsResponse.java +++ b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/JmsResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -62,11 +62,11 @@ public class JmsResponse { /** * Create a new {@link JmsResponse} instance. - * @param response the content of the result + * @param response the content of the response * @param destination the destination */ protected JmsResponse(T response, Object destination) { - Assert.notNull(response, "Result must not be null"); + Assert.notNull(response, "'response' must not be null"); this.response = response; this.destination = destination; } @@ -91,8 +91,8 @@ public T getResponse() { public Destination resolveDestination(DestinationResolver destinationResolver, Session session) throws JMSException { - if (this.destination instanceof Destination) { - return (Destination) this.destination; + if (this.destination instanceof Destination dest) { + return dest; } if (this.destination instanceof DestinationNameHolder nameHolder) { return destinationResolver.resolveDestinationName(session, diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessageListenerAdapter.java b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessageListenerAdapter.java index 6adbf2a80544..e361273c5ac4 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessageListenerAdapter.java +++ b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessageListenerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -208,8 +208,8 @@ public void onMessage(Message message, @Nullable Session session) throws JMSExce ((SessionAwareMessageListener) delegate).onMessage(message, session); return; } - if (delegate instanceof MessageListener) { - ((MessageListener) delegate).onMessage(message); + if (delegate instanceof MessageListener listener) { + listener.onMessage(message); return; } } @@ -232,8 +232,8 @@ public void onMessage(Message message, @Nullable Session session) throws JMSExce @Override public String getSubscriptionName() { Object delegate = getDelegate(); - if (delegate != this && delegate instanceof SubscriptionNameProvider) { - return ((SubscriptionNameProvider) delegate).getSubscriptionName(); + if (delegate != this && delegate instanceof SubscriptionNameProvider provider) { + return provider.getSubscriptionName(); } else { return delegate.getClass().getName(); @@ -296,8 +296,8 @@ protected Object invokeListenerMethod(String methodName, Object[] arguments) thr } catch (InvocationTargetException ex) { Throwable targetEx = ex.getTargetException(); - if (targetEx instanceof JMSException) { - throw (JMSException) targetEx; + if (targetEx instanceof JMSException jmsException) { + throw jmsException; } else { throw new ListenerExecutionFailedException( diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java index c858d2b1519c..f82a274fdda1 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java +++ b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -86,9 +86,10 @@ public void onMessage(jakarta.jms.Message jmsMessage, @Nullable Session session) @Override protected Object preProcessResponse(Object result) { MethodParameter returnType = getHandlerMethod().getReturnType(); - if (result instanceof Message) { - return MessageBuilder.fromMessage((Message) result) - .setHeader(AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType).build(); + if (result instanceof Message message) { + return MessageBuilder.fromMessage(message) + .setHeader(AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType) + .build(); } return MessageBuilder.withPayload(result).setHeader( AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType).build(); diff --git a/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java b/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java index 4428770630b4..eaacf41e75c7 100644 --- a/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java +++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java @@ -54,8 +54,6 @@ *
  • {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled
  • * * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Mark Pollack * @author Dave Syer * @author Juergen Hoeller diff --git a/spring-jms/src/main/java/org/springframework/jms/support/converter/MarshallingMessageConverter.java b/spring-jms/src/main/java/org/springframework/jms/support/converter/MarshallingMessageConverter.java index c3dd8389ae16..d13a05b35a20 100644 --- a/spring-jms/src/main/java/org/springframework/jms/support/converter/MarshallingMessageConverter.java +++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/MarshallingMessageConverter.java @@ -64,7 +64,7 @@ public class MarshallingMessageConverter implements MessageConverter, Initializi /** * Construct a new {@code MarshallingMessageConverter} with no {@link Marshaller} * or {@link Unmarshaller} set. The marshaller must be set after construction by invoking - * {@link #setMarshaller(Marshaller)} and {@link #setUnmarshaller(Unmarshaller)} . + * {@link #setMarshaller(Marshaller)} and {@link #setUnmarshaller(Unmarshaller)}. */ public MarshallingMessageConverter() { } diff --git a/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java b/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java index 70daf59e493d..fdf54e4f7913 100644 --- a/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java +++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/MessagingMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -70,8 +70,8 @@ public MessagingMessageConverter(MessageConverter payloadConverter) { * header mapper. */ public MessagingMessageConverter(MessageConverter payloadConverter, JmsHeaderMapper headerMapper) { - Assert.notNull(payloadConverter, "PayloadConverter must not be null"); - Assert.notNull(headerMapper, "HeaderMapper must not be null"); + Assert.notNull(payloadConverter, "'payloadConverter' must not be null"); + Assert.notNull(headerMapper, "'headerMapper' must not be null"); this.payloadConverter = payloadConverter; this.headerMapper = headerMapper; } @@ -101,11 +101,10 @@ public void afterPropertiesSet() { @Override public jakarta.jms.Message toMessage(Object object, Session session) throws JMSException, MessageConversionException { - if (!(object instanceof Message)) { + if (!(object instanceof Message input)) { throw new IllegalArgumentException("Could not convert [" + object + "] - only [" + Message.class.getName() + "] is handled by this converter"); } - Message input = (Message) object; MessageHeaders headers = input.getHeaders(); Object conversionHint = headers.get(AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER); jakarta.jms.Message reply = createMessageForPayload(input.getPayload(), session, conversionHint); diff --git a/spring-jms/src/test/java/org/springframework/jms/annotation/AbstractJmsAnnotationDrivenTests.java b/spring-jms/src/test/java/org/springframework/jms/annotation/AbstractJmsAnnotationDrivenTests.java index 0dab9e4fca85..bb3840f1a668 100644 --- a/spring-jms/src/test/java/org/springframework/jms/annotation/AbstractJmsAnnotationDrivenTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/annotation/AbstractJmsAnnotationDrivenTests.java @@ -84,8 +84,8 @@ protected void testSampleConfiguration(ApplicationContext context) { context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class); JmsListenerContainerTestFactory simpleFactory = context.getBean("simpleFactory", JmsListenerContainerTestFactory.class); - assertThat(defaultFactory.getListenerContainers().size()).isEqualTo(1); - assertThat(simpleFactory.getListenerContainers().size()).isEqualTo(1); + assertThat(defaultFactory.getListenerContainers()).hasSize(1); + assertThat(simpleFactory.getListenerContainers()).hasSize(1); } /** @@ -96,7 +96,7 @@ protected void testSampleConfiguration(ApplicationContext context) { protected void testFullConfiguration(ApplicationContext context) { JmsListenerContainerTestFactory simpleFactory = context.getBean("simpleFactory", JmsListenerContainerTestFactory.class); - assertThat(simpleFactory.getListenerContainers().size()).isEqualTo(1); + assertThat(simpleFactory.getListenerContainers()).hasSize(1); MethodJmsListenerEndpoint endpoint = (MethodJmsListenerEndpoint) simpleFactory.getListenerContainers().get(0).getEndpoint(); assertThat(endpoint.getId()).isEqualTo("listener1"); @@ -121,8 +121,8 @@ protected void testCustomConfiguration(ApplicationContext context) { context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class); JmsListenerContainerTestFactory customFactory = context.getBean("customFactory", JmsListenerContainerTestFactory.class); - assertThat(defaultFactory.getListenerContainers().size()).isEqualTo(1); - assertThat(customFactory.getListenerContainers().size()).isEqualTo(1); + assertThat(defaultFactory.getListenerContainers()).hasSize(1); + assertThat(customFactory.getListenerContainers()).hasSize(1); JmsListenerEndpoint endpoint = defaultFactory.getListenerContainers().get(0).getEndpoint(); assertThat(endpoint.getClass()).as("Wrong endpoint type").isEqualTo(SimpleJmsListenerEndpoint.class); assertThat(((SimpleJmsListenerEndpoint) endpoint).getMessageListener()).as("Wrong listener set in custom endpoint").isEqualTo(context.getBean("simpleMessageListener")); @@ -143,7 +143,7 @@ protected void testCustomConfiguration(ApplicationContext context) { protected void testExplicitContainerFactoryConfiguration(ApplicationContext context) { JmsListenerContainerTestFactory defaultFactory = context.getBean("simpleFactory", JmsListenerContainerTestFactory.class); - assertThat(defaultFactory.getListenerContainers().size()).isEqualTo(1); + assertThat(defaultFactory.getListenerContainers()).hasSize(1); } /** @@ -153,7 +153,7 @@ protected void testExplicitContainerFactoryConfiguration(ApplicationContext cont protected void testDefaultContainerFactoryConfiguration(ApplicationContext context) { JmsListenerContainerTestFactory defaultFactory = context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class); - assertThat(defaultFactory.getListenerContainers().size()).isEqualTo(1); + assertThat(defaultFactory.getListenerContainers()).hasSize(1); } /** @@ -165,7 +165,7 @@ protected void testDefaultContainerFactoryConfiguration(ApplicationContext conte protected void testJmsHandlerMethodFactoryConfiguration(ApplicationContext context) throws JMSException { JmsListenerContainerTestFactory simpleFactory = context.getBean("defaultFactory", JmsListenerContainerTestFactory.class); - assertThat(simpleFactory.getListenerContainers().size()).isEqualTo(1); + assertThat(simpleFactory.getListenerContainers()).hasSize(1); MethodJmsListenerEndpoint endpoint = (MethodJmsListenerEndpoint) simpleFactory.getListenerContainers().get(0).getEndpoint(); @@ -182,7 +182,7 @@ protected void testJmsHandlerMethodFactoryConfiguration(ApplicationContext conte protected void testJmsListenerRepeatable(ApplicationContext context) { JmsListenerContainerTestFactory simpleFactory = context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class); - assertThat(simpleFactory.getListenerContainers().size()).isEqualTo(2); + assertThat(simpleFactory.getListenerContainers()).hasSize(2); MethodJmsListenerEndpoint first = (MethodJmsListenerEndpoint) simpleFactory.getListenerContainer("first").getEndpoint(); diff --git a/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java b/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java index 1a1d61c5235d..bb7be165b816 100644 --- a/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -162,7 +162,7 @@ void composedJmsListeners() { EnableJmsDefaultContainerFactoryConfig.class, ComposedJmsListenersBean.class)) { JmsListenerContainerTestFactory simpleFactory = context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class); - assertThat(simpleFactory.getListenerContainers().size()).isEqualTo(2); + assertThat(simpleFactory.getListenerContainers()).hasSize(2); MethodJmsListenerEndpoint first = (MethodJmsListenerEndpoint) simpleFactory.getListenerContainer( "first").getEndpoint(); @@ -193,10 +193,10 @@ void lazyComponent() { EnableJmsDefaultContainerFactoryConfig.class, LazyBean.class); JmsListenerContainerTestFactory defaultFactory = context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class); - assertThat(defaultFactory.getListenerContainers().size()).isEqualTo(0); + assertThat(defaultFactory.getListenerContainers()).isEmpty(); context.getBean(LazyBean.class); // trigger lazy resolution - assertThat(defaultFactory.getListenerContainers().size()).isEqualTo(1); + assertThat(defaultFactory.getListenerContainers()).hasSize(1); MessageListenerTestContainer container = defaultFactory.getListenerContainers().get(0); assertThat(container.isStarted()).as("Should have been started " + container).isTrue(); context.close(); // close and stop the listeners diff --git a/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerEndpointRegistrarTests.java b/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerEndpointRegistrarTests.java index 40be2cabd9e8..02456b4fffc5 100644 --- a/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerEndpointRegistrarTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerEndpointRegistrarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 +73,7 @@ public void registerNullContainerFactoryIsAllowed() throws Exception { this.registrar.registerEndpoint(endpoint, null); this.registrar.afterPropertiesSet(); assertThat(this.registry.getListenerContainer("some id")).as("Container not created").isNotNull(); - assertThat(this.registry.getListenerContainers().size()).isEqualTo(1); + assertThat(this.registry.getListenerContainers()).hasSize(1); assertThat(this.registry.getListenerContainerIds().iterator().next()).isEqualTo("some id"); } @@ -96,7 +96,7 @@ public void registerContainerWithoutFactory() throws Exception { this.registrar.registerEndpoint(endpoint); this.registrar.afterPropertiesSet(); assertThat(this.registry.getListenerContainer("myEndpoint")).as("Container not created").isNotNull(); - assertThat(this.registry.getListenerContainers().size()).isEqualTo(1); + assertThat(this.registry.getListenerContainers()).hasSize(1); assertThat(this.registry.getListenerContainerIds().iterator().next()).isEqualTo("myEndpoint"); } diff --git a/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java b/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java index 160ceb125cc4..e2c5ccf92781 100644 --- a/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/core/JmsTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -237,7 +237,7 @@ void testSessionCallbackWithinSynchronizedTransaction() throws Exception { tac.close(); List synchs = TransactionSynchronizationManager.getSynchronizations(); - assertThat(synchs.size()).isEqualTo(1); + assertThat(synchs).hasSize(1); TransactionSynchronization synch = synchs.get(0); synch.beforeCommit(false); synch.beforeCompletion(); diff --git a/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java b/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java index 0fb8cb022e55..5f07ad640df2 100644 --- a/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -153,7 +153,7 @@ public void incomingMessageUsesMessageConverter() throws JMSException { listener.setMessageConverter(messageConverter); listener.onMessage(jmsMessage, session); verify(messageConverter, times(1)).fromMessage(jmsMessage); - assertThat(sample.simples.size()).isEqualTo(1); + assertThat(sample.simples).hasSize(1); assertThat(sample.simples.get(0).getPayload()).isEqualTo("FooBar"); } diff --git a/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java b/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java index d6d115da6951..bcda55eeab5b 100644 --- a/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/support/SimpleMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -78,7 +78,7 @@ public void testByteArrayConversion() throws JMSException { SimpleMessageConverter converter = new SimpleMessageConverter(); Message msg = converter.toMessage(content, session); - assertThat(((byte[]) converter.fromMessage(msg)).length).isEqualTo(content.length); + assertThat(((byte[]) converter.fromMessage(msg))).hasSize(content.length); verify(message).writeBytes(content); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/MessageHeaders.java b/spring-messaging/src/main/java/org/springframework/messaging/MessageHeaders.java index 74eb82910635..7dc660eeaf8f 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/MessageHeaders.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/MessageHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/JsonbMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/JsonbMessageConverter.java index 8da2761c2030..9125a4aea1ff 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/JsonbMessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/JsonbMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java index 6c01206abd3c..79378eb6189f 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -52,8 +52,6 @@ *

  • {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled
  • * * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Rossen Stoyanchev * @author Juergen Hoeller * @author Sebastien Deleuze diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java index 31e9b8223a0e..782915011cdd 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -144,7 +144,7 @@ protected boolean supports(Class clazz) { @Override @Nullable protected Object convertFromInternal(Message message, Class targetClass, @Nullable Object conversionHint) { - Assert.notNull(this.unmarshaller, "Property 'unmarshaller' is required"); + Assert.state(this.unmarshaller != null, "Property 'unmarshaller' is required"); try { Source source = getSource(message.getPayload()); Object result = this.unmarshaller.unmarshal(source); @@ -172,7 +172,7 @@ private Source getSource(Object payload) { protected Object convertToInternal(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) { - Assert.notNull(this.marshaller, "Property 'marshaller' is required"); + Assert.state(this.marshaller != null, "Property 'marshaller' is required"); try { if (byte[].class == getSerializedPayloadClass()) { ByteArrayOutputStream out = new ByteArrayOutputStream(1024); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java index 06d67a8931a9..cdfed1ff7c10 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java @@ -34,12 +34,13 @@ /** * {@link ReflectiveProcessor} implementation for {@link MessageMapping} - * annotated types. On top of registering reflection hints for invoking + * annotated types. In addition to registering reflection hints for invoking * the annotated method, this implementation handles: + * *

      - *
    • Return types.
    • - *
    • Parameters identified as potential payload.
    • - *
    • {@link Message} parameters.
    • + *
    • Return types
    • + *
    • Parameters identified as potential payloads
    • + *
    • {@link Message} parameters
    • *
    * * @author Sebastien Deleuze @@ -49,6 +50,7 @@ class MessageMappingReflectiveProcessor implements ReflectiveProcessor { private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + @Override public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) { if (element instanceof Class type) { @@ -99,6 +101,8 @@ protected void registerReturnValueHints(ReflectionHints hints, Method method) { @Nullable protected Type getMessageType(MethodParameter parameter) { MethodParameter nestedParameter = parameter.nested(); - return (nestedParameter.getNestedParameterType() == nestedParameter.getParameterType() ? null : nestedParameter.getNestedParameterType()); + return (nestedParameter.getNestedParameterType() == nestedParameter.getParameterType() ? + null : nestedParameter.getNestedParameterType()); } + } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodReturnValueHandlerComposite.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodReturnValueHandlerComposite.java index ba9479ec1066..5e3ed7e3a8a0 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodReturnValueHandlerComposite.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodReturnValueHandlerComposite.java @@ -129,17 +129,18 @@ public void handleReturnValue(@Nullable Object returnValue, MethodParameter retu @Override public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) { HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType); - return (handler instanceof AsyncHandlerMethodReturnValueHandler && - ((AsyncHandlerMethodReturnValueHandler) handler).isAsyncReturnValue(returnValue, returnType)); + return (handler instanceof AsyncHandlerMethodReturnValueHandler asyncHandler && + asyncHandler.isAsyncReturnValue(returnValue, returnType)); } @Override @Nullable public CompletableFuture toCompletableFuture(Object returnValue, MethodParameter returnType) { HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType); - if (handler instanceof AsyncHandlerMethodReturnValueHandler) { - return ((AsyncHandlerMethodReturnValueHandler) handler).toCompletableFuture(returnValue, returnType); + if (handler instanceof AsyncHandlerMethodReturnValueHandler asyncHandler) { + return asyncHandler.toCompletableFuture(returnValue, returnType); } return null; } + } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java index 2e1fdb201b15..2ab464277473 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java @@ -170,8 +170,8 @@ protected Object doInvoke(Object... args) throws Exception { } catch (IllegalArgumentException ex) { assertTargetBean(getBridgedMethod(), getBean(), args); - String text = (ex.getMessage() == null || ex.getCause() instanceof NullPointerException) - ? "Illegal argument": ex.getMessage(); + String text = (ex.getMessage() == null || ex.getCause() instanceof NullPointerException) ? + "Illegal argument": ex.getMessage(); throw new IllegalStateException(formatInvokeError(text, args), ex); } catch (InvocationTargetException ex) { diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/AbstractMethodMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/AbstractMethodMessageHandler.java index 4e731cda82aa..0ce87e47b177 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/AbstractMethodMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/AbstractMethodMessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -146,7 +146,7 @@ public void setHandlers(List handlers) { * Configure custom resolvers for handler method arguments. */ public void setArgumentResolverConfigurer(ArgumentResolverConfigurer configurer) { - Assert.notNull(configurer, "HandlerMethodArgumentResolver is required"); + Assert.notNull(configurer, "ArgumentResolverConfigurer is required"); this.argumentResolverConfigurer = configurer; } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ArgumentResolverConfigurer.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ArgumentResolverConfigurer.java index 9b6d71d84bb9..6c1b8de2f38e 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ArgumentResolverConfigurer.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ArgumentResolverConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -36,11 +36,11 @@ public class ArgumentResolverConfigurer { /** * Configure resolvers for custom handler method arguments. - * @param resolver the resolvers to add + * @param resolvers the resolvers to add */ - public void addCustomResolver(HandlerMethodArgumentResolver... resolver) { - Assert.notNull(resolver, "'resolvers' must not be null"); - this.customResolvers.addAll(Arrays.asList(resolver)); + public void addCustomResolver(HandlerMethodArgumentResolver... resolvers) { + Assert.notNull(resolvers, "'resolvers' must not be null"); + this.customResolvers.addAll(Arrays.asList(resolvers)); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ChannelSendOperator.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ChannelSendOperator.java index 108d14929906..0fa659141b4f 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ChannelSendOperator.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/ChannelSendOperator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -343,8 +343,8 @@ public void cancel() { private void releaseCachedItem() { synchronized (this) { Object item = this.item; - if (item instanceof DataBuffer) { - DataBufferUtils.release((DataBuffer) item); + if (item instanceof DataBuffer dataBuffer) { + DataBufferUtils.release(dataBuffer); } this.item = null; } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java index 1762536eec1e..d8403afc979c 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -171,7 +171,8 @@ public RequestSpec data(Object producer, Class elementClass) { Assert.notNull(producer, "'producer' must not be null"); Assert.notNull(elementClass, "'elementClass' must not be null"); ReactiveAdapter adapter = getAdapter(producer.getClass()); - Assert.notNull(adapter, "'producer' type is unknown to ReactiveAdapterRegistry"); + Assert.notNull(adapter, () -> "'producer' type is unknown to ReactiveAdapterRegistry: " + + producer.getClass().getName()); createPayload(adapter.toPublisher(producer), ResolvableType.forClass(elementClass)); return this; } @@ -186,7 +187,8 @@ public RequestSpec data(Object producer, ParameterizedTypeReference elementTy Assert.notNull(producer, "'producer' must not be null"); Assert.notNull(elementTypeRef, "'elementTypeRef' must not be null"); ReactiveAdapter adapter = getAdapter(producer.getClass()); - Assert.notNull(adapter, "'producer' type is unknown to ReactiveAdapterRegistry"); + Assert.notNull(adapter, () -> "'producer' type is unknown to ReactiveAdapterRegistry: " + + producer.getClass().getName()); createPayload(adapter.toPublisher(producer), ResolvableType.forType(elementTypeRef)); return this; } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/MetadataEncoder.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/MetadataEncoder.java index 3e54088ccb55..507745c7e127 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/MetadataEncoder.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/MetadataEncoder.java @@ -78,8 +78,8 @@ final class MetadataEncoder { this.strategies = strategies; this.isComposite = this.metadataMimeType.toString().equals( WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString()); - this.allocator = bufferFactory() instanceof NettyDataBufferFactory ? - ((NettyDataBufferFactory) bufferFactory()).getByteBufAllocator() : ByteBufAllocator.DEFAULT; + this.allocator = bufferFactory() instanceof NettyDataBufferFactory nettyDBF ? + nettyDBF.getByteBufAllocator() : ByteBufAllocator.DEFAULT; } @@ -193,7 +193,7 @@ private DataBuffer encodeEntries(List entries) { Object value = entry.value(); io.rsocket.metadata.CompositeMetadataCodec.encodeAndAddMetadata( composite, this.allocator, entry.mimeType().toString(), - value instanceof ByteBuf ? (ByteBuf) value : PayloadUtils.asByteBuf(encodeEntry(entry))); + value instanceof ByteBuf byteBuf ? byteBuf : PayloadUtils.asByteBuf(encodeEntry(entry))); }); return asDataBuffer(composite); } @@ -232,8 +232,8 @@ private DataBuffer encodeEntry(MetadataEntry entry) { @SuppressWarnings("unchecked") private DataBuffer encodeEntry(Object value, MimeType mimeType) { - if (value instanceof ByteBuf) { - return asDataBuffer((ByteBuf) value); + if (value instanceof ByteBuf byteBuf) { + return asDataBuffer(byteBuf); } ResolvableType type = ResolvableType.forInstance(value); Encoder encoder = this.strategies.encoder(type, mimeType); @@ -242,8 +242,8 @@ private DataBuffer encodeEntry(Object value, MimeType mimeType) { } private DataBuffer asDataBuffer(ByteBuf byteBuf) { - if (bufferFactory() instanceof NettyDataBufferFactory) { - return ((NettyDataBufferFactory) bufferFactory()).wrap(byteBuf); + if (bufferFactory() instanceof NettyDataBufferFactory nettyDBF) { + return nettyDBF.wrap(byteBuf); } else { DataBuffer buffer = bufferFactory().wrap(byteBuf.nioBuffer()); @@ -257,7 +257,7 @@ private Mono> resolveAsyncMetadata() { List> valueMonos = new ArrayList<>(); this.metadataEntries.forEach(entry -> { Object v = entry.value(); - valueMonos.add(v instanceof Mono ? (Mono) v : Mono.just(v)); + valueMonos.add(v instanceof Mono mono ? mono : Mono.just(v)); }); return Mono.zip(valueMonos, values -> { List result = new ArrayList<>(values.length); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java index 84475176bebd..36576f28f905 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -94,7 +94,7 @@ public interface RSocketRequester extends Disposable { * {@code "flight.{code}"} in which case the supplied route variables are * formatted via {@code toString()} and expanded into the template. * If a formatted variable contains a "." it is replaced with the escape - * sequence "%2E" to avoid treating it as separator by the responder . + * sequence "%2E" to avoid treating it as separator by the responder. *

    If the connection is set to use composite metadata, the route is * encoded as {@code "message/x.rsocket.routing.v0"}. Otherwise, the route * is encoded according to the mime type for the connection. diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/RSocketServiceProxyFactory.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/RSocketServiceProxyFactory.java index 0e663386e3ff..f4480923f5b6 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/RSocketServiceProxyFactory.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/RSocketServiceProxyFactory.java @@ -30,8 +30,6 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.framework.ReflectiveMethodInvocation; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.core.MethodIntrospector; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.annotation.AnnotatedElementUtils; @@ -44,116 +42,36 @@ * Factory for creating a client proxy given an RSocket service interface with * {@link RSocketExchange @RSocketExchange} methods. * - *

    This class is intended to be declared as a bean in Spring configuration. + *

    To create an instance, use static methods to obtain a + * {@link Builder Builder}. * * @author Rossen Stoyanchev * @since 6.0 */ -public final class RSocketServiceProxyFactory implements InitializingBean, EmbeddedValueResolverAware { +public final class RSocketServiceProxyFactory { - @Nullable - private final BuilderInitializedFactory builderInitializedFactory; + private final RSocketRequester rsocketRequester; + + private final List argumentResolvers; @Nullable - private final BeanStyleFactory beanStyleFactory; + private final StringValueResolver embeddedValueResolver; + private final ReactiveAdapterRegistry reactiveAdapterRegistry; + + private final Duration blockTimeout; - /** - * Create an instance with the underlying RSocketRequester to perform requests with. - * @param rsocketRequester the requester to use - * @deprecated in favor of using the Builder to initialize the - * RSocketServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - public RSocketServiceProxyFactory(RSocketRequester rsocketRequester) { - this.beanStyleFactory = new BeanStyleFactory(rsocketRequester); - this.builderInitializedFactory = null; - } private RSocketServiceProxyFactory( RSocketRequester rsocketRequester, List argumentResolvers, @Nullable StringValueResolver embeddedValueResolver, ReactiveAdapterRegistry reactiveAdapterRegistry, Duration blockTimeout) { - this.beanStyleFactory = null; - this.builderInitializedFactory = new BuilderInitializedFactory( - rsocketRequester, argumentResolvers, embeddedValueResolver, reactiveAdapterRegistry, blockTimeout); - } - - - /** - * Register a custom argument resolver, invoked ahead of default resolvers. - * @param resolver the resolver to add - * @deprecated in favor of using the Builder to initialize the - * RSocketServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - public void addCustomArgumentResolver(RSocketServiceArgumentResolver resolver) { - Assert.state(this.beanStyleFactory != null, "RSocketServiceProxyFactory was created through the builder"); - this.beanStyleFactory.addCustomArgumentResolver(resolver); - } - - /** - * Set the custom argument resolvers to use, ahead of default resolvers. - * @param resolvers the resolvers to use - * @deprecated in favor of using the Builder to initialize the - * RSocketServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - public void setCustomArgumentResolvers(List resolvers) { - Assert.state(this.beanStyleFactory != null, "RSocketServiceProxyFactory was created through the builder"); - this.beanStyleFactory.setCustomArgumentResolvers(resolvers); - } - - /** - * Set the StringValueResolver to use for resolving placeholders and - * expressions in {@link RSocketExchange#value()}. - * @param resolver the resolver to use - * @deprecated in favor of using the Builder to initialize the - * RSocketServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - @Override - public void setEmbeddedValueResolver(StringValueResolver resolver) { - if (this.beanStyleFactory != null) { - this.beanStyleFactory.setEmbeddedValueResolver(resolver); - } - } - - /** - * Set the {@link ReactiveAdapterRegistry} to use to support different - * asynchronous types for RSocket service method return values. - *

    By default this is {@link ReactiveAdapterRegistry#getSharedInstance()}. - * @deprecated in favor of using the Builder to initialize the - * RSocketServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) { - Assert.state(this.beanStyleFactory != null, "RSocketServiceProxyFactory was created through the builder"); - this.beanStyleFactory.setReactiveAdapterRegistry(registry); - } - - /** - * Configure how long to wait for a response for an RSocket service method - * with a synchronous (blocking) method signature. - *

    By default this is 5 seconds. - * @param blockTimeout the timeout value - * @deprecated in favor of using the Builder to initialize the - * RSocketServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - public void setBlockTimeout(Duration blockTimeout) { - Assert.state(this.beanStyleFactory != null, "RSocketServiceProxyFactory was created through the builder"); - this.beanStyleFactory.setBlockTimeout(blockTimeout); - } - - - @Override - @Deprecated - public void afterPropertiesSet() throws Exception { - if (this.beanStyleFactory != null) { - this.beanStyleFactory.afterPropertiesSet(); - } + this.rsocketRequester = rsocketRequester; + this.argumentResolvers = argumentResolvers; + this.embeddedValueResolver = embeddedValueResolver; + this.reactiveAdapterRegistry = reactiveAdapterRegistry; + this.blockTimeout = blockTimeout; } @@ -166,15 +84,26 @@ public void afterPropertiesSet() throws Exception { * @return the created proxy */ public S createClient(Class serviceType) { - if (this.builderInitializedFactory != null) { - return this.builderInitializedFactory.createClient(serviceType); - } - else if (this.beanStyleFactory != null) { - return this.beanStyleFactory.createClient(serviceType); - } - else { - throw new IllegalStateException("Expected Builder initialized or Bean-style delegate"); - } + + List serviceMethods = + MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod).stream() + .map(method -> createRSocketServiceMethod(serviceType, method)) + .toList(); + + return ProxyFactory.getProxy(serviceType, new ServiceMethodInterceptor(serviceMethods)); + } + + private boolean isExchangeMethod(Method method) { + return AnnotatedElementUtils.hasAnnotation(method, RSocketExchange.class); + } + + private RSocketServiceMethod createRSocketServiceMethod(Class serviceType, Method method) { + Assert.notNull(this.argumentResolvers, + "No argument resolvers: afterPropertiesSet was not called"); + + return new RSocketServiceMethod( + method, serviceType, this.argumentResolvers, this.rsocketRequester, + this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout); } @@ -330,163 +259,4 @@ public Object invoke(MethodInvocation invocation) throws Throwable { } } - - /** - * Temporary class until bean-style initialization is removed. - */ - private static final class BuilderInitializedFactory { - - private final RSocketRequester rsocketRequester; - - private final List argumentResolvers; - - @Nullable - private final StringValueResolver embeddedValueResolver; - - private final ReactiveAdapterRegistry reactiveAdapterRegistry; - - private final Duration blockTimeout; - - - public BuilderInitializedFactory( - RSocketRequester rsocketRequester, List argumentResolvers, - @Nullable StringValueResolver embeddedValueResolver, - ReactiveAdapterRegistry reactiveAdapterRegistry, Duration blockTimeout) { - - this.rsocketRequester = rsocketRequester; - this.argumentResolvers = argumentResolvers; - this.embeddedValueResolver = embeddedValueResolver; - this.reactiveAdapterRegistry = reactiveAdapterRegistry; - this.blockTimeout = blockTimeout; - } - - - public S createClient(Class serviceType) { - - List serviceMethods = - MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod).stream() - .map(method -> createRSocketServiceMethod(serviceType, method)) - .toList(); - - return ProxyFactory.getProxy(serviceType, new ServiceMethodInterceptor(serviceMethods)); - } - - private boolean isExchangeMethod(Method method) { - return AnnotatedElementUtils.hasAnnotation(method, RSocketExchange.class); - } - - private RSocketServiceMethod createRSocketServiceMethod(Class serviceType, Method method) { - Assert.notNull(this.argumentResolvers, - "No argument resolvers: afterPropertiesSet was not called"); - - return new RSocketServiceMethod( - method, serviceType, this.argumentResolvers, this.rsocketRequester, - this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout); - } - } - - - /** - * Temporary class to support bean-style initialization during deprecation period. - */ - private static final class BeanStyleFactory implements InitializingBean, EmbeddedValueResolverAware { - - private final RSocketRequester rsocketRequester; - - @Nullable - private List customArgumentResolvers; - - @Nullable - private List argumentResolvers; - - @Nullable - private StringValueResolver embeddedValueResolver; - - private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); - - private Duration blockTimeout = Duration.ofSeconds(5); - - - public BeanStyleFactory(RSocketRequester rsocketRequester) { - Assert.notNull(rsocketRequester, "RSocketRequester is required"); - this.rsocketRequester = rsocketRequester; - } - - - public void addCustomArgumentResolver(RSocketServiceArgumentResolver resolver) { - if (this.customArgumentResolvers == null) { - this.customArgumentResolvers = new ArrayList<>(); - } - this.customArgumentResolvers.add(resolver); - } - - public void setCustomArgumentResolvers(List resolvers) { - this.customArgumentResolvers = new ArrayList<>(resolvers); - } - - @Override - public void setEmbeddedValueResolver(StringValueResolver resolver) { - this.embeddedValueResolver = resolver; - } - - public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) { - this.reactiveAdapterRegistry = registry; - } - - public void setBlockTimeout(Duration blockTimeout) { - this.blockTimeout = blockTimeout; - } - - - @Override - public void afterPropertiesSet() { - this.argumentResolvers = initArgumentResolvers(); - } - - private List initArgumentResolvers() { - List resolvers = new ArrayList<>(); - - // Custom - if (this.customArgumentResolvers != null) { - resolvers.addAll(this.customArgumentResolvers); - } - - // Annotation-based - resolvers.add(new PayloadArgumentResolver(this.reactiveAdapterRegistry, false)); - resolvers.add(new DestinationVariableArgumentResolver()); - - // Type-based - resolvers.add(new MetadataArgumentResolver()); - - // Fallback - resolvers.add(new PayloadArgumentResolver(this.reactiveAdapterRegistry, true)); - - return resolvers; - } - - - public S createClient(Class serviceType) { - - List serviceMethods = - MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod).stream() - .map(method -> createRSocketServiceMethod(serviceType, method)) - .toList(); - - return ProxyFactory.getProxy(serviceType, new ServiceMethodInterceptor(serviceMethods)); - } - - private boolean isExchangeMethod(Method method) { - return AnnotatedElementUtils.hasAnnotation(method, RSocketExchange.class); - } - - private RSocketServiceMethod createRSocketServiceMethod(Class serviceType, Method method) { - Assert.notNull(this.argumentResolvers, - "No argument resolvers: afterPropertiesSet was not called"); - - return new RSocketServiceMethod( - method, serviceType, this.argumentResolvers, this.rsocketRequester, - this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout); - } - } - } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/AbstractBrokerMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/AbstractBrokerMessageHandler.java index dd67e265f306..e4f7f04314d2 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/AbstractBrokerMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/AbstractBrokerMessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -130,7 +130,7 @@ public SubscribableChannel getBrokerChannel() { } /** - * Return destination prefixes prefixes to use to filter messages to forward + * Return destination prefixes to use to filter messages to forward * to the broker. Messages that have a destination and where the destination * doesn't match are ignored. *

    By default this is not set. diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java index 8cfc8c8ee306..516fa12e1f15 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java @@ -442,7 +442,7 @@ protected void startInternal() { } if (logger.isInfoEnabled()) { - logger.info("Starting \"system\" session, " + toString()); + logger.info("Starting \"system\" session, " + this); } StompHeaderAccessor accessor = StompHeaderAccessor.create(StompCommand.CONNECT); @@ -699,7 +699,7 @@ protected TcpConnection getTcpConnection() { @Override public void afterConnected(TcpConnection connection) { if (logger.isDebugEnabled()) { - logger.debug("TCP connection opened in session=" + getSessionId()); + logger.debug("TCP connection " + connection + " opened in session=" + getSessionId()); } this.tcpConnection = connection; connection.onReadInactivity(() -> { @@ -978,7 +978,7 @@ public void clearConnection() { this.tcpConnection = null; if (conn != null) { if (logger.isDebugEnabled()) { - logger.debug("Closing TCP connection in session " + this.sessionId); + logger.debug("Closing TCP connection " + conn + " in session " + this.sessionId); } conn.close(); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompDecoder.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompDecoder.java index c183803423e4..234d9917e06f 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompDecoder.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -264,9 +264,12 @@ private void readHeaders(ByteBuffer byteBuffer, StompHeaderAccessor headerAccess * "Value Encoding". */ private String unescape(String inString) { + int index = inString.indexOf('\\'); + if (index == -1) { + return inString; + } StringBuilder sb = new StringBuilder(inString.length()); int pos = 0; // position in the old string - int index = inString.indexOf('\\'); while (index >= 0) { sb.append(inString, pos, index); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/support/ErrorMessage.java b/spring-messaging/src/main/java/org/springframework/messaging/support/ErrorMessage.java index c4ae07d424a1..e097701169eb 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/support/ErrorMessage.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/support/ErrorMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-messaging/src/main/java/org/springframework/messaging/support/GenericMessage.java b/spring-messaging/src/main/java/org/springframework/messaging/support/GenericMessage.java index 88595b61c4b3..1b359e1bd179 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/support/GenericMessage.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/support/GenericMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java b/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java index 1b46f878b9af..dfe6bea5c8e4 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpConnection.java b/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpConnection.java index 87efa56f534f..12da763ab6d8 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpConnection.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNetty2TcpConnection.java @@ -81,4 +81,8 @@ public void close() { this.completionSink.tryEmitEmpty(); } + @Override + public String toString() { + return "ReactorNetty2TcpConnection[inbound=" + this.inbound + "]"; + } } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNettyTcpConnection.java b/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNettyTcpConnection.java index 36adf8e26126..9bdbc43fc561 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNettyTcpConnection.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/tcp/reactor/ReactorNettyTcpConnection.java @@ -80,4 +80,9 @@ public void close() { this.completionSink.tryEmitEmpty(); } + @Override + public String toString() { + return "ReactorNettyTcpConnection[inbound=" + this.inbound + "]"; + } + } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/MessageHeadersTests.java b/spring-messaging/src/test/java/org/springframework/messaging/MessageHeadersTests.java index 5504cd811bee..aae1eb45fa4b 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/MessageHeadersTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/MessageHeadersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -196,7 +196,7 @@ public MyMH() { } MessageHeaders headers = new MyMH(); assertThat(headers.getId().toString()).isEqualTo("00000000-0000-0000-0000-000000000001"); - assertThat(headers.size()).isEqualTo(1); + assertThat(headers).hasSize(1); } } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolverTests.java index 2e71ce8815e5..1535df80cbab 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolverTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -26,7 +26,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.messaging.Message; @@ -81,7 +81,7 @@ public void setup() throws Exception { this.paramAnnotatedRequired = new SynthesizingMethodParameter(payloadMethod, 2); this.paramWithSpelExpression = new SynthesizingMethodParameter(payloadMethod, 3); this.paramValidated = new SynthesizingMethodParameter(payloadMethod, 4); - this.paramValidated.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); + this.paramValidated.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); this.paramValidatedNotAnnotated = new SynthesizingMethodParameter(payloadMethod, 5); this.paramNotAnnotated = new SynthesizingMethodParameter(payloadMethod, 6); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethodTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethodTests.java index b71e88bf6956..9ccadce12041 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethodTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethodTests.java @@ -51,8 +51,8 @@ public void resolveArg() throws Exception { Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method(); Object value = invoke(new Handler(), method); - assertThat(getStubResolver(0).getResolvedParameters().size()).isEqualTo(1); - assertThat(getStubResolver(1).getResolvedParameters().size()).isEqualTo(1); + assertThat(getStubResolver(0).getResolvedParameters()).hasSize(1); + assertThat(getStubResolver(1).getResolvedParameters()).hasSize(1); assertThat(value).isEqualTo("99-value"); assertThat(getStubResolver(0).getResolvedParameters().get(0).getParameterName()).isEqualTo("intArg"); assertThat(getStubResolver(1).getResolvedParameters().get(0).getParameterName()).isEqualTo("stringArg"); @@ -65,8 +65,8 @@ public void resolveNoArgValue() throws Exception { Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method(); Object value = invoke(new Handler(), method); - assertThat(getStubResolver(0).getResolvedParameters().size()).isEqualTo(1); - assertThat(getStubResolver(1).getResolvedParameters().size()).isEqualTo(1); + assertThat(getStubResolver(0).getResolvedParameters()).hasSize(1); + assertThat(getStubResolver(1).getResolvedParameters()).hasSize(1); assertThat(value).isEqualTo("null-null"); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/ResolvableMethod.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/ResolvableMethod.java index 026e4a11622c..0d44a805bd9c 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/ResolvableMethod.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/ResolvableMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -39,7 +39,7 @@ import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.Factory; import org.springframework.cglib.proxy.MethodProxy; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodIntrospector; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; @@ -131,7 +131,7 @@ public class ResolvableMethod { private static final SpringObjenesis objenesis = new SpringObjenesis(); - private static final ParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + private static final ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer(); // Matches ValueConstants.DEFAULT_NONE (spring-web and spring-messaging) private static final String DEFAULT_VALUE_NONE = "\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n"; diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/reactive/InvocableHandlerMethodTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/reactive/InvocableHandlerMethodTests.java index ead73327bb90..225e793c50fe 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/reactive/InvocableHandlerMethodTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/reactive/InvocableHandlerMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -58,8 +58,8 @@ public void resolveArg() { Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method(); Object value = invokeAndBlock(new Handler(), method); - assertThat(getStubResolver(0).getResolvedParameters().size()).isEqualTo(1); - assertThat(getStubResolver(1).getResolvedParameters().size()).isEqualTo(1); + assertThat(getStubResolver(0).getResolvedParameters()).hasSize(1); + assertThat(getStubResolver(1).getResolvedParameters()).hasSize(1); assertThat(value).isEqualTo("99-value"); assertThat(getStubResolver(0).getResolvedParameters().get(0).getParameterName()).isEqualTo("intArg"); assertThat(getStubResolver(1).getResolvedParameters().get(0).getParameterName()).isEqualTo("stringArg"); @@ -72,8 +72,8 @@ public void resolveNoArgValue() { Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method(); Object value = invokeAndBlock(new Handler(), method); - assertThat(getStubResolver(0).getResolvedParameters().size()).isEqualTo(1); - assertThat(getStubResolver(1).getResolvedParameters().size()).isEqualTo(1); + assertThat(getStubResolver(0).getResolvedParameters()).hasSize(1); + assertThat(getStubResolver(1).getResolvedParameters()).hasSize(1); assertThat(value).isEqualTo("null-null"); } @@ -145,7 +145,7 @@ public void voidMethod() { Object value = invokeAndBlock(handler, method); assertThat(value).isNull(); - assertThat(getStubResolver(0).getResolvedParameters().size()).isEqualTo(1); + assertThat(getStubResolver(0).getResolvedParameters()).hasSize(1); assertThat(handler.getResult()).isEqualTo("5.25"); assertThat(getStubResolver(0).getResolvedParameters().get(0).getParameterName()).isEqualTo("amount"); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/reactive/MethodMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/reactive/MethodMessageHandlerTests.java index d651e7d35bf5..2ac2be9f3df0 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/reactive/MethodMessageHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/invocation/reactive/MethodMessageHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -66,7 +66,7 @@ public void registeredMappings() { TestMethodMessageHandler messageHandler = initMethodMessageHandler(TestController.class); Map mappings = messageHandler.getHandlerMethods(); - assertThat(mappings.keySet().size()).isEqualTo(5); + assertThat(mappings.keySet()).hasSize(5); assertThat(mappings).containsOnlyKeys( "/handleMessage", "/handleMessageWithArgument", "/handleMessageWithError", "/handleMessageMatch1", "/handleMessageMatch2"); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/Msg.java b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/Msg.java index 75488b17f90c..e953f32ce3be 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/protobuf/Msg.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/protobuf/Msg.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterTests.java index 7761e1d4a0e5..8fbeb1fac3cb 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -119,7 +119,7 @@ private void testSendFlux(Function mapper, String... assertThat(payloads).isNotNull(); if (Arrays.equals(new String[] {""}, expectedValues)) { - assertThat(payloads.size()).isEqualTo(1); + assertThat(payloads).hasSize(1); assertThat(payloads.get(0).getMetadataUtf8()).isEqualTo("toA"); assertThat(payloads.get(0).getDataUtf8()).isEqualTo(""); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpAttributesTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpAttributesTests.java index ca82639fc048..4ff81a9d235f 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpAttributesTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpAttributesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -82,7 +82,7 @@ public void removeDestructionCallback() { this.simpAttributes.registerDestructionCallback("name1", callback1); this.simpAttributes.registerDestructionCallback("name2", callback2); - assertThat(this.simpAttributes.getAttributeNames().length).isEqualTo(2); + assertThat(this.simpAttributes.getAttributeNames()).hasSize(2); } @Test diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpMessagingTemplateTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpMessagingTemplateTests.java index 2062f7ceafaa..5e52478f4c04 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpMessagingTemplateTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/SimpMessagingTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -62,7 +62,7 @@ public void convertAndSendToUser() { this.messagingTemplate.convertAndSendToUser("joe", "/queue/foo", "data"); List> messages = this.messageChannel.getMessages(); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); Message message = messages.get(0); SimpMessageHeaderAccessor headerAccessor = @@ -78,7 +78,7 @@ public void convertAndSendToUserWithEncoding() { this.messagingTemplate.convertAndSendToUser("https://joe.openid.example.org/", "/queue/foo", "data"); List> messages = this.messageChannel.getMessages(); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); SimpMessageHeaderAccessor headerAccessor = MessageHeaderAccessor.getAccessor(messages.get(0), SimpMessageHeaderAccessor.class); @@ -133,7 +133,7 @@ public void convertAndSendWithCustomDestinationPrefix() { this.messagingTemplate.convertAndSendToUser("joe", "/queue/foo", "data"); List> messages = this.messageChannel.getMessages(); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); Message message = messages.get(0); SimpMessageHeaderAccessor headerAccessor = diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistryTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistryTests.java index 18970dfbb5c5..23a396b20699 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistryTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -53,17 +53,17 @@ public void registerSubscriptionInvalidInput() { this.registry.registerSubscription(subscribeMessage(null, subsId, dest)); MultiValueMap actual = this.registry.findSubscriptions(createMessage(dest)); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(0); + assertThat(actual).isEmpty(); this.registry.registerSubscription(subscribeMessage(sessId, null, dest)); actual = this.registry.findSubscriptions(createMessage(dest)); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(0); + assertThat(actual).isEmpty(); this.registry.registerSubscription(subscribeMessage(sessId, subsId, null)); actual = this.registry.findSubscriptions(createMessage(dest)); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(0); + assertThat(actual).isEmpty(); } @Test @@ -92,7 +92,7 @@ public void registerSubscriptionOneSession() { MultiValueMap actual = this.registry.findSubscriptions(createMessage(dest)); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(1); + assertThat(actual).hasSize(1); assertThat(sort(actual.get(sessId))).isEqualTo(subscriptionIds); } @@ -107,7 +107,7 @@ public void registerSameSubscriptionTwice() { MultiValueMap actual = this.registry.findSubscriptions(createMessage(dest)); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(1); + assertThat(actual).hasSize(1); assertThat(actual.get(sessId)).containsExactly(subId); } @@ -125,7 +125,7 @@ public void registerSubscriptionMultipleSessions() { MultiValueMap actual = this.registry.findSubscriptions(createMessage(dest)); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(3); + assertThat(actual).hasSize(3); assertThat(sort(actual.get(sessIds.get(0)))).isEqualTo(subscriptionIds); assertThat(sort(actual.get(sessIds.get(1)))).isEqualTo(subscriptionIds); assertThat(sort(actual.get(sessIds.get(2)))).isEqualTo(subscriptionIds); @@ -162,7 +162,7 @@ public void registerSubscriptionsWithSimpleAndPatternDestinations() { MultiValueMap actual = this.registry.findSubscriptions(destNasdaqIbmMessage); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(1); + assertThat(actual).hasSize(1); assertThat(actual.get(sess1)).containsExactlyInAnyOrder(subs2, subs1); this.registry.registerSubscription(subscribeMessage(sess2, subs1, destNasdaqIbm)); @@ -171,7 +171,7 @@ public void registerSubscriptionsWithSimpleAndPatternDestinations() { actual = this.registry.findSubscriptions(destNasdaqIbmMessage); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(2); + assertThat(actual).hasSize(2); assertThat(actual.get(sess1)).containsExactlyInAnyOrder(subs2, subs1); assertThat(actual.get(sess2)).isEqualTo(Collections.singletonList(subs1)); @@ -179,7 +179,7 @@ public void registerSubscriptionsWithSimpleAndPatternDestinations() { actual = this.registry.findSubscriptions(destNasdaqIbmMessage); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(1); + assertThat(actual).hasSize(1); assertThat(actual.get(sess2)).isEqualTo(Collections.singletonList(subs1)); this.registry.registerSubscription(subscribeMessage(sess1, subs1, "/topic/PRICE.STOCK.*.IBM")); @@ -187,7 +187,7 @@ public void registerSubscriptionsWithSimpleAndPatternDestinations() { actual = this.registry.findSubscriptions(destNasdaqIbmMessage); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(2); + assertThat(actual).hasSize(2); assertThat(actual.get(sess1)).containsExactlyInAnyOrder(subs1, subs2); assertThat(actual.get(sess2)).isEqualTo(Collections.singletonList(subs1)); @@ -195,7 +195,7 @@ public void registerSubscriptionsWithSimpleAndPatternDestinations() { actual = this.registry.findSubscriptions(destNasdaqIbmMessage); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(2); + assertThat(actual).hasSize(2); assertThat(actual.get(sess1)).isEqualTo(Collections.singletonList(subs1)); assertThat(actual.get(sess2)).isEqualTo(Collections.singletonList(subs1)); @@ -203,14 +203,14 @@ public void registerSubscriptionsWithSimpleAndPatternDestinations() { actual = this.registry.findSubscriptions(destNasdaqIbmMessage); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(1); + assertThat(actual).hasSize(1); assertThat(actual.get(sess2)).isEqualTo(Collections.singletonList(subs1)); this.registry.unregisterSubscription(unsubscribeMessage(sess2, subs1)); actual = this.registry.findSubscriptions(destNasdaqIbmMessage); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(0); + assertThat(actual).isEmpty(); } @Test // SPR-11755 @@ -289,14 +289,14 @@ public void registerSubscriptionWithSelector() { MultiValueMap actual = this.registry.findSubscriptions(message); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(1); + assertThat(actual).hasSize(1); assertThat(actual.get(sessionId)).isEqualTo(Collections.singletonList(subscriptionId)); // Then without actual = this.registry.findSubscriptions(createMessage(destination)); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(0); + assertThat(actual).isEmpty(); } @Test @@ -316,7 +316,7 @@ public void registerSubscriptionWithSelectorNotSupported() { MultiValueMap actual = this.registry.findSubscriptions(message); assertThat(actual).isNotNull(); - assertThat(actual.size()).isEqualTo(1); + assertThat(actual).hasSize(1); assertThat(actual.get(sessionId)).isEqualTo(Collections.singletonList(subscriptionId)); } @@ -408,7 +408,7 @@ public void findSubscriptionsReturnsMapSafeToIterate() throws Exception { MultiValueMap subscriptions = this.registry.findSubscriptions(createMessage("/foo")); assertThat(subscriptions).isNotNull(); - assertThat(subscriptions.size()).isEqualTo(2); + assertThat(subscriptions).hasSize(2); Iterator>> iterator = subscriptions.entrySet().iterator(); iterator.next(); @@ -426,7 +426,7 @@ public void findSubscriptionsReturnsMapSafeToIterateIncludingValues() throws Exc MultiValueMap allSubscriptions = this.registry.findSubscriptions(createMessage("/foo")); assertThat(allSubscriptions).isNotNull(); - assertThat(allSubscriptions.size()).isEqualTo(1); + assertThat(allSubscriptions).hasSize(1); Iterator iteratorValues = allSubscriptions.get("sess1").iterator(); iteratorValues.next(); @@ -443,14 +443,14 @@ public void cacheLimitExceeded() throws Exception { this.registry.registerSubscription(subscribeMessage("sess1", "1", "/foo")); this.registry.registerSubscription(subscribeMessage("sess1", "2", "/bar")); - assertThat(this.registry.findSubscriptions(createMessage("/foo")).size()).isEqualTo(1); - assertThat(this.registry.findSubscriptions(createMessage("/bar")).size()).isEqualTo(1); + assertThat(this.registry.findSubscriptions(createMessage("/foo"))).hasSize(1); + assertThat(this.registry.findSubscriptions(createMessage("/bar"))).hasSize(1); this.registry.registerSubscription(subscribeMessage("sess2", "1", "/foo")); this.registry.registerSubscription(subscribeMessage("sess2", "2", "/bar")); - assertThat(this.registry.findSubscriptions(createMessage("/foo")).size()).isEqualTo(2); - assertThat(this.registry.findSubscriptions(createMessage("/bar")).size()).isEqualTo(2); + assertThat(this.registry.findSubscriptions(createMessage("/foo"))).hasSize(2); + assertThat(this.registry.findSubscriptions(createMessage("/bar"))).hasSize(2); } private Message createMessage(String destination) { diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandlerTests.java index eba17ef3e7fd..1e15eeaaf687 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandlerTests.java @@ -230,7 +230,7 @@ public void readInactivity() throws Exception { verify(this.clientOutChannel, atLeast(2)).send(this.messageCaptor.capture()); List> messages = this.messageCaptor.getAllValues(); - assertThat(messages.size()).isEqualTo(2); + assertThat(messages).hasSize(2); MessageHeaders headers = messages.get(0).getHeaders(); assertThat(headers.get(SimpMessageHeaderAccessor.MESSAGE_TYPE_HEADER)).isEqualTo(SimpMessageType.CONNECT_ACK); @@ -261,7 +261,7 @@ public void writeInactivity() throws Exception { verify(this.clientOutChannel, times(2)).send(this.messageCaptor.capture()); List> messages = this.messageCaptor.getAllValues(); - assertThat(messages.size()).isEqualTo(2); + assertThat(messages).hasSize(2); MessageHeaders headers = messages.get(0).getHeaders(); assertThat(headers.get(SimpMessageHeaderAccessor.MESSAGE_TYPE_HEADER)).isEqualTo(SimpMessageType.CONNECT_ACK); @@ -292,7 +292,7 @@ public void readWriteIntervalCalculation() throws Exception { verify(this.clientOutChannel, times(1)).send(this.messageCaptor.capture()); List> messages = this.messageCaptor.getAllValues(); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); assertThat(messages.get(0).getHeaders().get(SimpMessageHeaderAccessor.MESSAGE_TYPE_HEADER)).isEqualTo(SimpMessageType.CONNECT_ACK); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java index 3110f6b9eec0..89b7d6d6a07a 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java @@ -97,7 +97,7 @@ public void clientInboundChannel() { TestChannel channel = context.getBean("clientInboundChannel", TestChannel.class); Set handlers = channel.getSubscribers(); - assertThat(handlers.size()).isEqualTo(3); + assertThat(handlers).hasSize(3); assertThat(handlers.contains(context.getBean(SimpAnnotationMethodMessageHandler.class))).isTrue(); assertThat(handlers.contains(context.getBean(UserDestinationMessageHandler.class))).isTrue(); assertThat(handlers.contains(context.getBean(SimpleBrokerMessageHandler.class))).isTrue(); @@ -110,7 +110,7 @@ public void clientInboundChannelWithBrokerRelay() { TestChannel channel = context.getBean("clientInboundChannel", TestChannel.class); Set handlers = channel.getSubscribers(); - assertThat(handlers.size()).isEqualTo(3); + assertThat(handlers).hasSize(3); assertThat(handlers.contains(context.getBean(SimpAnnotationMethodMessageHandler.class))).isTrue(); assertThat(handlers.contains(context.getBean(UserDestinationMessageHandler.class))).isTrue(); assertThat(handlers.contains(context.getBean(StompBrokerRelayMessageHandler.class))).isTrue(); @@ -122,7 +122,7 @@ public void clientInboundChannelCustomized() { AbstractSubscribableChannel channel = context.getBean( "clientInboundChannel", AbstractSubscribableChannel.class); - assertThat(channel.getInterceptors().size()).isEqualTo(3); + assertThat(channel.getInterceptors()).hasSize(3); CustomThreadPoolTaskExecutor taskExecutor = context.getBean( "clientInboundChannelExecutor", CustomThreadPoolTaskExecutor.class); @@ -196,7 +196,7 @@ public void clientOutboundChannelCustomized() { AbstractSubscribableChannel channel = context.getBean( "clientOutboundChannel", AbstractSubscribableChannel.class); - assertThat(channel.getInterceptors().size()).isEqualTo(4); + assertThat(channel.getInterceptors()).hasSize(4); ThreadPoolTaskExecutor taskExecutor = context.getBean( "clientOutboundChannelExecutor", ThreadPoolTaskExecutor.class); @@ -217,7 +217,7 @@ public void brokerChannel() { TestChannel channel = context.getBean("brokerChannel", TestChannel.class); Set handlers = channel.getSubscribers(); - assertThat(handlers.size()).isEqualTo(2); + assertThat(handlers).hasSize(2); assertThat(handlers.contains(context.getBean(UserDestinationMessageHandler.class))).isTrue(); assertThat(handlers.contains(context.getBean(SimpleBrokerMessageHandler.class))).isTrue(); @@ -231,7 +231,7 @@ public void brokerChannelWithBrokerRelay() { TestChannel channel = context.getBean("brokerChannel", TestChannel.class); Set handlers = channel.getSubscribers(); - assertThat(handlers.size()).isEqualTo(2); + assertThat(handlers).hasSize(2); assertThat(handlers.contains(context.getBean(UserDestinationMessageHandler.class))).isTrue(); assertThat(handlers.contains(context.getBean(StompBrokerRelayMessageHandler.class))).isTrue(); } @@ -267,7 +267,7 @@ public void brokerChannelCustomized() { AbstractSubscribableChannel channel = context.getBean( "brokerChannel", AbstractSubscribableChannel.class); - assertThat(channel.getInterceptors().size()).isEqualTo(4); + assertThat(channel.getInterceptors()).hasSize(4); ThreadPoolTaskExecutor taskExecutor = context.getBean( "brokerChannelExecutor", ThreadPoolTaskExecutor.class); @@ -359,11 +359,11 @@ public void customArgumentAndReturnValueTypes() { context.getBean(SimpAnnotationMethodMessageHandler.class); List customResolvers = handler.getCustomArgumentResolvers(); - assertThat(customResolvers.size()).isEqualTo(1); + assertThat(customResolvers).hasSize(1); assertThat(handler.getArgumentResolvers().contains(customResolvers.get(0))).isTrue(); List customHandlers = handler.getCustomReturnValueHandlers(); - assertThat(customHandlers.size()).isEqualTo(1); + assertThat(customHandlers).hasSize(1); assertThat(handler.getReturnValueHandlers().contains(customHandlers.get(0))).isTrue(); } @@ -461,7 +461,7 @@ public void userBroadcasts() { StompBrokerRelayMessageHandler relay = context.getBean(StompBrokerRelayMessageHandler.class); assertThat(relay.getSystemSubscriptions()).isNotNull(); - assertThat(relay.getSystemSubscriptions().size()).isEqualTo(2); + assertThat(relay.getSystemSubscriptions()).hasSize(2); assertThat(relay.getSystemSubscriptions().get("/topic/unresolved-user-destination")).isSameAs(handler1); assertThat(relay.getSystemSubscriptions().get("/topic/simp-user-registry")).isSameAs(handler2); } @@ -517,7 +517,7 @@ private void testDotSeparator(ApplicationContext context, boolean expectLeadingS message = MessageBuilder.createMessage("123".getBytes(), headers.getMessageHeaders()); inChannel.send(message); - assertThat(outChannel.messages.size()).isEqualTo(2); + assertThat(outChannel.messages).hasSize(2); Message outputMessage = outChannel.messages.remove(1); headers = StompHeaderAccessor.wrap(outputMessage); @@ -534,7 +534,7 @@ private void testDotSeparator(ApplicationContext context, boolean expectLeadingS accessor.setSessionId("sess1"); template.convertAndSendToUser("sess1", "queue.q1", "456".getBytes(), accessor.getMessageHeaders()); - assertThat(outChannel.messages.size()).isEqualTo(1); + assertThat(outChannel.messages).hasSize(1); outputMessage = outChannel.messages.remove(0); headers = StompHeaderAccessor.wrap(outputMessage); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/BufferingStompDecoderTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/BufferingStompDecoderTests.java index 80d20b8a24f2..717610340b78 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/BufferingStompDecoderTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/BufferingStompDecoderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -45,7 +45,7 @@ public void basic() throws InterruptedException { String chunk = "SEND\na:alpha\n\nMessage body\0"; List> messages = stompDecoder.decode(toByteBuffer(chunk)); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); assertThat(new String(messages.get(0).getPayload())).isEqualTo("Message body"); assertThat(stompDecoder.getBufferSize()).isEqualTo(0); @@ -62,7 +62,7 @@ public void oneMessageInTwoChunks() throws InterruptedException { assertThat(messages).isEqualTo(Collections.>emptyList()); messages = stompDecoder.decode(toByteBuffer(chunk2)); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); assertThat(new String(messages.get(0).getPayload())).isEqualTo("Message body"); assertThat(stompDecoder.getBufferSize()).isEqualTo(0); @@ -75,7 +75,7 @@ public void twoMessagesInOneChunk() throws InterruptedException { String chunk = "SEND\na:alpha\n\nPayload1\0" + "SEND\na:alpha\n\nPayload2\0"; List> messages = stompDecoder.decode(toByteBuffer(chunk)); - assertThat(messages.size()).isEqualTo(2); + assertThat(messages).hasSize(2); assertThat(new String(messages.get(0).getPayload())).isEqualTo("Payload1"); assertThat(new String(messages.get(1).getPayload())).isEqualTo("Payload2"); @@ -90,7 +90,7 @@ public void oneFullAndOneSplitMessageContentLength() throws InterruptedException String chunk1 = "SEND\na:alpha\n\nPayload1\0SEND\ncontent-length:" + contentLength + "\n"; List> messages = stompDecoder.decode(toByteBuffer(chunk1)); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); assertThat(new String(messages.get(0).getPayload())).isEqualTo("Payload1"); assertThat(stompDecoder.getBufferSize()).isEqualTo(23); @@ -99,14 +99,14 @@ public void oneFullAndOneSplitMessageContentLength() throws InterruptedException String chunk2 = "\nPayload2a"; messages = stompDecoder.decode(toByteBuffer(chunk2)); - assertThat(messages.size()).isEqualTo(0); + assertThat(messages).isEmpty(); assertThat(stompDecoder.getBufferSize()).isEqualTo(33); assertThat((int) stompDecoder.getExpectedContentLength()).isEqualTo(contentLength); String chunk3 = "-Payload2b\0"; messages = stompDecoder.decode(toByteBuffer(chunk3)); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); assertThat(new String(messages.get(0).getPayload())).isEqualTo("Payload2a-Payload2b"); assertThat(stompDecoder.getBufferSize()).isEqualTo(0); assertThat(stompDecoder.getExpectedContentLength()).isNull(); @@ -118,7 +118,7 @@ public void oneFullAndOneSplitMessageNoContentLength() throws InterruptedExcepti String chunk1 = "SEND\na:alpha\n\nPayload1\0SEND\na:alpha\n"; List> messages = stompDecoder.decode(toByteBuffer(chunk1)); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); assertThat(new String(messages.get(0).getPayload())).isEqualTo("Payload1"); assertThat(stompDecoder.getBufferSize()).isEqualTo(13); @@ -127,14 +127,14 @@ public void oneFullAndOneSplitMessageNoContentLength() throws InterruptedExcepti String chunk2 = "\nPayload2a"; messages = stompDecoder.decode(toByteBuffer(chunk2)); - assertThat(messages.size()).isEqualTo(0); + assertThat(messages).isEmpty(); assertThat(stompDecoder.getBufferSize()).isEqualTo(23); assertThat(stompDecoder.getExpectedContentLength()).isNull(); String chunk3 = "-Payload2b\0"; messages = stompDecoder.decode(toByteBuffer(chunk3)); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); assertThat(new String(messages.get(0).getPayload())).isEqualTo("Payload2a-Payload2b"); assertThat(stompDecoder.getBufferSize()).isEqualTo(0); assertThat(stompDecoder.getExpectedContentLength()).isNull(); @@ -171,7 +171,7 @@ public void incompleteCommand() { String chunk = "MESSAG"; List> messages = stompDecoder.decode(toByteBuffer(chunk)); - assertThat(messages.size()).isEqualTo(0); + assertThat(messages).isEmpty(); } // SPR-13416 @@ -182,7 +182,7 @@ public void incompleteHeaderWithPartialEscapeSequence() throws Exception { String chunk = "SEND\na:long\\"; List> messages = stompDecoder.decode(toByteBuffer(chunk)); - assertThat(messages.size()).isEqualTo(0); + assertThat(messages).isEmpty(); } @Test diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/DefaultStompSessionTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/DefaultStompSessionTests.java index d4b0dbc9e69d..4f34f2f75325 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/DefaultStompSessionTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/DefaultStompSessionTests.java @@ -597,7 +597,7 @@ public void receiptReceived() { assertThat(received.get()).isNotNull(); assertThat(received.get()).isTrue(); assertThat(receivedHeaders.get()).isNotNull(); - assertThat(receivedHeaders.get().get("foo").size()).isEqualTo(1); + assertThat(receivedHeaders.get().get("foo")).hasSize(1); assertThat(receivedHeaders.get().get("foo").get(0)).isEqualTo("bar"); } @@ -628,7 +628,7 @@ public void receiptReceivedBeforeTaskAdded() { assertThat(received.get()).isNotNull(); assertThat(received.get()).isTrue(); assertThat(receivedHeaders.get()).isNotNull(); - assertThat(receivedHeaders.get().get("foo").size()).isEqualTo(1); + assertThat(receivedHeaders.get().get("foo")).hasSize(1); assertThat(receivedHeaders.get().get("foo").get(0)).isEqualTo("bar"); } @@ -688,7 +688,7 @@ public void disconnectWithHeaders() { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); headers = StompHeaders.readOnlyStompHeaders(accessor.getNativeHeaders()); assertThat(headers.size()).as(headers.toString()).isEqualTo(1); - assertThat(headers.get("foo").size()).isEqualTo(1); + assertThat(headers.get("foo")).hasSize(1); assertThat(headers.get("foo").get(0)).isEqualTo("bar"); assertThat(this.session.isConnected()).isFalse(); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNetty2StompBrokerRelayIntegrationTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNetty2StompBrokerRelayIntegrationTests.java index f1c4702e7454..cdadff8ae7b7 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNetty2StompBrokerRelayIntegrationTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/ReactorNetty2StompBrokerRelayIntegrationTests.java @@ -16,8 +16,6 @@ package org.springframework.messaging.simp.stomp; -import org.junit.jupiter.api.Disabled; - import org.springframework.messaging.tcp.TcpOperations; import org.springframework.messaging.tcp.reactor.ReactorNetty2TcpClient; @@ -27,7 +25,6 @@ * * @author Rossen Stoyanchev */ -@Disabled("gh-29287 :: Disabled because they fail too frequently") public class ReactorNetty2StompBrokerRelayIntegrationTests extends AbstractStompBrokerRelayIntegrationTests { @Override diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java index a544d0e66912..f9a7f968c58e 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java @@ -92,7 +92,7 @@ void virtualHost() { this.brokerRelay.start(); this.brokerRelay.handleMessage(connectMessage("sess1", "joe")); - assertThat(this.tcpClient.getSentMessages().size()).isEqualTo(2); + assertThat(this.tcpClient.getSentMessages()).hasSize(2); StompHeaderAccessor headers1 = this.tcpClient.getSentHeaders(0); assertThat(headers1.getCommand()).isEqualTo(StompCommand.CONNECT); @@ -116,7 +116,7 @@ void loginAndPasscode() { this.brokerRelay.start(); this.brokerRelay.handleMessage(connectMessage("sess1", "joe")); - assertThat(this.tcpClient.getSentMessages().size()).isEqualTo(2); + assertThat(this.tcpClient.getSentMessages()).hasSize(2); StompHeaderAccessor headers1 = this.tcpClient.getSentHeaders(0); assertThat(headers1.getCommand()).isEqualTo(StompCommand.CONNECT); @@ -143,7 +143,7 @@ void destinationExcluded() { accessor.setDestination("/user/daisy/foo"); this.brokerRelay.handleMessage(MessageBuilder.createMessage(new byte[0], accessor.getMessageHeaders())); - assertThat(this.tcpClient.getSentMessages().size()).isEqualTo(2); + assertThat(this.tcpClient.getSentMessages()).hasSize(2); StompHeaderAccessor headers = this.tcpClient.getSentHeaders(0); assertThat(headers.getCommand()).isEqualTo(StompCommand.CONNECT); assertThat(headers.getSessionId()).isEqualTo(StompBrokerRelayMessageHandler.SYSTEM_SESSION_ID); @@ -174,7 +174,7 @@ void destinationExcludedWithHeartbeat() { accessor.setDestination("/user/daisy/foo"); this.brokerRelay.handleMessage(MessageBuilder.createMessage(new byte[0], accessor.getMessageHeaders())); - assertThat(this.tcpClient.getSentMessages().size()).isEqualTo(3); + assertThat(this.tcpClient.getSentMessages()).hasSize(3); assertThat(this.tcpClient.getSentHeaders(2).getMessageType()).isEqualTo(SimpMessageType.HEARTBEAT); } @@ -184,7 +184,7 @@ void messageFromBrokerIsEnriched() { this.brokerRelay.start(); this.brokerRelay.handleMessage(connectMessage("sess1", "joe")); - assertThat(this.tcpClient.getSentMessages().size()).isEqualTo(2); + assertThat(this.tcpClient.getSentMessages()).hasSize(2); assertThat(this.tcpClient.getSentHeaders(0).getCommand()).isEqualTo(StompCommand.CONNECT); assertThat(this.tcpClient.getSentHeaders(1).getCommand()).isEqualTo(StompCommand.CONNECT); @@ -247,7 +247,7 @@ void systemSubscription() { MessageHeaders headers = accessor.getMessageHeaders(); this.tcpClient.handleMessage(MessageBuilder.createMessage(new byte[0], headers)); - assertThat(this.tcpClient.getSentMessages().size()).isEqualTo(2); + assertThat(this.tcpClient.getSentMessages()).hasSize(2); assertThat(this.tcpClient.getSentHeaders(0).getCommand()).isEqualTo(StompCommand.CONNECT); assertThat(this.tcpClient.getSentHeaders(1).getCommand()).isEqualTo(StompCommand.SUBSCRIBE); assertThat(this.tcpClient.getSentHeaders(1).getDestination()).isEqualTo("/topic/foo"); @@ -268,7 +268,7 @@ void alreadyConnected() { Message connect = connectMessage("sess1", "joe"); this.brokerRelay.handleMessage(connect); - assertThat(this.tcpClient.getSentMessages().size()).isEqualTo(2); + assertThat(this.tcpClient.getSentMessages()).hasSize(2); StompHeaderAccessor headers1 = this.tcpClient.getSentHeaders(0); assertThat(headers1.getCommand()).isEqualTo(StompCommand.CONNECT); @@ -280,7 +280,7 @@ void alreadyConnected() { this.brokerRelay.handleMessage(connect); - assertThat(this.tcpClient.getSentMessages().size()).isEqualTo(2); + assertThat(this.tcpClient.getSentMessages()).hasSize(2); assertThat(this.outboundChannel.getMessages()).isEmpty(); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompDecoderTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompDecoderTests.java index d8cb23d02db3..3380ec4b2c9d 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompDecoderTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompDecoderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -45,8 +45,8 @@ public void decodeFrameWithCrLfEols() { StompHeaderAccessor headers = StompHeaderAccessor.wrap(frame); assertThat(headers.getCommand()).isEqualTo(StompCommand.DISCONNECT); - assertThat(headers.toNativeHeaderMap().size()).isEqualTo(0); - assertThat(frame.getPayload().length).isEqualTo(0); + assertThat(headers.toNativeHeaderMap()).isEmpty(); + assertThat(frame.getPayload()).isEmpty(); } @Test @@ -55,8 +55,8 @@ public void decodeFrameWithNoHeadersAndNoBody() { StompHeaderAccessor headers = StompHeaderAccessor.wrap(frame); assertThat(headers.getCommand()).isEqualTo(StompCommand.DISCONNECT); - assertThat(headers.toNativeHeaderMap().size()).isEqualTo(0); - assertThat(frame.getPayload().length).isEqualTo(0); + assertThat(headers.toNativeHeaderMap()).isEmpty(); + assertThat(frame.getPayload()).isEmpty(); } @Test @@ -69,11 +69,11 @@ public void decodeFrameWithNoBody() { assertThat(headers.getCommand()).isEqualTo(StompCommand.CONNECT); - assertThat(headers.toNativeHeaderMap().size()).isEqualTo(2); + assertThat(headers.toNativeHeaderMap()).hasSize(2); assertThat(headers.getFirstNativeHeader("accept-version")).isEqualTo("1.1"); assertThat(headers.getHost()).isEqualTo("github.org"); - assertThat(frame.getPayload().length).isEqualTo(0); + assertThat(frame.getPayload()).isEmpty(); } @Test @@ -97,7 +97,7 @@ public void decodeFrameWithContentLength() { assertThat(headers.getCommand()).isEqualTo(StompCommand.SEND); - assertThat(headers.toNativeHeaderMap().size()).isEqualTo(1); + assertThat(headers.toNativeHeaderMap()).hasSize(1); assertThat(headers.getContentLength()).isEqualTo(Integer.valueOf(23)); String bodyText = new String(message.getPayload()); @@ -113,7 +113,7 @@ public void decodeFrameWithInvalidContentLength() { assertThat(headers.getCommand()).isEqualTo(StompCommand.SEND); - assertThat(headers.toNativeHeaderMap().size()).isEqualTo(1); + assertThat(headers.toNativeHeaderMap()).hasSize(1); assertThat(headers.getContentLength()).isEqualTo(Integer.valueOf(-1)); String bodyText = new String(message.getPayload()); @@ -127,7 +127,7 @@ public void decodeFrameWithContentLengthZero() { assertThat(headers.getCommand()).isEqualTo(StompCommand.SEND); - assertThat(headers.toNativeHeaderMap().size()).isEqualTo(1); + assertThat(headers.toNativeHeaderMap()).hasSize(1); assertThat(headers.getContentLength()).isEqualTo(Integer.valueOf(0)); String bodyText = new String(frame.getPayload()); @@ -141,7 +141,7 @@ public void decodeFrameWithNullOctectsInTheBody() { assertThat(headers.getCommand()).isEqualTo(StompCommand.SEND); - assertThat(headers.toNativeHeaderMap().size()).isEqualTo(1); + assertThat(headers.toNativeHeaderMap()).hasSize(1); assertThat(headers.getContentLength()).isEqualTo(Integer.valueOf(23)); String bodyText = new String(frame.getPayload()); @@ -155,7 +155,7 @@ public void decodeFrameWithEscapedHeaders() { assertThat(headers.getCommand()).isEqualTo(StompCommand.DISCONNECT); - assertThat(headers.toNativeHeaderMap().size()).isEqualTo(1); + assertThat(headers.toNativeHeaderMap()).hasSize(1); assertThat(headers.getFirstNativeHeader("a:\r\n\\b")).isEqualTo("alpha:bravo\r\n\\"); } @@ -169,11 +169,11 @@ public void decodeFrameWithHeaderWithBackslashValue() { assertThat(headers.getCommand()).isEqualTo(StompCommand.CONNECT); - assertThat(headers.toNativeHeaderMap().size()).isEqualTo(2); + assertThat(headers.toNativeHeaderMap()).hasSize(2); assertThat(headers.getFirstNativeHeader("accept-version")).isEqualTo("1.1"); assertThat(headers.getFirstNativeHeader("key")).isEqualTo("\\value"); - assertThat(frame.getPayload().length).isEqualTo(0); + assertThat(frame.getPayload()).isEmpty(); } @Test @@ -189,7 +189,7 @@ public void decodeFramesWithExtraNewLines() { final List> messages = decoder.decode(buffer); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); assertThat(StompHeaderAccessor.wrap(messages.get(0)).getCommand()).isEqualTo(StompCommand.SEND); } @@ -201,7 +201,7 @@ public void decodeMultipleFramesFromSameBuffer() { final List> messages = decoder.decode(buffer); - assertThat(messages.size()).isEqualTo(2); + assertThat(messages).hasSize(2); assertThat(StompHeaderAccessor.wrap(messages.get(0)).getCommand()).isEqualTo(StompCommand.SEND); assertThat(StompHeaderAccessor.wrap(messages.get(1)).getCommand()).isEqualTo(StompCommand.DISCONNECT); } @@ -216,11 +216,11 @@ public void decodeFrameWithHeaderWithEmptyValue() { assertThat(headers.getCommand()).isEqualTo(StompCommand.CONNECT); - assertThat(headers.toNativeHeaderMap().size()).isEqualTo(2); + assertThat(headers.toNativeHeaderMap()).hasSize(2); assertThat(headers.getFirstNativeHeader("accept-version")).isEqualTo("1.1"); assertThat(headers.getFirstNativeHeader("key")).isEqualTo(""); - assertThat(frame.getPayload().length).isEqualTo(0); + assertThat(frame.getPayload()).isEmpty(); } @Test @@ -272,7 +272,7 @@ public void decodeHeartbeat() { final List> messages = decoder.decode(buffer); - assertThat(messages.size()).isEqualTo(1); + assertThat(messages).hasSize(1); assertThat(StompHeaderAccessor.wrap(messages.get(0)).getMessageType()).isEqualTo(SimpMessageType.HEARTBEAT); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompHeaderAccessorTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompHeaderAccessorTests.java index c004e675ea1e..fc0ed9c61d31 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompHeaderAccessorTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompHeaderAccessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -123,7 +123,7 @@ public void toNativeHeadersSubscribe() { Map> actual = headers.toNativeHeaderMap(); - assertThat(actual.size()).isEqualTo(2); + assertThat(actual).hasSize(2); assertThat(actual.get(StompHeaderAccessor.STOMP_ID_HEADER).get(0)).isEqualTo("s1"); assertThat(actual.get(StompHeaderAccessor.STOMP_DESTINATION_HEADER).get(0)).isEqualTo("/d"); } @@ -135,7 +135,7 @@ public void toNativeHeadersUnsubscribe() { Map> actual = headers.toNativeHeaderMap(); - assertThat(actual.size()).isEqualTo(1); + assertThat(actual).hasSize(1); assertThat(actual.get(StompHeaderAccessor.STOMP_ID_HEADER).get(0)).isEqualTo("s1"); } @@ -193,7 +193,7 @@ public void modifyCustomNativeHeader() { headers.setNativeHeader("accountId", accountId.toLowerCase()); Map> actual = headers.toNativeHeaderMap(); - assertThat(actual.size()).isEqualTo(3); + assertThat(actual).hasSize(3); assertThat(actual.get(StompHeaderAccessor.STOMP_ID_HEADER).get(0)).isEqualTo("s1"); assertThat(actual.get(StompHeaderAccessor.STOMP_DESTINATION_HEADER).get(0)).isEqualTo("/d"); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java index 020aa2b1bdc3..4c34b2033f5e 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/DefaultUserDestinationResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -66,7 +66,7 @@ public void handleSubscribe() { UserDestinationResult actual = this.resolver.resolveDestination(message); assertThat(actual.getSourceDestination()).isEqualTo(sourceDestination); - assertThat(actual.getTargetDestinations().size()).isEqualTo(1); + assertThat(actual.getTargetDestinations()).hasSize(1); assertThat(actual.getTargetDestinations().iterator().next()).isEqualTo("/queue/foo-user123"); assertThat(actual.getSubscribeDestination()).isEqualTo(sourceDestination); assertThat(actual.getUser()).isEqualTo(user.getName()); @@ -81,7 +81,7 @@ public void handleSubscribeForDestinationWithoutLeadingSlash() { Message message = createMessage(SimpMessageType.SUBSCRIBE, user, "123", destination); UserDestinationResult actual = this.resolver.resolveDestination(message); - assertThat(actual.getTargetDestinations().size()).isEqualTo(1); + assertThat(actual.getTargetDestinations()).hasSize(1); assertThat(actual.getTargetDestinations().iterator().next()).isEqualTo("jms.queue.call-user123"); assertThat(actual.getSubscribeDestination()).isEqualTo(destination); } @@ -97,7 +97,7 @@ public void handleSubscribeOneUserMultipleSessions() { Message message = createMessage(SimpMessageType.SUBSCRIBE, user, "456", "/user/queue/foo"); UserDestinationResult actual = this.resolver.resolveDestination(message); - assertThat(actual.getTargetDestinations().size()).isEqualTo(1); + assertThat(actual.getTargetDestinations()).hasSize(1); assertThat(actual.getTargetDestinations().iterator().next()).isEqualTo("/queue/foo-user456"); } @@ -108,7 +108,7 @@ public void handleSubscribeNoUser() { UserDestinationResult actual = this.resolver.resolveDestination(message); assertThat(actual.getSourceDestination()).isEqualTo(sourceDestination); - assertThat(actual.getTargetDestinations().size()).isEqualTo(1); + assertThat(actual.getTargetDestinations()).hasSize(1); assertThat(actual.getTargetDestinations().iterator().next()).isEqualTo(("/queue/foo-user" + "123")); assertThat(actual.getSubscribeDestination()).isEqualTo(sourceDestination); assertThat(actual.getUser()).isNull(); @@ -129,7 +129,7 @@ public void handleUnsubscribe() { Message message = createMessage(SimpMessageType.UNSUBSCRIBE, user, "123", "/user/queue/foo"); UserDestinationResult actual = this.resolver.resolveDestination(message); - assertThat(actual.getTargetDestinations().size()).isEqualTo(1); + assertThat(actual.getTargetDestinations()).hasSize(1); assertThat(actual.getTargetDestinations().iterator().next()).isEqualTo("/queue/foo-user123"); } @@ -141,7 +141,7 @@ public void handleMessage() { UserDestinationResult actual = this.resolver.resolveDestination(message); assertThat(actual.getSourceDestination()).isEqualTo(sourceDestination); - assertThat(actual.getTargetDestinations().size()).isEqualTo(1); + assertThat(actual.getTargetDestinations()).hasSize(1); assertThat(actual.getTargetDestinations().iterator().next()).isEqualTo("/queue/foo-user123"); assertThat(actual.getSubscribeDestination()).isEqualTo("/user/queue/foo"); assertThat(actual.getUser()).isEqualTo(user.getName()); @@ -156,7 +156,7 @@ public void handleMessageForDestinationWithDotSeparator() { Message message = createMessage(SimpMessageType.MESSAGE, user, "123", destination); UserDestinationResult actual = this.resolver.resolveDestination(message); - assertThat(actual.getTargetDestinations().size()).isEqualTo(1); + assertThat(actual.getTargetDestinations()).hasSize(1); assertThat(actual.getTargetDestinations().iterator().next()).isEqualTo("jms.queue.call-user123"); assertThat(actual.getSubscribeDestination()).isEqualTo("/user/jms.queue.call"); } @@ -176,7 +176,7 @@ public void handleMessageToOtherUser() { UserDestinationResult actual = this.resolver.resolveDestination(message); assertThat(actual.getSourceDestination()).isEqualTo(sourceDestination); - assertThat(actual.getTargetDestinations().size()).isEqualTo(1); + assertThat(actual.getTargetDestinations()).hasSize(1); assertThat(actual.getTargetDestinations().iterator().next()).isEqualTo("/queue/foo-user456"); assertThat(actual.getSubscribeDestination()).isEqualTo("/user/queue/foo"); assertThat(actual.getUser()).isEqualTo(otherUser.getName()); @@ -195,7 +195,7 @@ public void handleMessageEncodedUserName() { Message message = createMessage(SimpMessageType.MESSAGE, new TestPrincipal("joe"), null, destination); UserDestinationResult actual = this.resolver.resolveDestination(message); - assertThat(actual.getTargetDestinations().size()).isEqualTo(1); + assertThat(actual.getTargetDestinations()).hasSize(1); assertThat(actual.getTargetDestinations().iterator().next()).isEqualTo("/queue/foo-useropenid123"); } @@ -206,7 +206,7 @@ public void handleMessageWithNoUser() { UserDestinationResult actual = this.resolver.resolveDestination(message); assertThat(actual.getSourceDestination()).isEqualTo(sourceDestination); - assertThat(actual.getTargetDestinations().size()).isEqualTo(1); + assertThat(actual.getTargetDestinations()).hasSize(1); assertThat(actual.getTargetDestinations().iterator().next()).isEqualTo("/queue/foo-user123"); assertThat(actual.getSubscribeDestination()).isEqualTo("/user/queue/foo"); assertThat(actual.getUser()).isNull(); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/MultiServerUserRegistryTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/MultiServerUserRegistryTests.java index 553c3af380c8..1c12de14f150 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/MultiServerUserRegistryTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/MultiServerUserRegistryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -78,12 +78,12 @@ void getUserFromRemoteRegistry() { SimpUser user = this.registry.getUser("joe"); assertThat(user).isNotNull(); assertThat(user.hasSessions()).isTrue(); - assertThat(user.getSessions().size()).isEqualTo(1); + assertThat(user.getSessions()).hasSize(1); SimpSession session = user.getSession("remote-sess"); assertThat(session).isNotNull(); assertThat(session.getId()).isEqualTo("remote-sess"); assertThat(session.getUser()).isSameAs(user); - assertThat(session.getSubscriptions().size()).isEqualTo(1); + assertThat(session.getSubscriptions()).hasSize(1); SimpSubscription subscription = session.getSubscriptions().iterator().next(); assertThat(subscription.getId()).isEqualTo("remote-sub"); assertThat(subscription.getSession()).isSameAs(session); @@ -115,7 +115,7 @@ void findSubscriptionsFromRemoteRegistry() { assertThat(this.registry.getUserCount()).isEqualTo(3); Set matches = this.registry.findSubscriptions(s -> s.getDestination().equals("/match")); - assertThat(matches.size()).isEqualTo(2); + assertThat(matches).hasSize(2); Iterator iterator = matches.iterator(); Set sessionIds = new HashSet<>(2); sessionIds.add(iterator.next().getSession().getId()); @@ -147,13 +147,13 @@ void getSessionsWhenUserIsConnectedToMultipleServers() { assertThat(this.registry.getUserCount()).isEqualTo(1); SimpUser user = this.registry.getUsers().iterator().next(); assertThat(user.hasSessions()).isTrue(); - assertThat(user.getSessions().size()).isEqualTo(2); + assertThat(user.getSessions()).hasSize(2); assertThat(user.getSessions()).containsExactlyInAnyOrder(localSession, remoteSession); assertThat(user.getSession("sess123")).isSameAs(localSession); assertThat(user.getSession("sess456")).isEqualTo(remoteSession); user = this.registry.getUser("joe"); - assertThat(user.getSessions().size()).isEqualTo(2); + assertThat(user.getSessions()).hasSize(2); assertThat(user.getSessions()).containsExactlyInAnyOrder(localSession, remoteSession); assertThat(user.getSession("sess123")).isSameAs(localSession); assertThat(user.getSession("sess456")).isEqualTo(remoteSession); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/support/ChannelInterceptorTests.java b/spring-messaging/src/test/java/org/springframework/messaging/support/ChannelInterceptorTests.java index b72adbcc958e..2bab41ff57fe 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/support/ChannelInterceptorTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/support/ChannelInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -60,7 +60,7 @@ public void preSendInterceptorReturningModifiedMessage() { this.channel.addInterceptor(interceptor); this.channel.send(MessageBuilder.withPayload("test").build()); - assertThat(this.messageHandler.getMessages().size()).isEqualTo(1); + assertThat(this.messageHandler.getMessages()).hasSize(1); Message result = this.messageHandler.getMessages().get(0); assertThat(result).isNotNull(); @@ -79,7 +79,7 @@ public void preSendInterceptorReturningNull() { assertThat(interceptor1.getCounter().get()).isEqualTo(1); assertThat(interceptor2.getCounter().get()).isEqualTo(1); - assertThat(this.messageHandler.getMessages().size()).isEqualTo(0); + assertThat(this.messageHandler.getMessages()).isEmpty(); assertThat(interceptor1.wasAfterCompletionInvoked()).isTrue(); assertThat(interceptor2.wasAfterCompletionInvoked()).isFalse(); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/support/MessageHeaderAccessorTests.java b/spring-messaging/src/test/java/org/springframework/messaging/support/MessageHeaderAccessorTests.java index d87a410b028b..86f08d006af5 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/support/MessageHeaderAccessorTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/support/MessageHeaderAccessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -44,7 +44,7 @@ public class MessageHeaderAccessorTests { @Test public void newEmptyHeaders() { MessageHeaderAccessor accessor = new MessageHeaderAccessor(); - assertThat(accessor.toMap().size()).isEqualTo(0); + assertThat(accessor.toMap()).isEmpty(); } @Test @@ -57,7 +57,7 @@ public void existingHeaders() throws InterruptedException { MessageHeaderAccessor accessor = new MessageHeaderAccessor(message); MessageHeaders actual = accessor.getMessageHeaders(); - assertThat(actual.size()).isEqualTo(3); + assertThat(actual).hasSize(3); assertThat(actual.get("foo")).isEqualTo("bar"); assertThat(actual.get("bar")).isEqualTo("baz"); } @@ -75,7 +75,7 @@ public void existingHeadersModification() throws InterruptedException { accessor.setHeader("foo", "BAR"); MessageHeaders actual = accessor.getMessageHeaders(); - assertThat(actual.size()).isEqualTo(3); + assertThat(actual).hasSize(3); assertThat(actual.getId()).isNotEqualTo(message.getHeaders().getId()); assertThat(actual.get("foo")).isEqualTo("BAR"); assertThat(actual.get("bar")).isEqualTo("baz"); @@ -110,7 +110,7 @@ public void removeHeaders() { accessor.removeHeaders("fo*"); MessageHeaders actual = accessor.getMessageHeaders(); - assertThat(actual.size()).isEqualTo(2); + assertThat(actual).hasSize(2); assertThat(actual.get("foo")).isNull(); assertThat(actual.get("bar")).isEqualTo("baz"); } @@ -128,7 +128,7 @@ public void copyHeaders() { accessor.copyHeaders(map2); MessageHeaders actual = accessor.getMessageHeaders(); - assertThat(actual.size()).isEqualTo(3); + assertThat(actual).hasSize(3); assertThat(actual.get("foo")).isEqualTo("BAR"); assertThat(actual.get("bar")).isEqualTo("baz"); } @@ -146,7 +146,7 @@ public void copyHeadersIfAbsent() { accessor.copyHeadersIfAbsent(map2); MessageHeaders actual = accessor.getMessageHeaders(); - assertThat(actual.size()).isEqualTo(3); + assertThat(actual).hasSize(3); assertThat(actual.get("foo")).isEqualTo("bar"); assertThat(actual.get("bar")).isEqualTo("baz"); } @@ -157,7 +157,7 @@ public void copyHeadersFromNullMap() { headers.copyHeaders(null); headers.copyHeadersIfAbsent(null); - assertThat(headers.getMessageHeaders().size()).isEqualTo(1); + assertThat(headers.getMessageHeaders()).hasSize(1); assertThat(headers.getMessageHeaders().keySet()).isEqualTo(Collections.singleton("id")); } @@ -174,9 +174,9 @@ public void toMap() { accessor.setHeader("foo", "bar3"); Map map3 = accessor.toMap(); - assertThat(map1.size()).isEqualTo(1); - assertThat(map2.size()).isEqualTo(1); - assertThat(map3.size()).isEqualTo(1); + assertThat(map1).hasSize(1); + assertThat(map2).hasSize(1); + assertThat(map3).hasSize(1); assertThat(map1.get("foo")).isEqualTo("bar1"); assertThat(map2.get("foo")).isEqualTo("bar2"); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/support/NativeMessageHeaderAccessorTests.java b/spring-messaging/src/test/java/org/springframework/messaging/support/NativeMessageHeaderAccessorTests.java index 73eba06caa45..72f37fc54fc2 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/support/NativeMessageHeaderAccessorTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/support/NativeMessageHeaderAccessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -67,7 +67,7 @@ public void createFromMessage() { NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor(message); Map actual = headerAccessor.toMap(); - assertThat(actual.size()).isEqualTo(2); + assertThat(actual).hasSize(2); assertThat(actual.get("a")).isEqualTo("b"); assertThat(actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS)).isNotNull(); assertThat(actual.get(NativeMessageHeaderAccessor.NATIVE_HEADERS)).isEqualTo(inputNativeHeaders); @@ -79,7 +79,7 @@ public void createFromMessageNull() { NativeMessageHeaderAccessor headerAccessor = new NativeMessageHeaderAccessor((Message) null); Map actual = headerAccessor.toMap(); - assertThat(actual.size()).isEqualTo(0); + assertThat(actual).isEmpty(); Map> actualNativeHeaders = headerAccessor.toNativeHeaderMap(); assertThat(actualNativeHeaders).isEqualTo(Collections.emptyMap()); @@ -104,7 +104,7 @@ public void createFromMessageAndModify() { Map actual = headerAccessor.toMap(); - assertThat(actual.size()).isEqualTo(2); + assertThat(actual).hasSize(2); assertThat(actual.get("a")).isEqualTo("B"); @SuppressWarnings("unchecked") diff --git a/spring-messaging/src/test/resources/log4j2-test.xml b/spring-messaging/src/test/resources/log4j2-test.xml index 0a2142f0d148..3f250c4a5562 100644 --- a/spring-messaging/src/test/resources/log4j2-test.xml +++ b/spring-messaging/src/test/resources/log4j2-test.xml @@ -6,14 +6,12 @@ - - + + - diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/support/HibernateDaoSupport.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/support/HibernateDaoSupport.java index a65d52555615..965334421dfd 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/support/HibernateDaoSupport.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/support/HibernateDaoSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java b/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java index f0665ec7e684..eb4967245b98 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java @@ -23,7 +23,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -73,25 +72,22 @@ public abstract class SharedEntityManagerCreator { private static final Map, Class[]> cachedQueryInterfaces = new ConcurrentReferenceHashMap<>(4); - private static final Set transactionRequiringMethods = new HashSet<>(8); - - private static final Set queryTerminatingMethods = new HashSet<>(8); - - static { - transactionRequiringMethods.add("joinTransaction"); - transactionRequiringMethods.add("flush"); - transactionRequiringMethods.add("persist"); - transactionRequiringMethods.add("merge"); - transactionRequiringMethods.add("remove"); - transactionRequiringMethods.add("refresh"); - - queryTerminatingMethods.add("execute"); // JPA 2.1 StoredProcedureQuery - queryTerminatingMethods.add("executeUpdate"); - queryTerminatingMethods.add("getSingleResult"); - queryTerminatingMethods.add("getResultStream"); - queryTerminatingMethods.add("getResultList"); - queryTerminatingMethods.add("list"); // Hibernate Query.list() method - } + private static final Set transactionRequiringMethods = Set.of( + "joinTransaction", + "flush", + "persist", + "merge", + "remove", + "refresh"); + + private static final Set queryTerminatingMethods = Set.of( + "execute", // JPA 2.1 StoredProcedureQuery + "executeUpdate", + "getSingleResult", + "getResultStream", + "getResultList", + "list" // Hibernate Query.list() method + ); /** diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesScanner.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesScanner.java index 74ab9bb8e775..48b5d11fa6d3 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesScanner.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesScanner.java @@ -58,10 +58,9 @@ public final class PersistenceManagedTypesScanner { private static final String PACKAGE_INFO_SUFFIX = ".package-info"; - private static final Set entityTypeFilters; + private static final Set entityTypeFilters = new LinkedHashSet<>(4); static { - entityTypeFilters = new LinkedHashSet<>(8); entityTypeFilters.add(new AnnotationTypeFilter(Entity.class, false)); entityTypeFilters.add(new AnnotationTypeFilter(Embeddable.class, false)); entityTypeFilters.add(new AnnotationTypeFilter(MappedSuperclass.class, false)); diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java index 1b2cd4b53e22..00a167f8d5ca 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -312,7 +312,7 @@ protected void parseJarFiles(Element persistenceUnit, SpringPersistenceUnitInfo // relative to the persistence unit root, according to the JPA spec URL rootUrl = unitInfo.getPersistenceUnitRootUrl(); if (rootUrl != null) { - unitInfo.addJarFileUrl(new URL(rootUrl, value)); + unitInfo.addJarFileUrl(ResourceUtils.toRelativeURL(rootUrl, value)); } else { logger.warn("Cannot resolve jar-file entry [" + value + "] in persistence unit '" + @@ -363,7 +363,7 @@ static URL determinePersistenceUnitRootUrl(Resource resource) throws IOException if (persistenceUnitRoot.endsWith("/")) { persistenceUnitRoot = persistenceUnitRoot.substring(0, persistenceUnitRoot.length() - 1); } - return new URL(persistenceUnitRoot); + return ResourceUtils.toURL(persistenceUnitRoot); } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/InjectionCodeGenerator.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/InjectionCodeGenerator.java index f1fb64cb0f41..1e14aaa3e3f8 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/InjectionCodeGenerator.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/InjectionCodeGenerator.java @@ -31,14 +31,15 @@ /** * Internal code generator that can inject a value into a field or single-arg * method. - *

    - * Generates code in the form:

    {@code
    + *
    + * 

    Generates code in the form: + *

    {@code
      * instance.age = value;
      * }
    or
    {@code
      * instance.setAge(value);
      * }
    - *

    - * Will also generate reflection based injection and register hints if the + * + *

    Will also generate reflection based injection and register hints if the * member is not visible. * * @author Phillip Webb @@ -52,25 +53,21 @@ class InjectionCodeGenerator { InjectionCodeGenerator(ClassName targetClassName, RuntimeHints hints) { - Assert.notNull(hints, "ClassName must not be null"); + Assert.notNull(targetClassName, "ClassName must not be null"); Assert.notNull(hints, "RuntimeHints must not be null"); this.targetClassName = targetClassName; this.hints = hints; } - CodeBlock generateInjectionCode(Member member, String instanceVariable, - CodeBlock resourceToInject) { - + CodeBlock generateInjectionCode(Member member, String instanceVariable, CodeBlock resourceToInject) { if (member instanceof Field field) { return generateFieldInjectionCode(field, instanceVariable, resourceToInject); } if (member instanceof Method method) { - return generateMethodInjectionCode(method, instanceVariable, - resourceToInject); + return generateMethodInjectionCode(method, instanceVariable, resourceToInject); } - throw new IllegalStateException( - "Unsupported member type " + member.getClass().getName()); + throw new IllegalStateException("Unsupported member type " + member.getClass().getName()); } private CodeBlock generateFieldInjectionCode(Field field, String instanceVariable, @@ -87,8 +84,7 @@ private CodeBlock generateFieldInjectionCode(Field field, String instanceVariabl "field", instanceVariable, resourceToInject); } else { - code.addStatement("$L.$L = $L", instanceVariable, field.getName(), - resourceToInject); + code.addStatement("$L.$L = $L", instanceVariable, field.getName(), resourceToInject); } return code.build(); } @@ -105,14 +101,12 @@ private CodeBlock generateMethodInjectionCode(Method method, String instanceVari code.addStatement("$T method = $T.findMethod($T.class, $S, $T.class)", Method.class, ReflectionUtils.class, method.getDeclaringClass(), method.getName(), method.getParameterTypes()[0]); - code.addStatement("$T.makeAccessible($L)", ReflectionUtils.class, - "method"); + code.addStatement("$T.makeAccessible($L)", ReflectionUtils.class, "method"); code.addStatement("$T.invokeMethod($L, $L, $L)", ReflectionUtils.class, "method", instanceVariable, resourceToInject); } else { - code.addStatement("$L.$L($L)", instanceVariable, method.getName(), - resourceToInject); + code.addStatement("$L.$L($L)", instanceVariable, method.getName(), resourceToInject); } return code.build(); } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java index fc286851271f..c0058ff74d04 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java @@ -767,21 +767,17 @@ private static class AotContribution implements BeanRegistrationAotContribution private static final String INSTANCE_PARAMETER = "instance"; - private final Class target; private final Collection injectedElements; - AotContribution(Class target, Collection injectedElements) { this.target = target; this.injectedElements = injectedElements; } - @Override - public void applyTo(GenerationContext generationContext, - BeanRegistrationCode beanRegistrationCode) { + public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { GeneratedClass generatedClass = generationContext.getGeneratedClasses() .addForFeatureComponent("PersistenceInjection", this.target, type -> { type.addJavadoc("Persistence injection for {@link $T}.", this.target); @@ -801,8 +797,8 @@ public void applyTo(GenerationContext generationContext, private CodeBlock generateMethodCode(RuntimeHints hints, GeneratedClass generatedClass) { CodeBlock.Builder code = CodeBlock.builder(); - InjectionCodeGenerator injectionCodeGenerator = new InjectionCodeGenerator( - generatedClass.getName(), hints); + InjectionCodeGenerator injectionCodeGenerator = + new InjectionCodeGenerator(generatedClass.getName(), hints); for (InjectedElement injectedElement : this.injectedElements) { CodeBlock resourceToInject = generateResourceToInjectCode(generatedClass.getMethods(), (PersistenceElement) injectedElement); @@ -814,8 +810,9 @@ private CodeBlock generateMethodCode(RuntimeHints hints, GeneratedClass generate return code.build(); } - private CodeBlock generateResourceToInjectCode(GeneratedMethods generatedMethods, - PersistenceElement injectedElement) { + private CodeBlock generateResourceToInjectCode( + GeneratedMethods generatedMethods, PersistenceElement injectedElement) { + String unitName = injectedElement.unitName; boolean requireEntityManager = (injectedElement.type != null); if (!requireEntityManager) { @@ -824,14 +821,13 @@ private CodeBlock generateResourceToInjectCode(GeneratedMethods generatedMethods EntityManagerFactoryUtils.class, ListableBeanFactory.class, REGISTERED_BEAN_PARAMETER, unitName); } - String[] methodNameParts = { "get", unitName, "EntityManager" }; + String[] methodNameParts = {"get", unitName, "EntityManager"}; GeneratedMethod generatedMethod = generatedMethods.add(methodNameParts, method -> generateGetEntityManagerMethod(method, injectedElement)); return CodeBlock.of("$L($L)", generatedMethod.getName(), REGISTERED_BEAN_PARAMETER); } - private void generateGetEntityManagerMethod(MethodSpec.Builder method, - PersistenceElement injectedElement) { + private void generateGetEntityManagerMethod(MethodSpec.Builder method, PersistenceElement injectedElement) { String unitName = injectedElement.unitName; Properties properties = injectedElement.properties; method.addJavadoc("Get the '$L' {@link $T}", @@ -849,10 +845,8 @@ private void generateGetEntityManagerMethod(MethodSpec.Builder method, if (hasProperties) { method.addStatement("$T properties = new Properties()", Properties.class); - for (String propertyName : new TreeSet<>( - properties.stringPropertyNames())) { - method.addStatement("properties.put($S, $S)", propertyName, - properties.getProperty(propertyName)); + for (String propertyName : new TreeSet<>(properties.stringPropertyNames())) { + method.addStatement("properties.put($S, $S)", propertyName, properties.getProperty(propertyName)); } } method.addStatement( @@ -861,7 +855,6 @@ private void generateGetEntityManagerMethod(MethodSpec.Builder method, (hasProperties) ? "properties" : null, injectedElement.synchronizedWithTransaction); } - } } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java index 13ec9dfe355b..654b26cc0dd0 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java @@ -149,7 +149,7 @@ public void testMultipleResults() { Query q = sharedEntityManager.createQuery("select p from Person as p"); List people = q.getResultList(); - assertThat(people.size()).isEqualTo(1); + assertThat(people).hasSize(1); assertThat(people.get(0).getFirstName()).isEqualTo(firstName); } @@ -186,7 +186,7 @@ public void testQueryNoPersons() { EntityManager em = entityManagerFactory.createEntityManager(); Query q = em.createQuery("select p from Person as p"); List people = q.getResultList(); - assertThat(people.size()).isEqualTo(0); + assertThat(people).isEmpty(); assertThatExceptionOfType(NoResultException.class).isThrownBy(q::getSingleResult); } @@ -198,7 +198,7 @@ public void testQueryNoPersonsNotTransactional() { EntityManager em = entityManagerFactory.createEntityManager(); Query q = em.createQuery("select p from Person as p"); List people = q.getResultList(); - assertThat(people.size()).isEqualTo(0); + assertThat(people).isEmpty(); assertThatExceptionOfType(NoResultException.class).isThrownBy(q::getSingleResult); } @@ -208,7 +208,7 @@ public void testQueryNoPersonsShared() { Query q = this.sharedEntityManager.createQuery("select p from Person as p"); q.setFlushMode(FlushModeType.AUTO); List people = q.getResultList(); - assertThat(people.size()).isEqualTo(0); + assertThat(people).isEmpty(); assertThatExceptionOfType(NoResultException.class).isThrownBy(q::getSingleResult); } @@ -221,7 +221,7 @@ public void testQueryNoPersonsSharedNotTransactional() { Query q = em.createQuery("select p from Person as p"); q.setFlushMode(FlushModeType.AUTO); List people = q.getResultList(); - assertThat(people.size()).isEqualTo(0); + assertThat(people).isEmpty(); assertThatException() .isThrownBy(q::getSingleResult) .withMessageContaining("closed"); diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBeanTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBeanTests.java index 3f311ed5d91a..d5cf2c1ce82c 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBeanTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/ApplicationManagedEntityManagerIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/ApplicationManagedEntityManagerIntegrationTests.java index 911db888156a..76933a62facc 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/ApplicationManagedEntityManagerIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/ApplicationManagedEntityManagerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/ContainerManagedEntityManagerIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/ContainerManagedEntityManagerIntegrationTests.java index e3acf003a387..1beff7a3b259 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/ContainerManagedEntityManagerIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/ContainerManagedEntityManagerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/EclipseLinkEntityManagerFactoryIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/EclipseLinkEntityManagerFactoryIntegrationTests.java index ed79fd301c33..5f346f519295 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/EclipseLinkEntityManagerFactoryIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/eclipselink/EclipseLinkEntityManagerFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java index d83a36d57a9f..e98ecb82f7e2 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -67,7 +67,7 @@ public void testEntityListener() { insertPerson(firstName); List people = sharedEntityManager.createQuery("select p from Person as p").getResultList(); - assertThat(people.size()).isEqualTo(1); + assertThat(people).hasSize(1); assertThat(people.get(0).getFirstName()).isEqualTo(firstName); assertThat(people.get(0).postLoaded).isSameAs(applicationContext); } @@ -80,7 +80,7 @@ public void testCurrentSession() { Query q = sessionFactory.getCurrentSession().createQuery("select p from Person as p"); List people = q.getResultList(); - assertThat(people.size()).isEqualTo(1); + assertThat(people).hasSize(1); assertThat(people.get(0).getFirstName()).isEqualTo(firstName); assertThat(people.get(0).postLoaded).isSameAs(applicationContext); } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java index 39007b999bc5..91f797d9396e 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java @@ -57,10 +57,10 @@ public void testMetaInfCase() throws Exception { PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource); assertThat(info).isNotNull(); - assertThat(info.length).isEqualTo(1); + assertThat(info).hasSize(1); assertThat(info[0].getPersistenceUnitName()).isEqualTo("OrderManagement"); - assertThat(info[0].getJarFileUrls().size()).isEqualTo(2); + assertThat(info[0].getJarFileUrls()).hasSize(2); assertThat(info[0].getJarFileUrls().get(0)).isEqualTo(new ClassPathResource("order.jar").getURL()); assertThat(info[0].getJarFileUrls().get(1)).isEqualTo(new ClassPathResource("order-supplemental.jar").getURL()); @@ -75,7 +75,7 @@ public void testExample1() throws Exception { PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource); assertThat(info).isNotNull(); - assertThat(info.length).isEqualTo(1); + assertThat(info).hasSize(1); assertThat(info[0].getPersistenceUnitName()).isEqualTo("OrderManagement"); assertThat(info[0].excludeUnlistedClasses()).as("Exclude unlisted should default false in 1.0.").isFalse(); @@ -89,13 +89,13 @@ public void testExample2() throws Exception { PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource); assertThat(info).isNotNull(); - assertThat(info.length).isEqualTo(1); + assertThat(info).hasSize(1); assertThat(info[0].getPersistenceUnitName()).isEqualTo("OrderManagement2"); - assertThat(info[0].getMappingFileNames().size()).isEqualTo(1); + assertThat(info[0].getMappingFileNames()).hasSize(1); assertThat(info[0].getMappingFileNames().get(0)).isEqualTo("mappings.xml"); - assertThat(info[0].getProperties().keySet().size()).isEqualTo(0); + assertThat(info[0].getProperties().keySet()).isEmpty(); assertThat(info[0].excludeUnlistedClasses()).as("Exclude unlisted should default false in 1.0.").isFalse(); } @@ -108,14 +108,14 @@ public void testExample3() throws Exception { PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource); assertThat(info).isNotNull(); - assertThat(info.length).isEqualTo(1); + assertThat(info).hasSize(1); assertThat(info[0].getPersistenceUnitName()).isEqualTo("OrderManagement3"); - assertThat(info[0].getJarFileUrls().size()).isEqualTo(2); + assertThat(info[0].getJarFileUrls()).hasSize(2); assertThat(info[0].getJarFileUrls().get(0)).isEqualTo(new ClassPathResource("order.jar").getURL()); assertThat(info[0].getJarFileUrls().get(1)).isEqualTo(new ClassPathResource("order-supplemental.jar").getURL()); - assertThat(info[0].getProperties().keySet().size()).isEqualTo(0); + assertThat(info[0].getProperties().keySet()).isEmpty(); assertThat(info[0].getJtaDataSource()).isNull(); assertThat(info[0].getNonJtaDataSource()).isNull(); @@ -134,13 +134,13 @@ public void testExample4() throws Exception { PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource); assertThat(info).isNotNull(); - assertThat(info.length).isEqualTo(1); + assertThat(info).hasSize(1); assertThat(info[0].getPersistenceUnitName()).isEqualTo("OrderManagement4"); - assertThat(info[0].getMappingFileNames().size()).isEqualTo(1); + assertThat(info[0].getMappingFileNames()).hasSize(1); assertThat(info[0].getMappingFileNames().get(0)).isEqualTo("order-mappings.xml"); - assertThat(info[0].getManagedClassNames().size()).isEqualTo(3); + assertThat(info[0].getManagedClassNames()).hasSize(3); assertThat(info[0].getManagedClassNames().get(0)).isEqualTo("com.acme.Order"); assertThat(info[0].getManagedClassNames().get(1)).isEqualTo("com.acme.Customer"); assertThat(info[0].getManagedClassNames().get(2)).isEqualTo("com.acme.Item"); @@ -148,7 +148,7 @@ public void testExample4() throws Exception { assertThat(info[0].excludeUnlistedClasses()).as("Exclude unlisted should be true when no value.").isTrue(); assertThat(info[0].getTransactionType()).isSameAs(PersistenceUnitTransactionType.RESOURCE_LOCAL); - assertThat(info[0].getProperties().keySet().size()).isEqualTo(0); + assertThat(info[0].getProperties().keySet()).isEmpty(); builder.clear(); } @@ -161,19 +161,19 @@ public void testExample5() throws Exception { PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource); assertThat(info).isNotNull(); - assertThat(info.length).isEqualTo(1); + assertThat(info).hasSize(1); assertThat(info[0].getPersistenceUnitName()).isEqualTo("OrderManagement5"); - assertThat(info[0].getMappingFileNames().size()).isEqualTo(2); + assertThat(info[0].getMappingFileNames()).hasSize(2); assertThat(info[0].getMappingFileNames().get(0)).isEqualTo("order1.xml"); assertThat(info[0].getMappingFileNames().get(1)).isEqualTo("order2.xml"); - assertThat(info[0].getJarFileUrls().size()).isEqualTo(2); + assertThat(info[0].getJarFileUrls()).hasSize(2); assertThat(info[0].getJarFileUrls().get(0)).isEqualTo(new ClassPathResource("order.jar").getURL()); assertThat(info[0].getJarFileUrls().get(1)).isEqualTo(new ClassPathResource("order-supplemental.jar").getURL()); assertThat(info[0].getPersistenceProviderClassName()).isEqualTo("com.acme.AcmePersistence"); - assertThat(info[0].getProperties().keySet().size()).isEqualTo(0); + assertThat(info[0].getProperties().keySet()).isEmpty(); assertThat(info[0].excludeUnlistedClasses()).as("Exclude unlisted should default false in 1.0.").isFalse(); } @@ -192,7 +192,7 @@ public void testExampleComplex() throws Exception { new PathMatchingResourcePatternResolver(), dataSourceLookup); PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource); - assertThat(info.length).isEqualTo(2); + assertThat(info).hasSize(2); PersistenceUnitInfo pu1 = info[0]; @@ -200,10 +200,10 @@ public void testExampleComplex() throws Exception { assertThat(pu1.getPersistenceProviderClassName()).isEqualTo("com.acme.AcmePersistence"); - assertThat(pu1.getMappingFileNames().size()).isEqualTo(1); + assertThat(pu1.getMappingFileNames()).hasSize(1); assertThat(pu1.getMappingFileNames().get(0)).isEqualTo("ormap2.xml"); - assertThat(pu1.getJarFileUrls().size()).isEqualTo(1); + assertThat(pu1.getJarFileUrls()).hasSize(1); assertThat(pu1.getJarFileUrls().get(0)).isEqualTo(new ClassPathResource("order.jar").getURL()); assertThat(pu1.excludeUnlistedClasses()).isFalse(); @@ -211,7 +211,7 @@ public void testExampleComplex() throws Exception { assertThat(pu1.getTransactionType()).isSameAs(PersistenceUnitTransactionType.RESOURCE_LOCAL); Properties props = pu1.getProperties(); - assertThat(props.keySet().size()).isEqualTo(2); + assertThat(props.keySet()).hasSize(2); assertThat(props.getProperty("com.acme.persistence.sql-logging")).isEqualTo("on"); assertThat(props.getProperty("foo")).isEqualTo("bar"); @@ -226,7 +226,7 @@ public void testExampleComplex() throws Exception { assertThat(pu2.getTransactionType()).isSameAs(PersistenceUnitTransactionType.JTA); assertThat(pu2.getPersistenceProviderClassName()).isEqualTo("com.acme.AcmePersistence"); - assertThat(pu2.getMappingFileNames().size()).isEqualTo(1); + assertThat(pu2.getMappingFileNames()).hasSize(1); assertThat(pu2.getMappingFileNames().get(0)).isEqualTo("order2.xml"); // the following assertions fail only during coverage runs @@ -247,9 +247,9 @@ public void testExample6() throws Exception { new PathMatchingResourcePatternResolver(), new JndiDataSourceLookup()); String resource = "/org/springframework/orm/jpa/persistence-example6.xml"; PersistenceUnitInfo[] info = reader.readPersistenceUnitInfos(resource); - assertThat(info.length).isEqualTo(1); + assertThat(info).hasSize(1); assertThat(info[0].getPersistenceUnitName()).isEqualTo("pu"); - assertThat(info[0].getProperties().keySet().size()).isEqualTo(0); + assertThat(info[0].getProperties().keySet()).isEmpty(); assertThat(info[0].excludeUnlistedClasses()).as("Exclude unlisted should default false in 1.0.").isFalse(); } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java index c38924840ab6..d51505fb1d0a 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -440,7 +440,7 @@ public void testPersistenceContextsFromJndi() { persistenceContexts.put("", "pc1"); persistenceContexts.put("Person", "pc2"); Map extendedPersistenceContexts = new HashMap<>(); - extendedPersistenceContexts .put("", "pc3"); + extendedPersistenceContexts.put("", "pc3"); ExpectedLookupTemplate jt = new ExpectedLookupTemplate(); jt.addObject("java:comp/env/pc1", mockEm); jt.addObject("java:comp/env/pc2", mockEm2); @@ -480,7 +480,7 @@ public void testPersistenceContextsFromJndiWithDefaultUnit() { persistenceContexts.put("System", "pc1"); persistenceContexts.put("Person", "pc2"); Map extendedPersistenceContexts = new HashMap<>(); - extendedPersistenceContexts .put("System", "pc3"); + extendedPersistenceContexts.put("System", "pc3"); ExpectedLookupTemplate jt = new ExpectedLookupTemplate(); jt.addObject("java:comp/env/pc1", mockEm); jt.addObject("java:comp/env/pc2", mockEm2); @@ -519,7 +519,7 @@ public void testSinglePersistenceContextFromJndi() { Map persistenceContexts = new HashMap<>(); persistenceContexts.put("System", "pc1"); Map extendedPersistenceContexts = new HashMap<>(); - extendedPersistenceContexts .put("System", "pc2"); + extendedPersistenceContexts.put("System", "pc2"); ExpectedLookupTemplate jt = new ExpectedLookupTemplate(); jt.addObject("java:comp/env/pc1", mockEm); jt.addObject("java:comp/env/pc2", mockEm2); diff --git a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java index 32f544067d8a..2f69913413ea 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -101,6 +101,7 @@ import org.springframework.util.ClassUtils; import org.springframework.util.FileCopyUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; import org.springframework.util.xml.StaxUtils; @@ -985,7 +986,7 @@ public String addMtomAttachment(DataHandler dataHandler, String elementNamespace private String getHost(String elementNamespace, DataHandler dataHandler) { try { - URI uri = new URI(elementNamespace); + URI uri = ResourceUtils.toURI(elementNamespace); return uri.getHost(); } catch (URISyntaxException ex) { diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java index dc3fab49d326..83dde60db7b1 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -32,11 +32,13 @@ import reactor.core.publisher.Mono; import org.springframework.core.Ordered; -import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.dao.CannotAcquireLockException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.PermissionDeniedDataAccessException; +import org.springframework.dao.PessimisticLockingFailureException; import org.springframework.dao.QueryTimeoutException; import org.springframework.dao.TransientDataAccessResourceException; import org.springframework.lang.Nullable; @@ -215,17 +217,23 @@ public static DataAccessException convertR2dbcException(String task, @Nullable S return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); } if (ex instanceof R2dbcRollbackException) { - return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex); + if ("40001".equals(ex.getSqlState())) { + return new CannotAcquireLockException(buildMessage(task, sql, ex), ex); + } + return new PessimisticLockingFailureException(buildMessage(task, sql, ex), ex); } if (ex instanceof R2dbcTimeoutException) { return new QueryTimeoutException(buildMessage(task, sql, ex), ex); } } - if (ex instanceof R2dbcNonTransientException) { + else if (ex instanceof R2dbcNonTransientException) { if (ex instanceof R2dbcNonTransientResourceException) { return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex); } if (ex instanceof R2dbcDataIntegrityViolationException) { + if ("23505".equals(ex.getSqlState())) { + return new DuplicateKeyException(buildMessage(task, sql, ex), ex); + } return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); } if (ex instanceof R2dbcPermissionDeniedException) { diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionHolder.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionHolder.java index 1c713048db17..5ad913d0b813 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionHolder.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -23,7 +23,6 @@ import org.springframework.transaction.support.ResourceHolderSupport; import org.springframework.util.Assert; - /** * Resource holder wrapping a R2DBC {@link Connection}. * {@link R2dbcTransactionManager} binds instances of this class to the subscription, @@ -109,7 +108,7 @@ protected void setConnection(@Nullable Connection connection) { * @see #released() */ public Connection getConnection() { - Assert.notNull(this.currentConnection, "Active Connection is required"); + Assert.state(this.currentConnection != null, "Active Connection is required"); return this.currentConnection; } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/lookup/AbstractRoutingConnectionFactory.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/lookup/AbstractRoutingConnectionFactory.java index c02cc5637314..60d81086955e 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/lookup/AbstractRoutingConnectionFactory.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/lookup/AbstractRoutingConnectionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -200,7 +200,7 @@ public ConnectionFactoryMetadata getMetadata() { * in the {@link #setTargetConnectionFactories targetConnectionFactories} map, * falls back to the specified {@link #setDefaultTargetConnectionFactory default * target ConnectionFactory} if necessary. - * @return {@link Mono} emitting the current {@link ConnectionFactory} as + * @return {@link Mono} that emits the current {@link ConnectionFactory} as * per {@link #determineCurrentLookupKey()} * @see #determineCurrentLookupKey() */ diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java index 3f1f28d53b2a..072adb7a9f88 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java @@ -37,14 +37,14 @@ import org.springframework.util.Assert; /** - * A non-blocking, reactive client for performing database calls requests with + * A non-blocking, reactive client for performing database calls with * Reactive Streams back pressure. Provides a higher level, common API over * R2DBC client libraries. * - *

    Use one of the static factory methods {@link #create(ConnectionFactory)} - * or obtain a {@link DatabaseClient#builder()} to create an instance. + *

    Use the static factory method {@link #create(ConnectionFactory)} or obtain + * a {@linkplain DatabaseClient#builder() builder} to create an instance. * - * Usage example: + *

    Usage example: *

      * ConnectionFactory factory = …
      *
    @@ -52,8 +52,7 @@
      * Mono<Actor> actor = client.sql("select first_name, last_name from t_actor")
      *     .map(row -> new Actor(row.get("first_name", String.class),
      *          row.get("last_name", String.class)))
    - *     .first();
    - * 
    + * .first();
    * * @author Mark Paluch * @since 5.3 @@ -61,13 +60,13 @@ public interface DatabaseClient extends ConnectionAccessor { /** - * Return the {@link ConnectionFactory} that this client uses. + * Get the {@link ConnectionFactory} that this client uses. * @return the connection factory */ ConnectionFactory getConnectionFactory(); /** - * Specify a static {@code sql} statement to run. Contract for specifying a + * Specify a static {@code sql} statement to run. Contract for specifying an * SQL call along with options leading to the execution. The SQL string can * contain either native parameter bind markers or named parameters (e.g. * {@literal :foo, :bar}) when {@link NamedParameterExpander} is enabled. @@ -79,7 +78,7 @@ public interface DatabaseClient extends ConnectionAccessor { GenericExecuteSpec sql(String sql); /** - * Specify a {@link Supplier SQL supplier} that provides SQL to run. + * Specify an {@linkplain Supplier SQL supplier} that provides SQL to run. * Contract for specifying an SQL call along with options leading to * the execution. The SQL string can contain either native parameter * bind markers or named parameters (e.g. {@literal :foo, :bar}) when @@ -99,7 +98,7 @@ public interface DatabaseClient extends ConnectionAccessor { /** * Create a {@code DatabaseClient} that will use the provided {@link ConnectionFactory}. * @param factory the {@code ConnectionFactory} to use for obtaining connections - * @return a new {@code DatabaseClient}. Guaranteed to be not {@code null}. + * @return a new {@code DatabaseClient}; never {@code null} */ static DatabaseClient create(ConnectionFactory factory) { return new DefaultDatabaseClientBuilder().connectionFactory(factory).build(); @@ -129,14 +128,14 @@ interface Builder { Builder connectionFactory(ConnectionFactory factory); /** - * Configure a {@link ExecuteFunction} to execute {@link Statement} objects. + * Configure an {@link ExecuteFunction} to execute {@link Statement} objects. * @see Statement#execute() */ Builder executeFunction(ExecuteFunction executeFunction); /** * Configure whether to use named parameter expansion. - * Defaults to {@code true}. + *

    Defaults to {@code true}. * @param enabled {@code true} to use named parameter expansion; * {@code false} to disable named parameter expansion * @see NamedParameterExpander @@ -144,7 +143,7 @@ interface Builder { Builder namedParameters(boolean enabled); /** - * Configures a {@link Consumer} to configure this builder. + * Apply a {@link Consumer} to configure this builder. */ Builder apply(Consumer builderConsumer); @@ -238,7 +237,7 @@ default GenericExecuteSpec filter(Function the result type - * @return a {@link Flux} emitting mapped elements + * @return a {@link Flux} that emits mapped elements * @since 6.0 * @see Result#filter(Predicate) * @see Result#flatMap(Function) diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java index 1f1673ce6481..4c4b03027bdf 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java @@ -200,16 +200,15 @@ private static Mono sumRowsUpdated( } /** - * Determine SQL from potential provider object. - * @param sqlProvider object that's potentially a SqlProvider + * Get SQL from a potential provider object. + * @param object an object that is potentially an SqlProvider * @return the SQL string, or {@code null} * @see SqlProvider */ @Nullable - private static String getSql(Object sqlProvider) { - - if (sqlProvider instanceof SqlProvider) { - return ((SqlProvider) sqlProvider).getSql(); + private static String getSql(Object object) { + if (object instanceof SqlProvider sqlProvider) { + return sqlProvider.getSql(); } else { return null; @@ -218,7 +217,7 @@ private static String getSql(Object sqlProvider) { /** - * Base class for {@link DatabaseClient.GenericExecuteSpec} implementations. + * Default {@link DatabaseClient.GenericExecuteSpec} implementation. */ class DefaultGenericExecuteSpec implements GenericExecuteSpec { @@ -352,10 +351,10 @@ private ResultFunction getResultFunction(Supplier sqlSupplier) { if (logger.isDebugEnabled()) { logger.debug("Executing SQL statement [" + sql + "]"); } - if (sqlSupplier instanceof PreparedOperation) { + if (sqlSupplier instanceof PreparedOperation preparedOperation) { Statement statement = connection.createStatement(sql); BindTarget bindTarget = new StatementWrapper(statement); - ((PreparedOperation) sqlSupplier).bindTo(bindTarget); + preparedOperation.bindTo(bindTarget); return statement; } @@ -397,7 +396,7 @@ private ResultFunction getResultFunction(Supplier sqlSupplier) { Function> resultFunction = connection -> { Statement statement = statementFunction.apply(connection); return Flux.from(this.filterFunction.filter(statement, DefaultDatabaseClient.this.executeFunction)) - .cast(Result.class).checkpoint("SQL \"" + sql + "\" [DatabaseClient]"); + .cast(Result.class).checkpoint("SQL \"" + sql + "\" [DatabaseClient]"); }; return new ResultFunction(resultFunction, sql); diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterExpander.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterExpander.java index 6713baba2af4..15b1bcd1fb95 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterExpander.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterExpander.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -74,7 +74,7 @@ private ParsedSql getParsedSql(String sql) { * placeholders to be used for a select list. Select lists should be limited * to 100 or fewer elements. A larger number of elements is not guaranteed to be * supported by the database and is strictly vendor-dependent. - * @param sql sql the original SQL statement + * @param sql the original SQL statement * @param bindMarkersFactory the bind marker factory * @param paramSource the source for named parameters * @return the expanded sql that accepts bind parameters and allows for execution diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/PreparedOperation.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/PreparedOperation.java index 6164063f68ab..29361e78aa5f 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/PreparedOperation.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/PreparedOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -23,25 +23,25 @@ /** * Extension to {@link QueryOperation} for a prepared SQL query * {@link Supplier} with bound parameters. Contains parameter - * bindings that can be {@link #bindTo bound} bound to a {@link BindTarget}. + * bindings that can be {@link #bindTo bound} to a {@link BindTarget}. * *

    Can be executed with {@link org.springframework.r2dbc.core.DatabaseClient}. * * @author Mark Paluch * @since 5.3 - * @param underlying operation source. + * @param underlying operation source * @see org.springframework.r2dbc.core.DatabaseClient#sql(Supplier) */ public interface PreparedOperation extends QueryOperation { /** - * Return the underlying query source. + * Get the underlying query source. * @return the query source, such as a statement/criteria object */ T getSource(); /** - * Apply bindings to {@link BindTarget}. + * Apply bindings to the supplied {@link BindTarget}. * @param target the target to apply bindings to */ void bindTo(BindTarget target); diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java index a8e3b7ea9730..71569702ccc7 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/AnonymousBindMarkers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactory.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactory.java index 90cff2a4eb56..c77999bdbf0d 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactory.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/binding/BindMarkersFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -82,7 +82,7 @@ static BindMarkersFactory indexed(String prefix, int beginWith) { * @see io.r2dbc.spi.Statement#bind(int, Object) */ static BindMarkersFactory anonymous(String placeholder) { - Assert.hasText(placeholder, "Placeholder must not be empty!"); + Assert.hasText(placeholder, "Placeholder must not be empty"); return new BindMarkersFactory() { @Override public BindMarkers create() { diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/ConnectionFactoryUtilsUnitTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/ConnectionFactoryUtilsUnitTests.java index ebbf3c370b87..f8449ab7782a 100644 --- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/ConnectionFactoryUtilsUnitTests.java +++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/ConnectionFactoryUtilsUnitTests.java @@ -26,10 +26,12 @@ import io.r2dbc.spi.R2dbcTransientResourceException; import org.junit.jupiter.api.Test; -import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.dao.CannotAcquireLockException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.PermissionDeniedDataAccessException; +import org.springframework.dao.PessimisticLockingFailureException; import org.springframework.dao.QueryTimeoutException; import org.springframework.dao.TransientDataAccessResourceException; import org.springframework.r2dbc.BadSqlGrammarException; @@ -41,6 +43,7 @@ * Unit tests for {@link ConnectionFactoryUtils}. * * @author Mark Paluch + * @author Juergen Hoeller */ public class ConnectionFactoryUtilsUnitTests { @@ -48,63 +51,71 @@ public class ConnectionFactoryUtilsUnitTests { public void shouldTranslateTransientResourceException() { Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "", new R2dbcTransientResourceException("")); - assertThat(exception).isInstanceOf(TransientDataAccessResourceException.class); + assertThat(exception).isExactlyInstanceOf(TransientDataAccessResourceException.class); } @Test public void shouldTranslateRollbackException() { Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "", new R2dbcRollbackException()); - assertThat(exception).isInstanceOf(ConcurrencyFailureException.class); + assertThat(exception).isExactlyInstanceOf(PessimisticLockingFailureException.class); + + exception = ConnectionFactoryUtils.convertR2dbcException("", "", + new R2dbcRollbackException("reason", "40001")); + assertThat(exception).isExactlyInstanceOf(CannotAcquireLockException.class); } @Test public void shouldTranslateTimeoutException() { Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "", new R2dbcTimeoutException()); - assertThat(exception).isInstanceOf(QueryTimeoutException.class); + assertThat(exception).isExactlyInstanceOf(QueryTimeoutException.class); } @Test public void shouldNotTranslateUnknownExceptions() { Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "", new MyTransientExceptions()); - assertThat(exception).isInstanceOf(UncategorizedR2dbcException.class); + assertThat(exception).isExactlyInstanceOf(UncategorizedR2dbcException.class); } @Test public void shouldTranslateNonTransientResourceException() { Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "", new R2dbcNonTransientResourceException()); - assertThat(exception).isInstanceOf(DataAccessResourceFailureException.class); + assertThat(exception).isExactlyInstanceOf(DataAccessResourceFailureException.class); } @Test public void shouldTranslateIntegrityViolationException() { Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "", new R2dbcDataIntegrityViolationException()); - assertThat(exception).isInstanceOf(DataIntegrityViolationException.class); + assertThat(exception).isExactlyInstanceOf(DataIntegrityViolationException.class); + + exception = ConnectionFactoryUtils.convertR2dbcException("", "", + new R2dbcDataIntegrityViolationException("reason", "23505")); + assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class); } @Test public void shouldTranslatePermissionDeniedException() { Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "", new R2dbcPermissionDeniedException()); - assertThat(exception).isInstanceOf(PermissionDeniedDataAccessException.class); + assertThat(exception).isExactlyInstanceOf(PermissionDeniedDataAccessException.class); } @Test public void shouldTranslateBadSqlGrammarException() { Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "", new R2dbcBadGrammarException()); - assertThat(exception).isInstanceOf(BadSqlGrammarException.class); + assertThat(exception).isExactlyInstanceOf(BadSqlGrammarException.class); } @Test public void messageGeneration() { Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK", "SOME-SQL", new R2dbcTransientResourceException("MESSAGE")); - assertThat(exception).isInstanceOf( + assertThat(exception).isExactlyInstanceOf( TransientDataAccessResourceException.class).hasMessage("TASK; SQL [SOME-SQL]; MESSAGE"); } @@ -112,7 +123,7 @@ public void messageGeneration() { public void messageGenerationNullSQL() { Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK", null, new R2dbcTransientResourceException("MESSAGE")); - assertThat(exception).isInstanceOf( + assertThat(exception).isExactlyInstanceOf( TransientDataAccessResourceException.class).hasMessage("TASK; MESSAGE"); } @@ -120,7 +131,7 @@ public void messageGenerationNullSQL() { public void messageGenerationNullMessage() { Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK", "SOME-SQL", new R2dbcTransientResourceException()); - assertThat(exception).isInstanceOf( + assertThat(exception).isExactlyInstanceOf( TransientDataAccessResourceException.class).hasMessage("TASK; SQL [SOME-SQL]; null"); } diff --git a/spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java b/spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java index 9c4280ed320a..17dbd8cd90b7 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java +++ b/spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -37,11 +37,17 @@ public class MockHttpInputMessage implements HttpInputMessage { private final InputStream body; - public MockHttpInputMessage(byte[] content) { - Assert.notNull(content, "Byte array must not be null"); - this.body = new ByteArrayInputStream(content); + /** + * Create a {@code MockHttpInputMessage} with the supplied body. + */ + public MockHttpInputMessage(byte[] body) { + Assert.notNull(body, "Byte array must not be null"); + this.body = new ByteArrayInputStream(body); } + /** + * Create a {@code MockHttpInputMessage} with the supplied body. + */ public MockHttpInputMessage(InputStream body) { Assert.notNull(body, "InputStream must not be null"); this.body = body; diff --git a/spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java b/spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java index 49262749960a..7e911d05ca14 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java +++ b/spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -34,31 +34,23 @@ */ public class MockHttpOutputMessage implements HttpOutputMessage { - private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private final HttpHeaders headers = new HttpHeaders(); private final ByteArrayOutputStream body = new ByteArrayOutputStream(1024); - /** - * Return the headers. - */ @Override public HttpHeaders getHeaders() { return this.headers; } - /** - * Return the body content. - */ @Override public OutputStream getBody() throws IOException { return this.body; } /** - * Return body content as a byte array. + * Return the body content as a byte array. */ public byte[] getBodyAsBytes() { return this.body.toByteArray(); @@ -68,12 +60,12 @@ public byte[] getBodyAsBytes() { * Return the body content interpreted as a UTF-8 string. */ public String getBodyAsString() { - return getBodyAsString(DEFAULT_CHARSET); + return getBodyAsString(StandardCharsets.UTF_8); } /** - * Return the body content as a string. - * @param charset the charset to use to turn the body content to a String + * Return the body content interpreted as a string using the supplied character set. + * @param charset the charset to use to turn the body content into a String */ public String getBodyAsString(Charset charset) { return StreamUtils.copyToString(this.body, charset); diff --git a/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java index aca9719d1d9b..bac710f1bf9f 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java +++ b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -18,7 +18,6 @@ import java.io.IOException; import java.net.URI; -import java.net.URISyntaxException; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; @@ -26,11 +25,13 @@ import org.springframework.lang.Nullable; import org.springframework.mock.http.MockHttpOutputMessage; import org.springframework.util.Assert; +import org.springframework.web.util.UriComponentsBuilder; /** * Mock implementation of {@link ClientHttpRequest}. * * @author Rossen Stoyanchev + * @author Brian Clozel * @author Sam Brannen * @since 3.2 */ @@ -47,20 +48,25 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie /** - * Default constructor. + * Create a {@code MockClientHttpRequest} with {@link HttpMethod#GET GET} as + * the HTTP request method and {@code "/"} as the {@link URI}. */ public MockClientHttpRequest() { - this.httpMethod = HttpMethod.GET; - try { - this.uri = new URI("/"); - } - catch (URISyntaxException ex) { - throw new IllegalStateException(ex); - } + this(HttpMethod.GET, URI.create("/")); + } + + /** + * Create a {@code MockClientHttpRequest} with the given {@link HttpMethod}, + * URI template, and URI template variable values. + * @since 6.0.3 + */ + public MockClientHttpRequest(HttpMethod httpMethod, String uriTemplate, Object... vars) { + this(httpMethod, UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(vars).encode().toUri()); } /** - * Create an instance with the given HttpMethod and URI. + * Create a {@code MockClientHttpRequest} with the given {@link HttpMethod} + * and {@link URI}. */ public MockClientHttpRequest(HttpMethod httpMethod, URI uri) { this.httpMethod = httpMethod; @@ -68,6 +74,9 @@ public MockClientHttpRequest(HttpMethod httpMethod, URI uri) { } + /** + * Set the HTTP method of the request. + */ public void setMethod(HttpMethod httpMethod) { this.httpMethod = httpMethod; } @@ -77,6 +86,9 @@ public HttpMethod getMethod() { return this.httpMethod; } + /** + * Set the URI of the request. + */ public void setURI(URI uri) { this.uri = uri; } @@ -86,10 +98,19 @@ public URI getURI() { return this.uri; } + /** + * Set the {@link ClientHttpResponse} to be used as the result of executing + * the this request. + * @see #execute() + */ public void setResponse(ClientHttpResponse clientHttpResponse) { this.clientHttpResponse = clientHttpResponse; } + /** + * Get the {@link #isExecuted() executed} flag. + * @see #execute() + */ public boolean isExecuted() { return this.executed; } @@ -120,14 +141,10 @@ protected ClientHttpResponse executeInternal() throws IOException { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(this.httpMethod); - sb.append(' ').append(this.uri); + sb.append(this.httpMethod).append(' ').append(this.uri); if (!getHeaders().isEmpty()) { sb.append(", headers: ").append(getHeaders()); } - if (sb.length() == 0) { - sb.append("Not yet initialized"); - } return sb.toString(); } diff --git a/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpResponse.java b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpResponse.java index b8a5d084c15a..ec164e005de3 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpResponse.java @@ -29,6 +29,7 @@ * Mock implementation of {@link ClientHttpResponse}. * * @author Rossen Stoyanchev + * @author Sam Brannen * @since 3.2 */ public class MockClientHttpResponse extends MockHttpInputMessage implements ClientHttpResponse { @@ -37,17 +38,27 @@ public class MockClientHttpResponse extends MockHttpInputMessage implements Clie /** - * Constructor with response body as a byte array. + * Create a {@code MockClientHttpResponse} with an empty response body and + * HTTP status code {@link HttpStatus#OK OK}. + * @since 6.0.3 + */ + public MockClientHttpResponse() { + this(new byte[0], HttpStatus.OK); + } + + /** + * Create a {@code MockClientHttpResponse} with response body as a byte array + * and the supplied HTTP status code. */ public MockClientHttpResponse(byte[] body, HttpStatusCode statusCode) { super(body); - Assert.notNull(statusCode, "HttpStatusCode is required"); + Assert.notNull(statusCode, "HttpStatusCode must not be null"); this.statusCode = statusCode; } /** - * Variant of {@link #MockClientHttpResponse(byte[], HttpStatusCode)} with a - * custom HTTP status code. + * Create a {@code MockClientHttpResponse} with response body as a byte array + * and a custom HTTP status code. * @since 5.3.17 */ public MockClientHttpResponse(byte[] body, int statusCode) { @@ -55,17 +66,18 @@ public MockClientHttpResponse(byte[] body, int statusCode) { } /** - * Constructor with response body as InputStream. + * Create a {@code MockClientHttpResponse} with response body as {@link InputStream} + * and the supplied HTTP status code. */ public MockClientHttpResponse(InputStream body, HttpStatusCode statusCode) { super(body); - Assert.notNull(statusCode, "HttpStatus is required"); + Assert.notNull(statusCode, "HttpStatusCode must not be null"); this.statusCode = statusCode; } /** - * Variant of {@link #MockClientHttpResponse(InputStream, HttpStatusCode)} with a - * custom HTTP status code. + * Create a {@code MockClientHttpResponse} with response body as {@link InputStream} + * and a custom HTTP status code. * @since 5.3.17 */ public MockClientHttpResponse(InputStream body, int statusCode) { @@ -86,12 +98,7 @@ public int getRawStatusCode() { @Override public String getStatusText() { - if (this.statusCode instanceof HttpStatus status) { - return status.getReasonPhrase(); - } - else { - return ""; - } + return (this.statusCode instanceof HttpStatus status ? status.getReasonPhrase() : ""); } @Override diff --git a/spring-test/src/main/java/org/springframework/mock/http/client/reactive/MockClientHttpResponse.java b/spring-test/src/main/java/org/springframework/mock/http/client/reactive/MockClientHttpResponse.java index 362f8c9c910a..fddc2687c487 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/client/reactive/MockClientHttpResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/http/client/reactive/MockClientHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -61,7 +61,7 @@ public MockClientHttpResponse(int status) { } public MockClientHttpResponse(HttpStatusCode status) { - Assert.notNull(status, "HttpStatusCode is required"); + Assert.notNull(status, "HttpStatusCode must not be null"); this.statusCode = status; } diff --git a/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java b/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java index 7bf5a693ca14..1e018ed6c3bb 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java +++ b/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java @@ -43,7 +43,6 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MimeType; import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; /** @@ -236,7 +235,7 @@ public static BodyBuilder method(HttpMethod method, String uri, Object... vars) */ @Deprecated(since = "6.0") public static BodyBuilder method(String httpMethod, String uri, Object... vars) { - Assert.isTrue(StringUtils.hasText(httpMethod), "HTTP method is required."); + Assert.hasText(httpMethod, "HTTP method is required."); return new DefaultBodyBuilder(HttpMethod.valueOf(httpMethod), toUri(uri, vars)); } diff --git a/spring-test/src/main/java/org/springframework/mock/web/HeaderValueHolder.java b/spring-test/src/main/java/org/springframework/mock/web/HeaderValueHolder.java index cea3d270d7eb..a0a739765e36 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/HeaderValueHolder.java +++ b/spring-test/src/main/java/org/springframework/mock/web/HeaderValueHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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,7 +16,6 @@ package org.springframework.mock.web; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; @@ -61,11 +60,7 @@ List getValues() { } List getStringValues() { - List stringList = new ArrayList<>(this.values.size()); - for (Object value : this.values) { - stringList.add(value.toString()); - } - return Collections.unmodifiableList(stringList); + return this.values.stream().map(Object::toString).toList(); } @Nullable diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockBodyContent.java b/spring-test/src/main/java/org/springframework/mock/web/MockBodyContent.java index 85ee83f89feb..81a905150eca 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockBodyContent.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockBodyContent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -69,8 +69,8 @@ public MockBodyContent(String content, @Nullable HttpServletResponse response, @ } private static JspWriter adaptJspWriter(@Nullable Writer targetWriter, @Nullable HttpServletResponse response) { - if (targetWriter instanceof JspWriter) { - return (JspWriter) targetWriter; + if (targetWriter instanceof JspWriter jspWriter) { + return jspWriter; } else { return new MockJspWriter(response, targetWriter); diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java index ad730966f0d2..c01211807956 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java @@ -549,11 +549,11 @@ public void setParameter(String name, String... values) { public void setParameters(Map params) { Assert.notNull(params, "Parameter map must not be null"); params.forEach((key, value) -> { - if (value instanceof String) { - setParameter(key, (String) value); + if (value instanceof String str) { + setParameter(key, str); } - else if (value instanceof String[]) { - setParameter(key, (String[]) value); + else if (value instanceof String[] strings) { + setParameter(key, strings); } else { throw new IllegalArgumentException( @@ -598,11 +598,11 @@ public void addParameter(String name, String... values) { public void addParameters(Map params) { Assert.notNull(params, "Parameter map must not be null"); params.forEach((key, value) -> { - if (value instanceof String) { - addParameter(key, (String) value); + if (value instanceof String str) { + addParameter(key, str); } - else if (value instanceof String[]) { - addParameter(key, (String[]) value); + else if (value instanceof String[] strings) { + addParameter(key, strings); } else { throw new IllegalArgumentException("Parameter map value must be single value " + @@ -1083,8 +1083,8 @@ private void doAddHeaderValue(String name, @Nullable Object value, boolean repla header = new HeaderValueHolder(); this.headers.put(name, header); } - if (value instanceof Collection) { - header.addValues((Collection) value); + if (value instanceof Collection collection) { + header.addValues(collection); } else if (value.getClass().isArray()) { header.addValueArray(value); @@ -1119,14 +1119,14 @@ public void removeHeader(String name) { public long getDateHeader(String name) { HeaderValueHolder header = this.headers.get(name); Object value = (header != null ? header.getValue() : null); - if (value instanceof Date) { - return ((Date) value).getTime(); + if (value instanceof Date date) { + return date.getTime(); } - else if (value instanceof Number) { - return ((Number) value).longValue(); + else if (value instanceof Number number) { + return number.longValue(); } - else if (value instanceof String) { - return parseDateHeader(name, (String) value); + else if (value instanceof String str) { + return parseDateHeader(name, str); } else if (value != null) { throw new IllegalArgumentException( @@ -1173,11 +1173,11 @@ public Enumeration getHeaderNames() { public int getIntHeader(String name) { HeaderValueHolder header = this.headers.get(name); Object value = (header != null ? header.getValue() : null); - if (value instanceof Number) { - return ((Number) value).intValue(); + if (value instanceof Number number) { + return number.intValue(); } - else if (value instanceof String) { - return Integer.parseInt((String) value); + else if (value instanceof String str) { + return Integer.parseInt(str); } else if (value != null) { throw new NumberFormatException("Value for header '" + name + "' is not a Number: " + value); @@ -1248,8 +1248,9 @@ public void addUserRole(String role) { @Override public boolean isUserInRole(String role) { - return (this.userRoles.contains(role) || (this.servletContext instanceof MockServletContext && - ((MockServletContext) this.servletContext).getDeclaredRoles().contains(role))); + return (this.userRoles.contains(role) || + (this.servletContext instanceof MockServletContext mockContext && + mockContext.getDeclaredRoles().contains(role))); } public void setUserPrincipal(@Nullable Principal userPrincipal) { @@ -1321,7 +1322,7 @@ public void setSession(HttpSession session) { public HttpSession getSession(boolean create) { checkActive(); // Reset session if invalidated. - if (this.session instanceof MockHttpSession && ((MockHttpSession) this.session).isInvalid()) { + if (this.session instanceof MockHttpSession mockSession && mockSession.isInvalid()) { this.session = null; } // Create new session if necessary. @@ -1346,8 +1347,8 @@ public HttpSession getSession() { @Override public String changeSessionId() { Assert.isTrue(this.session != null, "The request does not have a session"); - if (this.session instanceof MockHttpSession) { - return ((MockHttpSession) this.session).changeSessionId(); + if (this.session instanceof MockHttpSession mockSession) { + return mockSession.changeSessionId(); } return this.session.getId(); } diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java index 88af4672bfb1..65995e95610d 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java @@ -424,7 +424,7 @@ private String getCookieHeader(Cookie cookie) { buf.append("; Domain=").append(cookie.getDomain()); } int maxAge = cookie.getMaxAge(); - ZonedDateTime expires = (cookie instanceof MockCookie mockCookie? mockCookie.getExpires() : null); + ZonedDateTime expires = (cookie instanceof MockCookie mockCookie ? mockCookie.getExpires() : null); if (maxAge >= 0) { buf.append("; Max-Age=").append(maxAge); buf.append("; Expires="); diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java index e8d1d890e001..167128dee16c 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java @@ -167,11 +167,11 @@ public void setAttribute(String name, @Nullable Object value) { if (value != null) { Object oldValue = this.attributes.put(name, value); if (value != oldValue) { - if (oldValue instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) oldValue).valueUnbound(new HttpSessionBindingEvent(this, name, oldValue)); + if (oldValue instanceof HttpSessionBindingListener listener) { + listener.valueUnbound(new HttpSessionBindingEvent(this, name, oldValue)); } - if (value instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value)); + if (value instanceof HttpSessionBindingListener listener) { + listener.valueBound(new HttpSessionBindingEvent(this, name, value)); } } } @@ -185,8 +185,8 @@ public void removeAttribute(String name) { assertIsValid(); Assert.notNull(name, "Attribute name must not be null"); Object value = this.attributes.remove(name); - if (value instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value)); + if (value instanceof HttpSessionBindingListener listener) { + listener.valueUnbound(new HttpSessionBindingEvent(this, name, value)); } } @@ -199,8 +199,8 @@ public void clearAttributes() { String name = entry.getKey(); Object value = entry.getValue(); it.remove(); - if (value instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value)); + if (value instanceof HttpSessionBindingListener listener) { + listener.valueUnbound(new HttpSessionBindingEvent(this, name, value)); } } } @@ -251,14 +251,14 @@ public Serializable serializeState() { String name = entry.getKey(); Object value = entry.getValue(); it.remove(); - if (value instanceof Serializable) { - state.put(name, (Serializable) value); + if (value instanceof Serializable serializable) { + state.put(name, serializable); } else { // Not serializable... Servlet containers usually automatically // unbind the attribute in this case. - if (value instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value)); + if (value instanceof HttpSessionBindingListener listener) { + listener.valueUnbound(new HttpSessionBindingEvent(this, name, value)); } } } diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockJspWriter.java b/spring-test/src/main/java/org/springframework/mock/web/MockJspWriter.java index b99202c1e530..ab0c4ec51c34 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockJspWriter.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockJspWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -65,8 +65,8 @@ public MockJspWriter(Writer targetWriter) { public MockJspWriter(@Nullable HttpServletResponse response, @Nullable Writer targetWriter) { super(DEFAULT_BUFFER, true); this.response = (response != null ? response : new MockHttpServletResponse()); - if (targetWriter instanceof PrintWriter) { - this.targetWriter = (PrintWriter) targetWriter; + if (targetWriter instanceof PrintWriter printWriter) { + this.targetWriter = printWriter; } else if (targetWriter != null) { this.targetWriter = new PrintWriter(targetWriter); diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java b/spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java index f4a1b9b00483..6bcbf97d57da 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java @@ -342,13 +342,17 @@ public void include(String path, boolean flush) throws ServletException, IOExcep } public byte[] getContentAsByteArray() { - Assert.state(this.response instanceof MockHttpServletResponse, "MockHttpServletResponse required"); - return ((MockHttpServletResponse) this.response).getContentAsByteArray(); + if (this.response instanceof MockHttpServletResponse mockResponse) { + return mockResponse.getContentAsByteArray(); + } + throw new IllegalStateException("MockHttpServletResponse is required"); } public String getContentAsString() throws UnsupportedEncodingException { - Assert.state(this.response instanceof MockHttpServletResponse, "MockHttpServletResponse required"); - return ((MockHttpServletResponse) this.response).getContentAsString(); + if (this.response instanceof MockHttpServletResponse mockResponse) { + return mockResponse.getContentAsString(); + } + throw new IllegalStateException("MockHttpServletResponse is required"); } @Override diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockRequestDispatcher.java b/spring-test/src/main/java/org/springframework/mock/web/MockRequestDispatcher.java index 9a34e6b45cce..e6f7dadff1bd 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockRequestDispatcher.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockRequestDispatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -78,11 +78,11 @@ public void include(ServletRequest request, ServletResponse response) { * {@link HttpServletResponseWrapper} decorators if necessary. */ protected MockHttpServletResponse getMockHttpServletResponse(ServletResponse response) { - if (response instanceof MockHttpServletResponse) { - return (MockHttpServletResponse) response; + if (response instanceof MockHttpServletResponse mockResponse) { + return mockResponse; } - if (response instanceof HttpServletResponseWrapper) { - return getMockHttpServletResponse(((HttpServletResponseWrapper) response).getResponse()); + if (response instanceof HttpServletResponseWrapper wrapper) { + return getMockHttpServletResponse(wrapper.getResponse()); } throw new IllegalArgumentException("MockRequestDispatcher requires MockHttpServletResponse"); } diff --git a/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java index cf3614ee9310..26957c49f25e 100644 --- a/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java +++ b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -187,7 +187,7 @@ private static boolean isTestEnabledInThisEnvironment(ProfileValueSource profile String environmentValue = profileValueSource.get(ifProfileValue.name()); String[] annotatedValues = ifProfileValue.values(); if (StringUtils.hasLength(ifProfileValue.value())) { - Assert.isTrue(annotatedValues.length == 0, () -> "Setting both the 'value' and 'values' attributes " + + Assert.isTrue(annotatedValues.length == 0, "Setting both the 'value' and 'values' attributes " + "of @IfProfileValue is not allowed: choose one or the other."); annotatedValues = new String[] { ifProfileValue.value() }; } diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoadException.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoadException.java index 723d8ebb3c83..6dfe236a695e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextLoadException.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoadException.java @@ -38,11 +38,11 @@ public class ContextLoadException extends Exception { /** * Create a new {@code ContextLoadException} for the supplied - * {@link ApplicationContext} and {@link Exception}. + * {@link ApplicationContext} and {@link Throwable}. * @param applicationContext the application context that failed to load * @param cause the exception caught while attempting to load that context */ - public ContextLoadException(ApplicationContext applicationContext, Exception cause) { + public ContextLoadException(ApplicationContext applicationContext, Throwable cause) { super(cause); this.applicationContext = applicationContext; } diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java index 05427a80dacb..49d0181e251b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java @@ -75,9 +75,9 @@ public class MergedContextConfiguration implements Serializable { private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; private static final Set>> EMPTY_INITIALIZER_CLASSES = - Collections.>> emptySet(); + Collections.emptySet(); - private static final Set EMPTY_CONTEXT_CUSTOMIZERS = Collections. emptySet(); + private static final Set EMPTY_CONTEXT_CUSTOMIZERS = Collections.emptySet(); private final Class testClass; diff --git a/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java index 3461a7b13552..ccec852ce0c5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java @@ -144,7 +144,6 @@ public interface SmartContextLoader extends ContextLoader { * @return a new application context * @throws ContextLoadException if context loading failed * @see #processContextConfiguration(ContextConfigurationAttributes) - * @see #loadContextForAotProcessing(MergedContextConfiguration) * @see org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry) * @see org.springframework.context.ConfigurableApplicationContext#getEnvironment() */ diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java index ebd546dc547e..5d8d96dd9a17 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.function.Supplier; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -40,13 +39,13 @@ * *
      *
    • {@link #beforeTestClass() before test class execution}: prior to any - * before class callbacks of a particular testing framework (e.g., - * JUnit 4's {@link org.junit.BeforeClass @BeforeClass})
    • + * before class callbacks of a particular testing framework — for + * example, JUnit Jupiter's {@link org.junit.jupiter.api.BeforeAll @BeforeAll} *
    • {@link #prepareTestInstance test instance preparation}: * immediately following instantiation of the test class
    • *
    • {@link #beforeTestMethod before test setup}: - * prior to any before method callbacks of a particular testing framework - * (e.g., JUnit 4's {@link org.junit.Before @Before})
    • + * prior to any before method callbacks of a particular testing framework — + * for example, JUnit Jupiter's {@link org.junit.jupiter.api.BeforeEach @BeforeEach} *
    • {@link #beforeTestExecution before test execution}: * immediately before execution of the {@linkplain java.lang.reflect.Method * test method} but after test setup
    • @@ -54,11 +53,11 @@ * immediately after execution of the {@linkplain java.lang.reflect.Method * test method} but before test tear down *
    • {@link #afterTestMethod(Object, Method, Throwable) after test tear down}: - * after any after method callbacks of a particular testing - * framework (e.g., JUnit 4's {@link org.junit.After @After})
    • + * after any after method callbacks of a particular testing framework — + * for example, JUnit Jupiter's {@link org.junit.jupiter.api.AfterEach @AfterEach} *
    • {@link #afterTestClass() after test class execution}: after any - * after class callbacks of a particular testing framework (e.g., JUnit 4's - * {@link org.junit.AfterClass @AfterClass})
    • + * after class callbacks of a particular testing framework — for example, + * JUnit Jupiter's {@link org.junit.jupiter.api.AfterAll @AfterAll} *
    * *

    Support for loading and accessing @@ -93,17 +92,9 @@ public class TestContextManager { private final TestContext testContext; - private final ThreadLocal testContextHolder = ThreadLocal.withInitial( - // Implemented as an anonymous inner class instead of a lambda expression due to a bug - // in Eclipse IDE: "The blank final field testContext may not have been initialized" - new Supplier() { - @Override - public TestContext get() { - return copyTestContext(TestContextManager.this.testContext); - } - }); + private final ThreadLocal testContextHolder; - private final List testExecutionListeners = new ArrayList<>(); + private final List testExecutionListeners = new ArrayList<>(8); /** @@ -136,6 +127,7 @@ public TestContextManager(Class testClass) { */ public TestContextManager(TestContextBootstrapper testContextBootstrapper) { this.testContext = testContextBootstrapper.buildTestContext(); + this.testContextHolder = ThreadLocal.withInitial(() -> copyTestContext(this.testContext)); registerTestExecutionListeners(testContextBootstrapper.getTestExecutionListeners()); } @@ -162,7 +154,7 @@ public void registerTestExecutionListeners(List testExecu public void registerTestExecutionListeners(TestExecutionListener... testExecutionListeners) { for (TestExecutionListener listener : testExecutionListeners) { if (logger.isTraceEnabled()) { - logger.trace("Registering TestExecutionListener: " + listener); + logger.trace("Registering TestExecutionListener: " + typeName(listener)); } this.testExecutionListeners.add(listener); } @@ -191,8 +183,8 @@ private List getReversedTestExecutionListeners() { /** * Hook for pre-processing a test class before execution of any * tests within the class. Should be called prior to any framework-specific - * before class methods (e.g., methods annotated with JUnit 4's - * {@link org.junit.BeforeClass @BeforeClass}). + * before class methods — for example, methods annotated with + * JUnit Jupiter's {@link org.junit.jupiter.api.BeforeAll @BeforeAll}. *

    An attempt will be made to give each registered * {@link TestExecutionListener} a chance to pre-process the test class * execution. If a listener throws an exception, however, the remaining @@ -205,7 +197,7 @@ private List getReversedTestExecutionListeners() { public void beforeTestClass() throws Exception { Class testClass = getTestContext().getTestClass(); if (logger.isTraceEnabled()) { - logger.trace("beforeTestClass(): class [" + testClass.getName() + "]"); + logger.trace("beforeTestClass(): class [" + typeName(testClass) + "]"); } getTestContext().updateState(null, null, null); @@ -234,7 +226,7 @@ public void beforeTestClass() throws Exception { * {@link TestExecutionListener} a chance to prepare the test instance. If a * listener throws an exception, however, the remaining registered listeners * will not be called. - * @param testInstance the test instance to prepare (never {@code null}) + * @param testInstance the test instance to prepare * @throws Exception if a registered TestExecutionListener throws an exception * @see #getTestExecutionListeners() */ @@ -250,8 +242,10 @@ public void prepareTestInstance(Object testInstance) throws Exception { } catch (Throwable ex) { if (logger.isErrorEnabled()) { - logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener + - "] to prepare test instance [" + testInstance + "]", ex); + logger.error(""" + Caught exception while allowing TestExecutionListener [%s] to \ + prepare test instance [%s]""" + .formatted(typeName(testExecutionListener), testInstance), ex); } ReflectionUtils.rethrowException(ex); } @@ -263,10 +257,10 @@ public void prepareTestInstance(Object testInstance) throws Exception { * lifecycle callbacks of the underlying test framework — for example, * setting up test fixtures, starting a transaction, etc. *

    This method must be called immediately prior to - * framework-specific before lifecycle callbacks (e.g., methods - * annotated with JUnit 4's {@link org.junit.Before @Before}). For historical - * reasons, this method is named {@code beforeTestMethod}. Since the - * introduction of {@link #beforeTestExecution}, a more suitable name for + * framework-specific before lifecycle callbacks — for example, methods + * annotated with JUnit Jupiter's {@link org.junit.jupiter.api.BeforeEach @BeforeEach}. + * For historical reasons, this method is named {@code beforeTestMethod}. Since + * the introduction of {@link #beforeTestExecution}, a more suitable name for * this method might be something like {@code beforeTestSetUp} or * {@code beforeEach}; however, it is unfortunately impossible to rename * this method due to backward compatibility concerns. @@ -276,7 +270,7 @@ public void prepareTestInstance(Object testInstance) throws Exception { * {@link TestExecutionListener} a chance to perform its pre-processing. * If a listener throws an exception, however, the remaining registered * listeners will not be called. - * @param testInstance the current test instance (never {@code null}) + * @param testInstance the current test instance * @param testMethod the test method which is about to be executed on the * test instance * @throws Exception if a registered TestExecutionListener throws an exception @@ -305,15 +299,15 @@ public void beforeTestMethod(Object testInstance, Method testMethod) throws Exce * {@linkplain TestContext test context} — for example, for timing * or logging purposes. *

    This method must be called after framework-specific - * before lifecycle callbacks (e.g., methods annotated with JUnit 4's - * {@link org.junit.Before @Before}). + * before lifecycle callbacks — for example, methods annotated + * with JUnit Jupiter's {@link org.junit.jupiter.api.BeforeEach @BeforeEach}. *

    The managed {@link TestContext} will be updated with the supplied * {@code testInstance} and {@code testMethod}. *

    An attempt will be made to give each registered * {@link TestExecutionListener} a chance to perform its pre-processing. * If a listener throws an exception, however, the remaining registered * listeners will not be called. - * @param testInstance the current test instance (never {@code null}) + * @param testInstance the current test instance * @param testMethod the test method which is about to be executed on the * test instance * @throws Exception if a registered TestExecutionListener throws an exception @@ -344,8 +338,8 @@ public void beforeTestExecution(Object testInstance, Method testMethod) throws E * {@linkplain TestContext test context} — for example, for timing * or logging purposes. *

    This method must be called before framework-specific - * after lifecycle callbacks (e.g., methods annotated with JUnit 4's - * {@link org.junit.After @After}). + * after lifecycle callbacks — for example, methods annotated + * with JUnit Jupiter's {@link org.junit.jupiter.api.AfterEach @AfterEach}. *

    The managed {@link TestContext} will be updated with the supplied * {@code testInstance}, {@code testMethod}, and {@code exception}. *

    Each registered {@link TestExecutionListener} will be given a chance @@ -356,7 +350,7 @@ public void beforeTestExecution(Object testInstance, Method testMethod) throws E * the first exception. *

    Note that registered listeners will be executed in the opposite * order in which they were registered. - * @param testInstance the current test instance (never {@code null}) + * @param testInstance the current test instance * @param testMethod the test method which has just been executed on the * test instance * @param exception the exception that was thrown during execution of the @@ -404,10 +398,10 @@ public void afterTestExecution(Object testInstance, Method testMethod, @Nullable * lifecycle callbacks of the underlying test framework — for example, * tearing down test fixtures, ending a transaction, etc. *

    This method must be called immediately after - * framework-specific after lifecycle callbacks (e.g., methods - * annotated with JUnit 4's {@link org.junit.After @After}). For historical - * reasons, this method is named {@code afterTestMethod}. Since the - * introduction of {@link #afterTestExecution}, a more suitable name for + * framework-specific after lifecycle callbacks — for example, methods + * annotated with JUnit Jupiter's {@link org.junit.jupiter.api.AfterEach @AfterEach}. + * For historical reasons, this method is named {@code afterTestMethod}. Since + * the introduction of {@link #afterTestExecution}, a more suitable name for * this method might be something like {@code afterTestTearDown} or * {@code afterEach}; however, it is unfortunately impossible to rename * this method due to backward compatibility concerns. @@ -420,7 +414,7 @@ public void afterTestExecution(Object testInstance, Method testMethod, @Nullable * subsequent exceptions {@linkplain Throwable#addSuppressed suppressed} in * the first exception. *

    Note that registered listeners will be executed in the opposite - * @param testInstance the current test instance (never {@code null}) + * @param testInstance the current test instance * @param testMethod the test method which has just been executed on the * test instance * @param exception the exception that was thrown during execution of the test @@ -464,8 +458,8 @@ public void afterTestMethod(Object testInstance, Method testMethod, @Nullable Th /** * Hook for post-processing a test class after execution of all * tests within the class. Should be called after any framework-specific - * after class methods (e.g., methods annotated with JUnit 4's - * {@link org.junit.AfterClass @AfterClass}). + * after class methods — for example, methods annotated with + * JUnit Jupiter's {@link org.junit.jupiter.api.AfterAll @AfterAll}. *

    Each registered {@link TestExecutionListener} will be given a chance * to perform its post-processing. If a listener throws an exception, the * remaining registered listeners will still be called. After all listeners @@ -481,7 +475,7 @@ public void afterTestMethod(Object testInstance, Method testMethod, @Nullable Th public void afterTestClass() throws Exception { Class testClass = getTestContext().getTestClass(); if (logger.isTraceEnabled()) { - logger.trace("afterTestClass(): class [" + testClass.getName() + "]"); + logger.trace("afterTestClass(): class [" + typeName(testClass) + "]"); } getTestContext().updateState(null, null, null); @@ -512,7 +506,7 @@ public void afterTestClass() throws Exception { private void prepareForBeforeCallback(String callbackName, Object testInstance, Method testMethod) { if (logger.isTraceEnabled()) { - logger.trace(String.format("%s(): instance [%s], method [%s]", callbackName, testInstance, testMethod)); + logger.trace("%s(): instance [%s], method [%s]".formatted(callbackName, testInstance, testMethod)); } getTestContext().updateState(testInstance, testMethod, null); } @@ -521,8 +515,8 @@ private void prepareForAfterCallback(String callbackName, Object testInstance, M @Nullable Throwable exception) { if (logger.isTraceEnabled()) { - logger.trace(String.format("%s(): instance [%s], method [%s], exception [%s]", - callbackName, testInstance, testMethod, exception)); + logger.trace("%s(): instance [%s], method [%s], exception [%s]" + .formatted(callbackName, testInstance, testMethod, exception)); } getTestContext().updateState(testInstance, testMethod, exception); } @@ -538,9 +532,10 @@ private void logException( Throwable ex, String callbackName, TestExecutionListener testExecutionListener, Class testClass) { if (logger.isWarnEnabled()) { - logger.warn(String.format("Caught exception while invoking '%s' callback on " + - "TestExecutionListener [%s] for test class [%s]", callbackName, testExecutionListener, - testClass), ex); + logger.warn(""" + Caught exception while invoking '%s' callback on TestExecutionListener [%s] \ + for test class [%s]""" + .formatted(callbackName, typeName(testExecutionListener), typeName(testClass)), ex); } } @@ -548,9 +543,10 @@ private void logException(Throwable ex, String callbackName, TestExecutionListen Object testInstance, Method testMethod) { if (logger.isWarnEnabled()) { - logger.warn(String.format("Caught exception while invoking '%s' callback on " + - "TestExecutionListener [%s] for test method [%s] and test instance [%s]", - callbackName, testExecutionListener, testMethod, testInstance), ex); + logger.warn(""" + Caught exception while invoking '%s' callback on TestExecutionListener [%s] for \ + test method [%s] and test instance [%s]""" + .formatted(callbackName, typeName(testExecutionListener), testMethod, testInstance), ex); } } @@ -570,9 +566,9 @@ private static TestContext copyTestContext(TestContext testContext) { } catch (Exception ex) { if (logger.isInfoEnabled()) { - logger.info(String.format("Failed to invoke copy constructor for [%s]; " + - "concurrent test execution is therefore likely not supported.", - testContext), ex); + logger.info(""" + Failed to invoke copy constructor for [%s]; concurrent test execution \ + is therefore likely not supported.""".formatted(testContext), ex); } } } @@ -581,4 +577,14 @@ private static TestContext copyTestContext(TestContext testContext) { return testContext; } + private static String typeName(Object obj) { + if (obj == null) { + return "null"; + } + if (obj instanceof Class type) { + return type.getName(); + } + return obj.getClass().getName(); + } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java index 875e6abf8583..337df27254fb 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java @@ -112,11 +112,11 @@ * {@code Environment}'s set of {@code PropertySources}. Each location * will be added to the enclosing {@code Environment} as its own property * source, in the order declared. - *

    Supported File Formats

    + *

    Supported File Formats

    *

    Both traditional and XML-based properties file formats are supported * — for example, {@code "classpath:/com/example/test.properties"} * or {@code "file:/path/to/file.xml"}. - *

    Path Resource Semantics

    + *

    Path Resource Semantics

    *

    Each path will be interpreted as a Spring * {@link org.springframework.core.io.Resource Resource}. A plain path * — for example, {@code "test.properties"} — will be treated as a @@ -134,9 +134,9 @@ * in paths (i.e., ${...}) will be * {@linkplain org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) resolved} * against the {@code Environment}. - *

    Default Properties File Detection

    + *

    Default Properties File Detection

    *

    See the class-level Javadoc for a discussion on detection of defaults. - *

    Precedence

    + *

    Precedence

    *

    Properties loaded from resource locations have lower precedence than * inlined {@link #properties}. *

    This attribute may not be used in conjunction with @@ -209,7 +209,7 @@ * {@code ApplicationContext} is loaded for the test. All key-value pairs * will be added to the enclosing {@code Environment} as a single test * {@code PropertySource} with the highest precedence. - *

    Supported Syntax

    + *

    Supported Syntax

    *

    The supported syntax for key-value pairs is the same as the * syntax defined for entries in a Java * {@linkplain java.util.Properties#load(java.io.Reader) properties file}: @@ -218,7 +218,7 @@ *

  • {@code "key:value"}
  • *
  • {@code "key value"}
  • * - *

    Precedence

    + *

    Precedence

    *

    Properties declared via this attribute have higher precedence than * properties loaded from resource {@link #locations}. *

    This attribute may be used in conjunction with {@link #value} diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java index fac0cfc6df2f..b8d40fb3aed5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java @@ -16,7 +16,10 @@ package org.springframework.test.context.aot; +import java.util.Arrays; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; @@ -30,12 +33,19 @@ import org.springframework.aot.generate.GeneratedFiles; import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.TypeReference; +import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.aot.AotServices; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.context.aot.ApplicationContextAotGenerator; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.core.log.LogMessage; import org.springframework.javapoet.ClassName; import org.springframework.test.context.BootstrapUtils; @@ -117,16 +127,36 @@ public void processAheadOfTime(Stream> testClasses) throws TestContextA try { resetAotFactories(); + Set> coreRuntimeHintsRegistrarClasses = new LinkedHashSet<>(); + ReflectiveRuntimeHintsRegistrar reflectiveRuntimeHintsRegistrar = new ReflectiveRuntimeHintsRegistrar(); + MultiValueMap> mergedConfigMappings = new LinkedMultiValueMap<>(); ClassLoader classLoader = getClass().getClassLoader(); testClasses.forEach(testClass -> { MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); mergedConfigMappings.add(mergedConfig, testClass); - this.testRuntimeHintsRegistrars.forEach(registrar -> - registrar.registerHints(this.runtimeHints, testClass, classLoader)); + collectRuntimeHintsRegistrarClasses(testClass, coreRuntimeHintsRegistrarClasses); + reflectiveRuntimeHintsRegistrar.registerRuntimeHints(this.runtimeHints, testClass); + this.testRuntimeHintsRegistrars.forEach(registrar -> { + if (logger.isTraceEnabled()) { + logger.trace("Processing RuntimeHints contribution from class [%s]" + .formatted(registrar.getClass().getCanonicalName())); + } + registrar.registerHints(this.runtimeHints, testClass, classLoader); + }); }); - MultiValueMap> initializerClassMappings = processAheadOfTime(mergedConfigMappings); + coreRuntimeHintsRegistrarClasses.stream() + .map(BeanUtils::instantiateClass) + .forEach(registrar -> { + if (logger.isTraceEnabled()) { + logger.trace("Processing RuntimeHints contribution from class [%s]" + .formatted(registrar.getClass().getCanonicalName())); + } + registrar.registerHints(this.runtimeHints, classLoader); + }); + + MultiValueMap> initializerClassMappings = processAheadOfTime(mergedConfigMappings); generateAotTestContextInitializerMappings(initializerClassMappings); generateAotTestAttributeMappings(); } @@ -135,6 +165,25 @@ public void processAheadOfTime(Stream> testClasses) throws TestContextA } } + /** + * Collect all {@link RuntimeHintsRegistrar} classes declared via + * {@link ImportRuntimeHints @ImportRuntimeHints} on the supplied test class + * and add them to the supplied {@link Set}. + * @param testClass the test class on which to search for {@code @ImportRuntimeHints} + * @param coreRuntimeHintsRegistrarClasses the set of registrar classes + */ + private void collectRuntimeHintsRegistrarClasses( + Class testClass, Set> coreRuntimeHintsRegistrarClasses) { + + MergedAnnotations.from(testClass, SearchStrategy.TYPE_HIERARCHY) + .stream(ImportRuntimeHints.class) + .filter(MergedAnnotation::isPresent) + .map(MergedAnnotation::synthesize) + .map(ImportRuntimeHints::value) + .flatMap(Arrays::stream) + .forEach(coreRuntimeHintsRegistrarClasses::add); + } + private void resetAotFactories() { AotTestAttributesFactory.reset(); AotTestContextInitializersFactory.reset(); @@ -205,7 +254,7 @@ private GenericApplicationContext loadContextForAotProcessing( Class testClass = mergedConfig.getTestClass(); ContextLoader contextLoader = mergedConfig.getContextLoader(); - Assert.notNull(contextLoader, """ + Assert.notNull(contextLoader, () -> """ Cannot load an ApplicationContext with a NULL 'contextLoader'. \ Consider annotating test class [%s] with @ContextConfiguration or \ @ContextHierarchy.""".formatted(testClass.getName())); diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestRuntimeHintsRegistrar.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestRuntimeHintsRegistrar.java index e347d8cd655f..22a87c769b7b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/TestRuntimeHintsRegistrar.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestRuntimeHintsRegistrar.java @@ -34,6 +34,12 @@ * specific to particular test classes, favor implementing {@code RuntimeHintsRegistrar} * over this API. * + *

    As an alternative to implementing and registering a {@code TestRuntimeHintsRegistrar}, + * you may choose to annotate a test class with + * {@link org.springframework.aot.hint.annotation.Reflective @Reflective}, + * {@link org.springframework.aot.hint.annotation.RegisterReflectionForBinding @RegisterReflectionForBinding}, + * or {@link org.springframework.context.annotation.ImportRuntimeHints @ImportRuntimeHints}. + * * @author Sam Brannen * @since 6.0 * @see org.springframework.aot.hint.RuntimeHintsRegistrar diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java index c41719720f95..1278b3388e16 100644 --- a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java +++ b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java @@ -21,6 +21,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.aot.AotDetector; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; @@ -184,7 +185,7 @@ protected ApplicationContext loadContextInternal(MergedContextConfiguration merg } else { String[] locations = mergedContextConfiguration.getLocations(); - Assert.notNull(locations, """ + Assert.notNull(locations, () -> """ Cannot load an ApplicationContext with a NULL 'locations' array. \ Consider annotating test class [%s] with @ContextConfiguration or \ @ContextHierarchy.""".formatted(mergedContextConfiguration.getTestClass().getName())); @@ -224,7 +225,7 @@ else if (logger.isDebugEnabled()) { private ContextLoader getContextLoader(MergedContextConfiguration mergedConfig) { ContextLoader contextLoader = mergedConfig.getContextLoader(); - Assert.notNull(contextLoader, """ + Assert.notNull(contextLoader, () -> """ Cannot load an ApplicationContext with a NULL 'contextLoader'. \ Consider annotating test class [%s] with @ContextConfiguration or \ @ContextHierarchy.""".formatted(mergedConfig.getTestClass().getName())); @@ -239,14 +240,23 @@ private ContextLoader getContextLoader(MergedContextConfiguration mergedConfig) * unmodified. *

    This allows for transparent {@link org.springframework.test.context.cache.ContextCache ContextCache} * support for AOT-optimized application contexts. + * @param mergedConfig the original {@code MergedContextConfiguration} + * @return {@code AotMergedContextConfiguration} or the original {@code MergedContextConfiguration} + * @throws IllegalStateException if running in AOT mode and the test class does not + * have an AOT-optimized {@code ApplicationContext} * @since 6.0 */ @SuppressWarnings("unchecked") private MergedContextConfiguration replaceIfNecessary(MergedContextConfiguration mergedConfig) { - Class testClass = mergedConfig.getTestClass(); - if (this.aotTestContextInitializers.isSupportedTestClass(testClass)) { + if (AotDetector.useGeneratedArtifacts()) { + Class testClass = mergedConfig.getTestClass(); Class> contextInitializerClass = this.aotTestContextInitializers.getContextInitializerClass(testClass); + Assert.state(contextInitializerClass != null, () -> """ + Failed to load AOT ApplicationContextInitializer class for test class [%s]. \ + This can occur if AOT processing has not taken place for the test suite. It \ + can also occur if AOT processing failed for the test class, in which case you \ + can consult the logs generated during AOT processing.""".formatted(testClass.getName())); return new AotMergedContextConfiguration(testClass, contextInitializerClass, mergedConfig, this); } return mergedConfig; diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java index 9a205249586f..bfb1b70452f1 100644 --- a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java +++ b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -203,8 +203,8 @@ private void remove(List removedContexts, MergedCont // Physically remove and close leaf nodes first (i.e., on the way back up the // stack as opposed to prior to the recursive call). ApplicationContext context = this.contextMap.remove(key); - if (context instanceof ConfigurableApplicationContext) { - ((ConfigurableApplicationContext) context).close(); + if (context instanceof ConfigurableApplicationContext cac) { + cac.close(); } removedContexts.add(key); } diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java index 7fdeb98ef2e9..3e49b71b39cb 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java @@ -291,7 +291,7 @@ private static boolean isExplicitValue(@Nullable Object value) { } private static boolean isEmptyString(@Nullable Object value) { - return (value instanceof String && ((String) value).isEmpty()); + return (value instanceof String str && str.isEmpty()); } private static boolean isEmptyArray(@Nullable Object value) { diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java index 7e167a79ada0..546256efa6c7 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java @@ -91,7 +91,7 @@ * {@link #value}, but it may be used instead of {@link #value}. Similarly, * this attribute may be used in conjunction with or instead of * {@link #statements}. - *

    Path Resource Semantics

    + *

    Path Resource Semantics

    *

    Each path will be interpreted as a Spring * {@link org.springframework.core.io.Resource Resource}. A plain path * — for example, {@code "schema.sql"} — will be treated as a @@ -103,7 +103,7 @@ * {@link org.springframework.util.ResourceUtils#CLASSPATH_URL_PREFIX classpath:}, * {@link org.springframework.util.ResourceUtils#FILE_URL_PREFIX file:}, * {@code http:}, etc.) will be loaded using the specified resource protocol. - *

    Default Script Detection

    + *

    Default Script Detection

    *

    If no SQL scripts or {@link #statements} are specified, an attempt will * be made to detect a default script depending on where this * annotation is declared. If a default cannot be detected, an @@ -127,7 +127,7 @@ * Inlined SQL statements to execute. *

    This attribute may be used in conjunction with or instead of * {@link #scripts}. - *

    Ordering

    + *

    Ordering

    *

    Statements declared via this attribute will be executed after * statements loaded from resource {@link #scripts}. If you wish to have * inlined statements executed before scripts, simply declare multiple diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/AbstractExpressionEvaluatingCondition.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/AbstractExpressionEvaluatingCondition.java index 22a9cfa8e332..09db2ae79052 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/AbstractExpressionEvaluatingCondition.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/AbstractExpressionEvaluatingCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -93,7 +93,7 @@ protected ConditionEvaluationResult evaluateAnnotation(Cl AnnotatedElement element = context.getElement().get(); Optional annotation = findMergedAnnotation(element, annotationType); - if (!annotation.isPresent()) { + if (annotation.isEmpty()) { String reason = String.format("%s is enabled since @%s is not present", element, annotationType.getSimpleName()); if (logger.isDebugEnabled()) { @@ -138,8 +138,7 @@ protected ConditionEvaluationResult evaluateAnnotation(Cl // since the DirtiesContextTestExecutionListener will never be invoked for // a disabled test class. // See https://github.com/spring-projects/spring-framework/issues/26694 - if (loadContext && result.isDisabled() && element instanceof Class) { - Class testClass = (Class) element; + if (loadContext && result.isDisabled() && element instanceof Class testClass) { DirtiesContext dirtiesContext = TestContextAnnotationUtils.findMergedAnnotation(testClass, DirtiesContext.class); if (dirtiesContext != null) { HierarchyMode hierarchyMode = dirtiesContext.hierarchyMode(); @@ -167,7 +166,7 @@ private boolean evaluateExpression(String expression, boo applicationContext = gac; } - if (!(applicationContext instanceof ConfigurableApplicationContext)) { + if (!(applicationContext instanceof ConfigurableApplicationContext cac)) { if (logger.isWarnEnabled()) { String contextType = applicationContext.getClass().getName(); logger.warn(String.format("@%s(\"%s\") could not be evaluated on [%s] since the test " + @@ -177,7 +176,7 @@ private boolean evaluateExpression(String expression, boo return false; } - ConfigurableBeanFactory configurableBeanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); + ConfigurableBeanFactory configurableBeanFactory = cac.getBeanFactory(); BeanExpressionResolver expressionResolver = configurableBeanFactory.getBeanExpressionResolver(); Assert.state(expressionResolver != null, "No BeanExpressionResolver"); BeanExpressionContext beanExpressionContext = new BeanExpressionContext(configurableBeanFactory, null); @@ -189,11 +188,11 @@ private boolean evaluateExpression(String expression, boo gac.close(); } - if (result instanceof Boolean) { - return (Boolean) result; + if (result instanceof Boolean b) { + return b; } - else if (result instanceof String) { - String str = ((String) result).trim().toLowerCase(); + else if (result instanceof String str) { + str = str.trim().toLowerCase(); if ("true".equals(str)) { return true; } diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java index 66e9c3a16ba4..904618a68d18 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java @@ -59,7 +59,7 @@ * Spring TestContext Framework to standard JUnit tests by means of the * {@link TestContextManager} and associated support classes and annotations. * - *

    To use this class, simply annotate a JUnit 4 based test class with + *

    To use this class, annotate a JUnit 4 based test class with * {@code @RunWith(SpringJUnit4ClassRunner.class)} or {@code @RunWith(SpringRunner.class)}. * *

    The following list constitutes all annotations currently supported directly @@ -82,7 +82,7 @@ *

    If you would like to use the Spring TestContext Framework with a runner * other than this one, use {@link SpringClassRule} and {@link SpringMethodRule}. * - *

    NOTE: As of Spring Framework 4.3, this class requires JUnit 4.12 or higher. + *

    NOTE: This class requires JUnit 4.12 or higher. * * @author Sam Brannen * @author Juergen Hoeller @@ -268,8 +268,7 @@ protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) { * JUnit-specified timeouts will work fine in combination with Spring * transactions. However, JUnit-specific timeouts still differ from * Spring-specific timeouts in that the former execute in a separate - * thread while the latter simply execute in the main thread (like regular - * tests). + * thread while the latter execute in the main thread (like regular tests). * @see #methodInvoker(FrameworkMethod, Object) * @see #withBeforeTestExecutionCallbacks(FrameworkMethod, Object, Statement) * @see #withAfterTestExecutionCallbacks(FrameworkMethod, Object, Statement) diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringRunner.java b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringRunner.java index b6d87e566b21..dd6ca659bc91 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringRunner.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. @@ -21,7 +21,7 @@ /** * {@code SpringRunner} is an alias for the {@link SpringJUnit4ClassRunner}. * - *

    To use this class, simply annotate a JUnit 4 based test class with + *

    To use this class, annotate a JUnit 4 based test class with * {@code @RunWith(SpringRunner.class)}. * *

    If you would like to use the Spring TestContext Framework with a runner other than diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java index 90bae04ef82c..f2c5f31dce93 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,7 +72,7 @@ *

  • {@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}
  • * * - *

    NOTE: As of Spring Framework 4.3, this class requires JUnit 4.12 or higher. + *

    NOTE: This class requires JUnit 4.12 or higher. * * @author Sam Brannen * @author Philippe Marschall diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java index 57478d4bd8a6..bb205c7cd82c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -76,7 +76,7 @@ *

  • {@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}
  • * * - *

    NOTE: As of Spring Framework 4.3, this class requires JUnit 4.12 or higher. + *

    NOTE: This class requires JUnit 4.12 or higher. * *

    WARNING: Due to the shortcomings of JUnit rules, the * {@code SpringMethodRule} diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestExecutionCallbacks.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestExecutionCallbacks.java index 344ec19d3e39..f95eb5a3823e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestExecutionCallbacks.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestExecutionCallbacks.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -28,6 +28,8 @@ * JUnit 4 execution chain by calling {@link TestContextManager#beforeTestExecution * beforeTestExecution()} on the supplied {@link TestContextManager}. * + *

    NOTE: This class requires JUnit 4.9 or higher. + * * @author Sam Brannen * @since 5.0 * @see #evaluate() diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java index 5aa4d67dc648..17336b18bbdf 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java @@ -188,7 +188,7 @@ else if (logger.isDebugEnabled()) { * register a JVM shutdown hook for it * @return a new application context */ - private final GenericApplicationContext loadContext( + private GenericApplicationContext loadContext( MergedContextConfiguration mergedConfig, boolean forAotProcessing) throws Exception { if (logger.isTraceEnabled()) { diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java index 9c59804ef0b9..963c826c6daf 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -86,8 +86,8 @@ public static boolean isAutowirableConstructor(Constructor constructor, Class public static boolean isAutowirableConstructor(Executable executable, Class testClass, @Nullable PropertyProvider fallbackPropertyProvider) { - return (executable instanceof Constructor && - isAutowirableConstructor((Constructor) executable, testClass, fallbackPropertyProvider)); + return (executable instanceof Constructor constructor && + isAutowirableConstructor(constructor, testClass, fallbackPropertyProvider)); } /** diff --git a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTestNGSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTestNGSpringContextTests.java index 269f82a06986..aa334031f18c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTestNGSpringContextTests.java +++ b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTestNGSpringContextTests.java @@ -201,7 +201,7 @@ protected void springTestContextAfterTestClass() throws Exception { private Throwable getTestResultException(ITestResult testResult) { Throwable testResultException = testResult.getThrowable(); if (testResultException instanceof InvocationTargetException) { - testResultException = ((InvocationTargetException) testResultException).getCause(); + testResultException = testResultException.getCause(); } return testResultException; } diff --git a/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java b/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java index 0b6730198930..ba809f2131a0 100644 --- a/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java index 8975ef8f5808..6992cf06285c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -32,7 +32,6 @@ import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.util.Assert; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @@ -193,9 +192,11 @@ private void setUpRequestContextIfNecessary(TestContext testContext) { if (context instanceof WebApplicationContext wac) { ServletContext servletContext = wac.getServletContext(); - Assert.state(servletContext instanceof MockServletContext, () -> String.format( - "The WebApplicationContext for test context %s must be configured with a MockServletContext.", - testContext)); + if (!(servletContext instanceof MockServletContext mockServletContext)) { + throw new IllegalStateException( + "The WebApplicationContext for test context %s must be configured with a MockServletContext." + .formatted(testContext)); + } if (logger.isTraceEnabled()) { logger.trace("Setting up MockHttpServletRequest, MockHttpServletResponse, ServletWebRequest, " + @@ -206,7 +207,6 @@ else if (logger.isDebugEnabled()) { "and RequestContextHolder for test class " + testContext.getTestClass().getName()); } - MockServletContext mockServletContext = (MockServletContext) servletContext; MockHttpServletRequest request = new MockHttpServletRequest(mockServletContext); request.setAttribute(CREATED_BY_THE_TESTCONTEXT_FRAMEWORK, Boolean.TRUE); MockHttpServletResponse response = new MockHttpServletResponse(); diff --git a/spring-test/src/main/java/org/springframework/test/util/TestSocketUtils.java b/spring-test/src/main/java/org/springframework/test/util/TestSocketUtils.java new file mode 100644 index 000000000000..dbf8e477fe98 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/util/TestSocketUtils.java @@ -0,0 +1,139 @@ +/* + * Copyright 2002-2022 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.test.util; + +import java.net.InetAddress; +import java.net.ServerSocket; +import java.util.Random; + +import javax.net.ServerSocketFactory; + +import org.springframework.util.Assert; + +/** + * Simple utility for finding available TCP ports on {@code localhost} for use in + * integration testing scenarios. + * + *

    This is a limited form of {@link org.springframework.util.SocketUtils} which + * has been deprecated since Spring Framework 5.3.16 and removed in Spring + * Framework 6.0. + * + *

    {@code TestSocketUtils} can be used in integration tests which start an + * external server on an available random port. However, these utilities make no + * guarantee about the subsequent availability of a given port and are therefore + * unreliable. Instead of using {@code TestSocketUtils} to find an available local + * port for a server, it is recommended that you rely on a server's ability to + * start on a random ephemeral port that it selects or is assigned by the + * operating system. To interact with that server, you should query the server + * for the port it is currently using. + * + * @author Sam Brannen + * @author Ben Hale + * @author Arjen Poutsma + * @author Gunnar Hillert + * @author Gary Russell + * @author Chris Bono + * @since 5.3.24 + */ +public class TestSocketUtils { + + /** + * The minimum value for port ranges used when finding an available TCP port. + */ + static final int PORT_RANGE_MIN = 1024; + + /** + * The maximum value for port ranges used when finding an available TCP port. + */ + static final int PORT_RANGE_MAX = 65535; + + private static final int PORT_RANGE_PLUS_ONE = PORT_RANGE_MAX - PORT_RANGE_MIN + 1; + + private static final int MAX_ATTEMPTS = 1_000; + + private static final Random random = new Random(System.nanoTime()); + + private static final TestSocketUtils INSTANCE = new TestSocketUtils(); + + + /** + * Although {@code TestSocketUtils} consists solely of static utility methods, + * this constructor is intentionally {@code public}. + *

    Rationale
    + *

    Static methods from this class may be invoked from within XML + * configuration files using the Spring Expression Language (SpEL) and the + * following syntax. + *

    
    +	 * <bean id="myBean" ... p:port="#{T(org.springframework.test.util.TestSocketUtils).findAvailableTcpPort()}" />
    +	 * 
    + *

    If this constructor were {@code private}, you would be required to supply + * the fully qualified class name to SpEL's {@code T()} function for each usage. + * Thus, the fact that this constructor is {@code public} allows you to reduce + * boilerplate configuration with SpEL as can be seen in the following example. + *

    
    +	 * <bean id="socketUtils" class="org.springframework.test.util.TestSocketUtils" />
    +	 * <bean id="myBean" ... p:port="#{socketUtils.findAvailableTcpPort()}" />
    +	 * 
    + */ + public TestSocketUtils() { + } + + /** + * Find an available TCP port randomly selected from the range [1024, 65535]. + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort() { + return INSTANCE.findAvailableTcpPortInternal(); + } + + + /** + * Internal implementation of {@link #findAvailableTcpPort()}. + *

    Package-private solely for testing purposes. + */ + int findAvailableTcpPortInternal() { + int candidatePort; + int searchCounter = 0; + do { + Assert.state(++searchCounter <= MAX_ATTEMPTS, () -> String.format( + "Could not find an available TCP port in the range [%d, %d] after %d attempts", + PORT_RANGE_MIN, PORT_RANGE_MAX, MAX_ATTEMPTS)); + candidatePort = PORT_RANGE_MIN + random.nextInt(PORT_RANGE_PLUS_ONE); + } + while (!isPortAvailable(candidatePort)); + + return candidatePort; + } + + /** + * Determine if the specified TCP port is currently available on {@code localhost}. + *

    Package-private solely for testing purposes. + */ + boolean isPortAvailable(int port) { + try { + ServerSocket serverSocket = ServerSocketFactory.getDefault() + .createServerSocket(port, 1, InetAddress.getByName("localhost")); + serverSocket.close(); + return true; + } + catch (Exception ex) { + return false; + } + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/util/XmlExpectationsHelper.java b/spring-test/src/main/java/org/springframework/test/util/XmlExpectationsHelper.java index 5241d57a05e7..05ce30153583 100644 --- a/spring-test/src/main/java/org/springframework/test/util/XmlExpectationsHelper.java +++ b/spring-test/src/main/java/org/springframework/test/util/XmlExpectationsHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -82,7 +82,7 @@ public void assertSource(String content, Matcher matcher) throws public void assertXmlEqual(String expected, String actual) throws Exception { XmlUnitDiff diff = new XmlUnitDiff(expected, actual); if (diff.hasDifferences()) { - AssertionErrors.fail("Body content " + diff.toString()); + AssertionErrors.fail("Body content " + diff); } } diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java index 79386749e921..41e8f195e2a8 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java @@ -235,8 +235,8 @@ private RequestMatcher multipartData(MultiValueMap expectedMap, boole for (int i = 0; i < values.size(); i++) { Object expected = values.get(i); Object actual = actualMap.get(name).get(i); - if (expected instanceof Resource) { - expected = StreamUtils.copyToByteArray(((Resource) expected).getInputStream()); + if (expected instanceof Resource resource) { + expected = StreamUtils.copyToByteArray(resource.getInputStream()); } if (expected instanceof byte[]) { assertTrue("Multipart is not a file", actual instanceof byte[]); diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/JsonPathRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/JsonPathRequestMatchers.java index 55050cdc9aaa..43714408aa53 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/match/JsonPathRequestMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/match/JsonPathRequestMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -52,7 +52,7 @@ public class JsonPathRequestMatchers { * @param args arguments to parameterize the {@code JsonPath} expression with, * using formatting specifiers defined in {@link String#format(String, Object...)} */ - protected JsonPathRequestMatchers(String expression, Object ... args) { + protected JsonPathRequestMatchers(String expression, Object... args) { this.jsonPathHelper = new JsonPathExpectationsHelper(expression, args); } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java index 31b3d6c37d41..f3c9e3650af0 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java @@ -26,6 +26,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector; +import org.springframework.http.client.reactive.JdkClientHttpConnector; import org.springframework.http.client.reactive.JettyClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.http.client.reactive.ReactorNetty2ClientHttpConnector; @@ -317,7 +318,9 @@ else if (jettyClientPresent) { else if (httpComponentsClientPresent) { return new HttpComponentsClientHttpConnector(); } - throw new IllegalStateException("No suitable default ClientHttpConnector found"); + else { + return new JdkClientHttpConnector(); + } } private ExchangeStrategies initExchangeStrategies() { diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WiretapConnector.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WiretapConnector.java index f0120d3a676b..64830f879b8b 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WiretapConnector.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WiretapConnector.java @@ -89,10 +89,8 @@ public Mono connect(HttpMethod method, URI uri, */ ExchangeResult getExchangeResult(String requestId, @Nullable String uriTemplate, Duration timeout) { ClientExchangeInfo clientInfo = this.exchanges.remove(requestId); - Assert.state(clientInfo != null, () -> { - String header = WebTestClient.WEBTESTCLIENT_REQUEST_ID; - return "No match for " + header + "=" + requestId; - }); + Assert.state(clientInfo != null, () -> "No match for %s=%s".formatted( + WebTestClient.WEBTESTCLIENT_REQUEST_ID, requestId)); return new ExchangeResult(clientInfo.getRequest(), clientInfo.getResponse(), clientInfo.getRequest().getRecorder().getContent(), clientInfo.getResponse().getRecorder().getContent(), @@ -282,8 +280,8 @@ public Flux getBody() { @Nullable public Object getMockServerResult() { - return (getDelegate() instanceof MockServerClientHttpResponse ? - ((MockServerClientHttpResponse) getDelegate()).getServerResult() : null); + return (getDelegate() instanceof MockServerClientHttpResponse mockResponse ? + mockResponse.getServerResult() : null); } } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/MockMvc.java b/spring-test/src/main/java/org/springframework/test/web/servlet/MockMvc.java index 158b12b27d1e..7acf46ba1529 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/MockMvc.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/MockMvc.java @@ -165,8 +165,8 @@ public DispatcherServlet getDispatcherServlet() { * @see org.springframework.test.web.servlet.result.MockMvcResultMatchers */ public ResultActions perform(RequestBuilder requestBuilder) throws Exception { - if (this.defaultRequestBuilder != null && requestBuilder instanceof Mergeable) { - requestBuilder = (RequestBuilder) ((Mergeable) requestBuilder).merge(this.defaultRequestBuilder); + if (this.defaultRequestBuilder != null && requestBuilder instanceof Mergeable mergeable) { + requestBuilder = (RequestBuilder) mergeable.merge(this.defaultRequestBuilder); } MockHttpServletRequest request = requestBuilder.buildRequest(this.servletContext); @@ -187,8 +187,8 @@ public ResultActions perform(RequestBuilder requestBuilder) throws Exception { mockResponse.setDefaultCharacterEncoding(this.defaultResponseCharacterEncoding.name()); } - if (requestBuilder instanceof SmartRequestBuilder) { - request = ((SmartRequestBuilder) requestBuilder).postProcessRequest(request); + if (requestBuilder instanceof SmartRequestBuilder smartRequestBuilder) { + request = smartRequestBuilder.postProcessRequest(request); } MvcResult mvcResult = new DefaultMvcResult(request, mockResponse); @@ -227,8 +227,8 @@ public MvcResult andReturn() { } private MockHttpServletResponse unwrapResponseIfNecessary(ServletResponse servletResponse) { - while (servletResponse instanceof HttpServletResponseWrapper) { - servletResponse = ((HttpServletResponseWrapper) servletResponse).getResponse(); + while (servletResponse instanceof HttpServletResponseWrapper wrapper) { + servletResponse = wrapper.getResponse(); } Assert.isInstanceOf(MockHttpServletResponse.class, servletResponse); return (MockHttpServletResponse) servletResponse; diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/TestDispatcherServlet.java b/spring-test/src/main/java/org/springframework/test/web/servlet/TestDispatcherServlet.java index 145f224ff9a2..7956dae8a394 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/TestDispatcherServlet.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/TestDispatcherServlet.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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,8 +73,8 @@ protected void service(HttpServletRequest request, HttpServletResponse response) if (request.getAsyncContext() != null) { MockAsyncContext asyncContext; - if (request.getAsyncContext() instanceof MockAsyncContext) { - asyncContext = (MockAsyncContext) request.getAsyncContext(); + if (request.getAsyncContext() instanceof MockAsyncContext mockAsyncContext) { + asyncContext = mockAsyncContext; } else { MockHttpServletRequest mockRequest = WebUtils.getNativeRequest(request, MockHttpServletRequest.class); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java index 013b8027632f..adcb0115cfde 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java @@ -167,8 +167,8 @@ private MockHttpServletRequestBuilder initRequestBuilder( DataBufferUtils.release(buffer); // Adapt to jakarta.servlet.http.Part... - MockPart mockPart = (part instanceof FilePart ? - new MockPart(part.name(), ((FilePart) part).filename(), partBytes) : + MockPart mockPart = (part instanceof FilePart filePart ? + new MockPart(part.name(), filePart.filename(), partBytes) : new MockPart(part.name(), partBytes)); mockPart.getHeaders().putAll(part.headers()); requestBuilder.part(mockPart); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java index 4a58df0c0c4d..82ad85870977 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java @@ -36,7 +36,6 @@ import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder; -import org.springframework.util.Assert; import org.springframework.validation.Validator; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.context.WebApplicationContext; @@ -140,22 +139,25 @@ static WebTestClient.Builder bindTo(MockMvc mockMvc) { */ static ResultActions resultActionsFor(ExchangeResult exchangeResult) { Object serverResult = exchangeResult.getMockServerResult(); - Assert.notNull(serverResult, "No MvcResult"); - Assert.isInstanceOf(MvcResult.class, serverResult); + if (!(serverResult instanceof MvcResult mvcResult)) { + throw new IllegalArgumentException( + "Result from mock server exchange must be an instance of MvcResult instead of " + + (serverResult != null ? serverResult.getClass().getName() : "null")); + } return new ResultActions() { @Override public ResultActions andExpect(ResultMatcher matcher) throws Exception { - matcher.match((MvcResult) serverResult); + matcher.match(mvcResult); return this; } @Override public ResultActions andDo(ResultHandler handler) throws Exception { - handler.handle((MvcResult) serverResult); + handler.handle(mvcResult); return this; } @Override public MvcResult andReturn() { - return (MvcResult) serverResult; + return mvcResult; } }; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java index 218d0cc58456..77ea7563e16a 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java @@ -370,8 +370,7 @@ private void params(MockHttpServletRequest request) { } private void addRequestParameter(MockHttpServletRequest request, NameValuePair param) { - if (param instanceof KeyDataPair) { - KeyDataPair pair = (KeyDataPair) param; + if (param instanceof KeyDataPair pair) { File file = pair.getFile(); MockPart part; if (file != null) { @@ -424,17 +423,17 @@ public boolean isMergeEnabled() { @Override public Object merge(@Nullable Object parent) { - if (parent instanceof RequestBuilder) { + if (parent instanceof RequestBuilder requestBuilder) { if (parent instanceof MockHttpServletRequestBuilder) { MockHttpServletRequestBuilder copiedParent = MockMvcRequestBuilders.get("/"); copiedParent.merge(parent); this.parentBuilder = copiedParent; } else { - this.parentBuilder = (RequestBuilder) parent; + this.parentBuilder = requestBuilder; } - if (parent instanceof SmartRequestBuilder) { - this.parentPostProcessor = (SmartRequestBuilder) parent; + if (parent instanceof SmartRequestBuilder smartRequestBuilder) { + this.parentPostProcessor = smartRequestBuilder; } } return this; diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java index 6e3fd56b0b22..576dc6c50cd4 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java @@ -98,7 +98,7 @@ public class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque /** - * Create a new MockMultipartFile with the given content. + * Add a new {@link MockMultipartFile} with the given content. * @param name the name of the file * @param content the content of the file */ @@ -108,7 +108,7 @@ public MockMultipartHttpServletRequestBuilder file(String name, byte[] content) } /** - * Add the given MockMultipartFile. + * Add the given {@link MockMultipartFile}. * @param file the multipart file */ public MockMultipartHttpServletRequestBuilder file(MockMultipartFile file) { @@ -141,7 +141,6 @@ public Object merge(@Nullable Object parent) { parentBuilder.parts.keySet().forEach(name -> this.parts.putIfAbsent(name, parentBuilder.parts.get(name))); } - } else { throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]"); @@ -193,4 +192,5 @@ private Charset getCharsetOrDefault(Part part, Charset defaultCharset) { } return defaultCharset; } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/ContentResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/ContentResultMatchers.java index cd45f0920745..dcd08ba6de29 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/ContentResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/ContentResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java index d35d48f259fd..de9a86eacde4 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -33,7 +33,6 @@ import static org.springframework.test.util.AssertionErrors.assertEquals; import static org.springframework.test.util.AssertionErrors.assertNotNull; import static org.springframework.test.util.AssertionErrors.assertTrue; -import static org.springframework.test.util.AssertionErrors.fail; /** * Factory for assertions on the selected handler or handler method. @@ -68,8 +67,8 @@ public ResultMatcher handlerType(Class type) { Object handler = result.getHandler(); assertNotNull("No handler", handler); Class actual = handler.getClass(); - if (handler instanceof HandlerMethod) { - actual = ((HandlerMethod) handler).getBeanType(); + if (handler instanceof HandlerMethod handlerMethod) { + actual = handlerMethod.getBeanType(); } assertEquals("Handler type", type, ClassUtils.getUserClass(actual)); }; @@ -101,12 +100,12 @@ public ResultMatcher handlerType(Class type) { */ public ResultMatcher methodCall(Object obj) { return result -> { - if (!(obj instanceof MethodInvocationInfo)) { - fail(String.format("The supplied object [%s] is not an instance of %s. " + - "Ensure that you invoke the handler method via MvcUriComponentsBuilder.on().", - obj, MethodInvocationInfo.class.getName())); + if (!(obj instanceof MethodInvocationInfo invocationInfo)) { + throw new AssertionError(""" + The supplied object [%s] is not an instance of %s. Ensure \ + that you invoke the handler method via MvcUriComponentsBuilder.on().""" + .formatted(obj, MethodInvocationInfo.class.getName())); } - MethodInvocationInfo invocationInfo = (MethodInvocationInfo) obj; Method expected = invocationInfo.getControllerMethod(); Method actual = getHandlerMethod(result).getMethod(); assertEquals("Handler method", expected, actual); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/ModelResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/ModelResultMatchers.java index 6cb0fce1f889..599c05f09b5a 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/ModelResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/ModelResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -214,8 +214,8 @@ public ResultMatcher hasNoErrors() { return result -> { ModelAndView mav = getModelAndView(result); for (Object value : mav.getModel().values()) { - if (value instanceof Errors) { - assertFalse("Unexpected binding/validation errors: " + value, ((Errors) value).hasErrors()); + if (value instanceof Errors errors) { + assertFalse("Unexpected binding/validation errors: " + value, errors.hasErrors()); } } }; @@ -252,8 +252,8 @@ private BindingResult getBindingResult(ModelAndView mav, String name) { private int getErrorCount(ModelMap model) { int count = 0; for (Object value : model.values()) { - if (value instanceof Errors) { - count += ((Errors) value).getErrorCount(); + if (value instanceof Errors errors) { + count += errors.getErrorCount(); } } return count; diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/XpathResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/XpathResultMatchers.java index 4528d2b40d4d..60869f37e66a 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/XpathResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/XpathResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -52,7 +52,7 @@ public class XpathResultMatchers { * @param args arguments to parameterize the XPath expression with using the * formatting specifiers defined in {@link String#format(String, Object...)} */ - protected XpathResultMatchers(String expression, @Nullable Map namespaces, Object ... args) + protected XpathResultMatchers(String expression, @Nullable Map namespaces, Object... args) throws XPathExpressionException { this.xpathHelper = new XpathExpectationsHelper(expression, namespaces, args); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java index f8636fbd91e9..269ed8836aff 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -164,8 +164,8 @@ public final MockMvc build() { if (this.defaultRequestBuilder == null) { this.defaultRequestBuilder = MockMvcRequestBuilders.get("/"); } - if (this.defaultRequestBuilder instanceof ConfigurableSmartRequestBuilder) { - ((ConfigurableSmartRequestBuilder) this.defaultRequestBuilder).with(processor); + if (this.defaultRequestBuilder instanceof ConfigurableSmartRequestBuilder configurableBuilder) { + configurableBuilder.with(processor); } } } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java index 02dbfa93badc..9bbd3eed92f6 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -323,6 +323,14 @@ public A findAnnotationOnBean( return this.beanFactory.findAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit); } + @Override + public Set findAllAnnotationsOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + return this.beanFactory.findAllAnnotationsOnBean(beanName, annotationType, allowFactoryBeanInit); + } + //--------------------------------------------------------------------- // Implementation of HierarchicalBeanFactory interface @@ -402,8 +410,8 @@ private class StubBeanFactory extends StaticListableBeanFactory implements Autow @Override public Object initializeBean(Object existingBean, String beanName) throws BeansException { - if (existingBean instanceof ApplicationContextAware) { - ((ApplicationContextAware) existingBean).setApplicationContext(StubWebApplicationContext.this); + if (existingBean instanceof ApplicationContextAware applicationContextAware) { + applicationContextAware.setApplicationContext(StubWebApplicationContext.this); } return existingBean; } diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java index 19c428eae0c1..89a728f2f237 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -236,11 +236,11 @@ void setMultipleParameters() { params.put("key3", new String[] { "value3A", "value3B" }); request.setParameters(params); String[] values1 = request.getParameterValues("key1"); - assertThat(values1.length).isEqualTo(1); + assertThat(values1).hasSize(1); assertThat(request.getParameter("key1")).isEqualTo("newValue1"); assertThat(request.getParameter("key2")).isEqualTo("value2"); String[] values3 = request.getParameterValues("key3"); - assertThat(values3.length).isEqualTo(2); + assertThat(values3).hasSize(2); assertThat(values3[0]).isEqualTo("value3A"); assertThat(values3[1]).isEqualTo("value3B"); } @@ -254,12 +254,12 @@ void addMultipleParameters() { params.put("key3", new String[] { "value3A", "value3B" }); request.addParameters(params); String[] values1 = request.getParameterValues("key1"); - assertThat(values1.length).isEqualTo(2); + assertThat(values1).hasSize(2); assertThat(values1[0]).isEqualTo("value1"); assertThat(values1[1]).isEqualTo("newValue1"); assertThat(request.getParameter("key2")).isEqualTo("value2"); String[] values3 = request.getParameterValues("key3"); - assertThat(values3.length).isEqualTo(2); + assertThat(values3).hasSize(2); assertThat(values3[0]).isEqualTo("value3A"); assertThat(values3[1]).isEqualTo("value3B"); } @@ -271,9 +271,9 @@ void removeAllParameters() { params.put("key2", "value2"); params.put("key3", new String[] { "value3A", "value3B" }); request.addParameters(params); - assertThat(request.getParameterMap().size()).isEqualTo(3); + assertThat(request.getParameterMap()).hasSize(3); request.removeAllParameters(); - assertThat(request.getParameterMap().size()).isEqualTo(0); + assertThat(request.getParameterMap()).isEmpty(); } @Test diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java index 316c30132f2d..dd61a572dae0 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java @@ -252,7 +252,7 @@ void httpHeaderNameCasingIsPreserved() throws Exception { response.addHeader(headerName, "value1"); Collection responseHeaders = response.getHeaderNames(); assertThat(responseHeaders).isNotNull(); - assertThat(responseHeaders.size()).isEqualTo(1); + assertThat(responseHeaders).hasSize(1); assertThat(responseHeaders.iterator().next()).as("HTTP header casing not being preserved").isEqualTo(headerName); } @@ -280,7 +280,7 @@ void servletOutputStreamCommittedWhenBufferSizeExceeded() throws IOException { int size = response.getBufferSize(); response.getOutputStream().write(new byte[size]); assertThat(response.isCommitted()).isTrue(); - assertThat(response.getContentAsByteArray().length).isEqualTo((size + 1)); + assertThat(response.getContentAsByteArray()).hasSize((size + 1)); } @Test @@ -290,7 +290,7 @@ void servletOutputStreamCommittedOnFlushBuffer() throws IOException { assertThat(response.isCommitted()).isFalse(); response.flushBuffer(); assertThat(response.isCommitted()).isTrue(); - assertThat(response.getContentAsByteArray().length).isEqualTo(1); + assertThat(response.getContentAsByteArray()).hasSize(1); } @Test @@ -303,7 +303,7 @@ void servletWriterCommittedWhenBufferSizeExceeded() throws IOException { Arrays.fill(data, 'p'); response.getWriter().write(data); assertThat(response.isCommitted()).isTrue(); - assertThat(response.getContentAsByteArray().length).isEqualTo((size + 1)); + assertThat(response.getContentAsByteArray()).hasSize((size + 1)); } @Test @@ -313,7 +313,7 @@ void servletOutputStreamCommittedOnOutputStreamFlush() throws IOException { assertThat(response.isCommitted()).isFalse(); response.getOutputStream().flush(); assertThat(response.isCommitted()).isTrue(); - assertThat(response.getContentAsByteArray().length).isEqualTo(1); + assertThat(response.getContentAsByteArray()).hasSize(1); } @Test @@ -323,7 +323,7 @@ void servletWriterCommittedOnWriterFlush() throws IOException { assertThat(response.isCommitted()).isFalse(); response.getWriter().flush(); assertThat(response.isCommitted()).isTrue(); - assertThat(response.getContentAsByteArray().length).isEqualTo(1); + assertThat(response.getContentAsByteArray()).hasSize(1); } @Test // SPR-16683 @@ -333,7 +333,7 @@ void servletWriterCommittedOnWriterClose() throws IOException { assertThat(response.isCommitted()).isFalse(); response.getWriter().close(); assertThat(response.isCommitted()).isTrue(); - assertThat(response.getContentAsByteArray().length).isEqualTo(1); + assertThat(response.getContentAsByteArray()).hasSize(1); } @Test // gh-23219 diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockMultipartHttpServletRequestTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockMultipartHttpServletRequestTests.java index 54caba8e8e3d..2fc17da77d24 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockMultipartHttpServletRequestTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockMultipartHttpServletRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -87,14 +87,14 @@ private void doTestMultipartHttpServletRequest(MultipartHttpServletRequest reque while (fileIter.hasNext()) { fileNames.add(fileIter.next()); } - assertThat(fileNames.size()).isEqualTo(2); + assertThat(fileNames).hasSize(2); assertThat(fileNames.contains("file1")).isTrue(); assertThat(fileNames.contains("file2")).isTrue(); MultipartFile file1 = request.getFile("file1"); MultipartFile file2 = request.getFile("file2"); Map fileMap = request.getFileMap(); List fileMapKeys = new ArrayList<>(fileMap.keySet()); - assertThat(fileMapKeys.size()).isEqualTo(2); + assertThat(fileMapKeys).hasSize(2); assertThat(fileMap.get("file1")).isEqualTo(file1); assertThat(fileMap.get("file2")).isEqualTo(file2); diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java index e9ef2635f8b3..94c01e74c291 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -180,7 +180,7 @@ void getServletRegistration() { void getServletRegistrations() { Map servletRegistrations = servletContext.getServletRegistrations(); assertThat(servletRegistrations).isNotNull(); - assertThat(servletRegistrations.size()).isEqualTo(0); + assertThat(servletRegistrations).isEmpty(); } /** @@ -198,7 +198,7 @@ void getFilterRegistration() { void getFilterRegistrations() { Map filterRegistrations = servletContext.getFilterRegistrations(); assertThat(filterRegistrations).isNotNull(); - assertThat(filterRegistrations.size()).isEqualTo(0); + assertThat(filterRegistrations).isEmpty(); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/TestContextConcurrencyTests.java b/spring-test/src/test/java/org/springframework/test/context/TestContextConcurrencyTests.java index 4194f243b3e2..d66c1454c0d6 100644 --- a/spring-test/src/test/java/org/springframework/test/context/TestContextConcurrencyTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/TestContextConcurrencyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -77,7 +77,7 @@ void invokeTestContextManagerFromConcurrentThreads() { }); assertThat(actualMethods).isEqualTo(expectedMethods); }); - assertThat(tcm.getTestContext().attributeNames().length).isEqualTo(0); + assertThat(tcm.getTestContext().attributeNames()).isEmpty(); } diff --git a/spring-test/src/test/java/org/springframework/test/context/TestContextManagerSuppressedExceptionsTests.java b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerSuppressedExceptionsTests.java index 6b6396bd137f..5cb3490731fe 100644 --- a/spring-test/src/test/java/org/springframework/test/context/TestContextManagerSuppressedExceptionsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerSuppressedExceptionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 @@ import static org.assertj.core.api.Assertions.fail; /** - * JUnit 4 based unit tests for {@link TestContextManager}, which verify proper + * JUnit Jupiter based unit tests for {@link TestContextManager}, which verify proper * support for suppressed exceptions thrown by {@link TestExecutionListener * TestExecutionListeners}. * diff --git a/spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java index c61ad4ab8d8d..8d7481da19e3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -18,7 +18,6 @@ import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; @@ -26,7 +25,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** - * JUnit 4 based unit test for {@link TestContextManager}, which verifies proper + * JUnit Jupiter based unit test for {@link TestContextManager}, which verifies proper * execution order of registered {@link TestExecutionListener * TestExecutionListeners}. * @@ -53,7 +52,7 @@ class TestContextManagerTests { @Test void listenerExecutionOrder() throws Exception { // @formatter:off - assertThat(this.testContextManager.getTestExecutionListeners().size()).as("Registered TestExecutionListeners").isEqualTo(3); + assertThat(this.testContextManager.getTestExecutionListeners()).as("Registered TestExecutionListeners").hasSize(3); this.testContextManager.beforeTestMethod(this, this.testMethod); assertExecutionOrder("beforeTestMethod", @@ -104,7 +103,7 @@ void listenerExecutionOrder() throws Exception { } private static void assertExecutionOrder(String usageContext, String... expectedBeforeTestMethodCalls) { - assertThat(executionOrder).as("execution order (" + usageContext + ") ==>").isEqualTo(Arrays.asList(expectedBeforeTestMethodCalls)); + assertThat(executionOrder).as("execution order (" + usageContext + ") ==>").containsExactly(expectedBeforeTestMethodCalls); } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/DeclarativeRuntimeHintsTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/DeclarativeRuntimeHintsTests.java new file mode 100644 index 000000000000..e983a5fa9743 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/aot/DeclarativeRuntimeHintsTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2022 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.test.context.aot; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.generate.InMemoryGeneratedFiles; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.test.context.aot.samples.hints.DeclarativeRuntimeHintsSpringJupiterTests; +import org.springframework.test.context.aot.samples.hints.DeclarativeRuntimeHintsSpringJupiterTests.SampleClassWithGetter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection; +import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.resource; + +/** + * Tests for declarative support for registering run-time hints for tests, tested + * via the {@link TestContextAotGenerator} + * + * @author Sam Brannen + * @since 6.0 + */ +class DeclarativeRuntimeHintsTests extends AbstractAotTests { + + private final RuntimeHints runtimeHints = new RuntimeHints(); + + private final TestContextAotGenerator generator = + new TestContextAotGenerator(new InMemoryGeneratedFiles(), this.runtimeHints); + + + @Test + void declarativeRuntimeHints() { + Class testClass = DeclarativeRuntimeHintsSpringJupiterTests.class; + + this.generator.processAheadOfTime(Stream.of(testClass)); + + // @Reflective + assertReflectionRegistered(testClass); + + // @RegisterReflectionForBinding + assertReflectionRegistered(SampleClassWithGetter.class); + assertReflectionRegistered(String.class); + assertThat(reflection().onMethod(SampleClassWithGetter.class, "getName")).accepts(this.runtimeHints); + + // @ImportRuntimeHints + assertThat(resource().forResource("org/example/config/enigma.txt")).accepts(this.runtimeHints); + } + + private void assertReflectionRegistered(Class type) { + assertThat(reflection().onType(type)).as("Reflection hint for %s", type).accepts(this.runtimeHints); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/hints/DeclarativeRuntimeHintsSpringJupiterTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/hints/DeclarativeRuntimeHintsSpringJupiterTests.java new file mode 100644 index 000000000000..4b1c96a10f4c --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/hints/DeclarativeRuntimeHintsSpringJupiterTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2022 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.test.context.aot.samples.hints; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.annotation.Reflective; +import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.test.context.aot.samples.hints.DeclarativeRuntimeHintsSpringJupiterTests.DemoHints; +import org.springframework.test.context.aot.samples.hints.DeclarativeRuntimeHintsSpringJupiterTests.SampleClassWithGetter; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sam Brannen + * @since 6.0 + */ +@SpringJUnitConfig +@Reflective +@RegisterReflectionForBinding(SampleClassWithGetter.class) +@ImportRuntimeHints(DemoHints.class) +public class DeclarativeRuntimeHintsSpringJupiterTests { + + @Test + void test(@Autowired String foo) { + assertThat(foo).isEqualTo("bar"); + } + + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + String foo() { + return "bar"; + } + } + + static class DemoHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.resources().registerPattern("org/example/config/*.txt"); + } + + } + + public static class SampleClassWithGetter { + + public String getName() { + return null; + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithTestInterface.java index f8b478d753e0..0161e9da08f9 100644 --- a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithTestInterface.java +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithTestInterface.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -34,7 +34,7 @@ @BootstrapWith(CustomTestContextBootstrapper.class) interface BootstrapWithTestInterface { - static class CustomTestContextBootstrapper extends DefaultTestContextBootstrapper { + class CustomTestContextBootstrapper extends DefaultTestContextBootstrapper { @Override protected List getContextCustomizerFactories() { diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationTestInterface.java index 8ef86beafa29..195f4dca0128 100644 --- a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationTestInterface.java +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationTestInterface.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -28,7 +28,7 @@ @ContextConfiguration(classes = Config.class) interface ContextConfigurationTestInterface { - static class Config { + class Config { @Bean Employee employee() { diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationTestInterface.java index 4dfbd72403e6..1046fe8bb020 100644 --- a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationTestInterface.java +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationTestInterface.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,7 +30,7 @@ interface WebAppConfigurationTestInterface { @Configuration - static class Config { + class Config { /* no user beans required for these tests */ } diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/ControllerIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/ControllerIntegrationTests.java index d12afba4c47c..89db1afcbba1 100644 --- a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/ControllerIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/ControllerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -85,8 +85,7 @@ void verifyRootWacSupport() { ApplicationContext parent = wac.getParent(); assertThat(parent).isNotNull(); - boolean condition = parent instanceof WebApplicationContext; - assertThat(condition).isTrue(); + assertThat(parent).isInstanceOf(WebApplicationContext.class); WebApplicationContext root = (WebApplicationContext) parent; assertThat(root.getBeansOfType(String.class).containsKey("bar")).isFalse(); diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java index b4485ebf1ed9..9b0ec5377de5 100644 --- a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -66,13 +66,11 @@ void verifyRootWacConfig() { void verifyDispatcherWacConfig() { ApplicationContext parent = wac.getParent(); assertThat(parent).isNotNull(); - boolean condition = parent instanceof WebApplicationContext; - assertThat(condition).isTrue(); + assertThat(parent).isInstanceOf(WebApplicationContext.class); ApplicationContext grandParent = parent.getParent(); assertThat(grandParent).isNotNull(); - boolean condition1 = grandParent instanceof WebApplicationContext; - assertThat(condition1).isFalse(); + assertThat(grandParent).isNotInstanceOf(WebApplicationContext.class); ServletContext dispatcherServletContext = wac.getServletContext(); assertThat(dispatcherServletContext).isNotNull(); diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/EarTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/EarTests.java index bf360dce9574..e20d58320b1a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/EarTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/EarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -58,8 +58,7 @@ String ear() { @Test void verifyEarConfig() { - boolean condition = context instanceof WebApplicationContext; - assertThat(condition).isFalse(); + assertThat(context).isNotInstanceOf(WebApplicationContext.class); assertThat(context.getParent()).isNull(); assertThat(ear).isEqualTo("ear"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java index 1e89e0735c0e..d53f9b10afde 100644 --- a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -71,8 +71,7 @@ void verifyEarConfig() { void verifyRootWacConfig() { ApplicationContext parent = wac.getParent(); assertThat(parent).isNotNull(); - boolean condition = parent instanceof WebApplicationContext; - assertThat(condition).isFalse(); + assertThat(parent).isNotInstanceOf(WebApplicationContext.class); assertThat(ear).isEqualTo("ear"); assertThat(root).isEqualTo("root"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/EmptyDatabaseConfig.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/EmptyDatabaseConfig.java index 6a36e322f956..432f0bae5399 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/EmptyDatabaseConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/EmptyDatabaseConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/ComposedSpringExtensionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/ComposedSpringExtensionTests.java index d10617fc4314..c62cd004ef03 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/ComposedSpringExtensionTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/ComposedSpringExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.comics.Person; import static org.assertj.core.api.Assertions.assertThat; @@ -38,9 +37,6 @@ * with JUnit Jupiter's {@link ExtendWith @ExtendWith} and Spring's * {@link ContextConfiguration @ContextConfiguration}. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 * @see SpringExtension diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfConditionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfConditionTests.java index 77d375f362f6..6663276540c9 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfConditionTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -26,7 +26,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.test.context.TestContextManager; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -40,9 +39,6 @@ * results and exception handling; whereas, {@link DisabledIfTests} only tests * the happy paths. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 * @see DisabledIfTests diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfTests.java index 1e3f61c3698e..d98c5499dcb6 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/DisabledIfTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 +22,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -31,9 +30,6 @@ * Integration tests which verify support for {@link DisabledIf @DisabledIf} * in conjunction with the {@link SpringExtension} in a JUnit Jupiter environment. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Tadaya Tsuyukubo * @author Sam Brannen * @since 5.0 diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/EnabledIfTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/EnabledIfTests.java index afccc93e8f2c..c10198669ff8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/EnabledIfTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/EnabledIfTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 +22,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -31,9 +30,6 @@ * Integration tests which verify support for {@link EnabledIf @EnabledIf} * in conjunction with the {@link SpringExtension} in a JUnit Jupiter environment. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Tadaya Tsuyukubo * @author Sam Brannen * @since 5.0 diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/FailingBeforeAndAfterMethodsSpringExtensionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/FailingBeforeAndAfterMethodsSpringExtensionTests.java index bb40b5669933..92dfb4532a8c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/FailingBeforeAndAfterMethodsSpringExtensionTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/FailingBeforeAndAfterMethodsSpringExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -32,7 +32,6 @@ import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.transaction.AfterTransaction; import org.springframework.test.context.transaction.BeforeTransaction; import org.springframework.transaction.PlatformTransactionManager; @@ -58,9 +57,6 @@ *

    Indirectly, this class also verifies that all {@code TestExecutionListener} * lifecycle callbacks are called. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 */ diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/RegisterExtensionSpringExtensionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/RegisterExtensionSpringExtensionTests.java index 7c3783db041e..8b34cea6df48 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/RegisterExtensionSpringExtensionTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/RegisterExtensionSpringExtensionTests.java @@ -31,7 +31,6 @@ import org.springframework.context.support.GenericApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.comics.Cat; import org.springframework.test.context.junit.jupiter.comics.Dog; import org.springframework.test.context.junit.jupiter.comics.Person; @@ -43,10 +42,6 @@ * with JUnit Jupiter by registering the {@link SpringExtension} via a static field. * Note, however, that this is not the recommended way to register the {@code SpringExtension}. * - *

    - * To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.1 * @see SpringExtensionTests diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionParameterizedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionParameterizedTests.java index ac2326febccb..fc3165b0b04a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionParameterizedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionParameterizedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 +24,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.comics.Cat; import org.springframework.test.context.junit.jupiter.comics.Dog; import org.springframework.test.context.junit.jupiter.comics.Person; @@ -36,9 +35,6 @@ * can be used with JUnit Jupiter's {@link ParameterizedTest @ParameterizedTest} * support in conjunction with the {@link SpringExtension}. * - *

    To run these tests in an IDE that does not have built-in support for the - * JUnit Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 * @see SpringExtension diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionTests.java index ee67fa14c9b6..56100f02d62f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringExtensionTests.java @@ -31,7 +31,6 @@ import org.springframework.context.support.GenericApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.comics.Cat; import org.springframework.test.context.junit.jupiter.comics.Dog; import org.springframework.test.context.junit.jupiter.comics.Person; @@ -42,9 +41,6 @@ * Integration tests which demonstrate that the Spring TestContext Framework can * be used with JUnit Jupiter via the {@link SpringExtension}. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 * @see SpringExtension diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java index 7796587d3f33..78ba6cbe39cc 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 +22,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.comics.Dog; import org.springframework.test.context.junit.jupiter.comics.Person; @@ -32,9 +31,6 @@ * Integration tests which demonstrate support for {@link Autowired @Autowired} * test class constructors with the Spring TestContext Framework and JUnit Jupiter. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 * @see SpringExtension diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterConstructorInjectionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterConstructorInjectionTests.java index 029bda128ff4..705152a6d952 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterConstructorInjectionTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterConstructorInjectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.comics.Dog; import org.springframework.test.context.junit.jupiter.comics.Person; @@ -34,9 +33,6 @@ * parameters in test class constructors using {@link Autowired @Autowired} * and {@link Value @Value} with the Spring TestContext Framework and JUnit Jupiter. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 * @see SpringExtension diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConstructorAnnotationIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConstructorAnnotationIntegrationTests.java index 7faa8450274d..5c931cf2cf02 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConstructorAnnotationIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/TestConstructorAnnotationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -23,7 +23,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.test.context.TestConstructor; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.comics.Dog; import org.springframework.test.context.junit.jupiter.comics.Person; @@ -35,9 +34,6 @@ * {@link Autowired @Autowired} test class constructors in conjunction with the * {@link TestConstructor @TestConstructor} annotation * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.2 * @see SpringExtension diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/CatInterfaceDefaultMethodsTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/CatInterfaceDefaultMethodsTests.java index d8ee9277d2b4..12fcca900759 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/CatInterfaceDefaultMethodsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/CatInterfaceDefaultMethodsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,7 +16,6 @@ package org.springframework.test.context.junit.jupiter.defaultmethods; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.comics.Cat; @@ -25,9 +24,6 @@ * interface default methods and Java generics in JUnit Jupiter test classes when used * with the Spring TestContext Framework and the {@link SpringExtension}. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 */ diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/DogInterfaceDefaultMethodsTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/DogInterfaceDefaultMethodsTests.java index ba5350a3fc13..d2f30cd7776d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/DogInterfaceDefaultMethodsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/DogInterfaceDefaultMethodsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,7 +16,6 @@ package org.springframework.test.context.junit.jupiter.defaultmethods; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.comics.Dog; @@ -25,9 +24,6 @@ * interface default methods and Java generics in JUnit Jupiter test classes when used * with the Spring TestContext Framework and the {@link SpringExtension}. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 */ diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/GenericComicCharactersInterfaceDefaultMethodsTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/GenericComicCharactersInterfaceDefaultMethodsTests.java index 5fdbafba293a..4781d0edcbdf 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/GenericComicCharactersInterfaceDefaultMethodsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/defaultmethods/GenericComicCharactersInterfaceDefaultMethodsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.junit.jupiter.TestConfig; @@ -34,9 +33,6 @@ * methods and Java generics in JUnit Jupiter test classes when used with the Spring * TestContext Framework and the {@link SpringExtension}. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 */ diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/CatTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/CatTests.java index b8c89eb68ba3..994407076f04 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/CatTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/CatTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,7 +16,6 @@ package org.springframework.test.context.junit.jupiter.generics; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.comics.Cat; @@ -25,9 +24,6 @@ * Java generics in JUnit Jupiter test classes when used with the Spring TestContext * Framework and the {@link SpringExtension}. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 */ diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/DogTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/DogTests.java index f69f57712d81..413d902e2e09 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/DogTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/DogTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,7 +16,6 @@ package org.springframework.test.context.junit.jupiter.generics; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.comics.Dog; @@ -25,9 +24,6 @@ * Java generics in JUnit Jupiter test classes when used with the Spring TestContext * Framework and the {@link SpringExtension}. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 */ diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericComicCharactersTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericComicCharactersTests.java index 3957d9a5b9c5..eb68fed7fd2e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericComicCharactersTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericComicCharactersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.junit.jupiter.TestConfig; @@ -34,9 +33,6 @@ * Java generics in JUnit Jupiter test classes when used with the Spring TestContext * Framework and the {@link SpringExtension}. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 */ diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericsAndNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericsAndNestedTests.java index 8c5990b31503..1fdade3b05ef 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericsAndNestedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/generics/GenericsAndNestedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -18,7 +18,6 @@ import org.junit.jupiter.api.Nested; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.comics.Cat; import org.springframework.test.context.junit.jupiter.comics.Dog; @@ -28,10 +27,6 @@ * test classes when used with the Spring TestContext Framework and the * {@link SpringExtension}. * - *

    - * To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0.5 */ diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionNestedTests.java index 2cd988ae8a07..61be8f3b961b 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionNestedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionNestedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -26,7 +26,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.NestedTestConfiguration; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.junit.jupiter.nested.ConstructorInjectionNestedTests.TopLevelConfig; @@ -39,10 +38,6 @@ * with the {@link SpringExtension} in a JUnit Jupiter environment ... when using * constructor injection as opposed to field injection (see SPR-16653). * - *

    - * To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0.5 * @see ContextConfigurationNestedTests diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/MultipleWebRequestsSpringExtensionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/MultipleWebRequestsSpringExtensionTests.java index b12292b099d2..5853421d3fd9 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/MultipleWebRequestsSpringExtensionTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/MultipleWebRequestsSpringExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 +20,6 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.context.WebApplicationContext; @@ -39,9 +38,6 @@ * {@link SpringExtension} (registered via a custom * {@link SpringJUnitWebConfig @SpringJUnitWebConfig} composed annotation). * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 * @see SpringExtension diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebSpringExtensionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebSpringExtensionTests.java index f07ca2fc5ff2..134078bc4582 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebSpringExtensionTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/web/WebSpringExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 +20,6 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; -import org.springframework.test.context.junit.SpringJUnitJupiterTestSuite; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.context.WebApplicationContext; @@ -42,9 +41,6 @@ * This allows the {@link MockMvc} instance to be configured local to the * test method without any fields in the test class. * - *

    To run these tests in an IDE that does not have built-in support for the JUnit - * Platform, simply run {@link SpringJUnitJupiterTestSuite} as a JUnit 4 test. - * * @author Sam Brannen * @since 5.0 * @see SpringExtension diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerConfiguredViaMetaAnnotationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerConfiguredViaMetaAnnotationTests.java index 1be693f8871a..4ad36fcec00d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerConfiguredViaMetaAnnotationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerConfiguredViaMetaAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -67,7 +67,7 @@ public class InitializerConfiguredViaMetaAnnotationTests { @Test public void beansFromInitializerAndComposedAnnotation() { - assertThat(strings.size()).isEqualTo(2); + assertThat(strings).hasSize(2); assertThat(foo).isEqualTo("foo"); assertThat(bar).isEqualTo("bar"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig.java index c1ac5401996a..a7a1fc8bf802 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfileResolverWithCustomDefaultsMetaConfig.java @@ -52,7 +52,7 @@ @Configuration @Profile("dev") - static class DevConfig { + class DevConfig { @Bean public String foo() { @@ -62,7 +62,7 @@ public String foo() { @Configuration @Profile("prod") - static class ProductionConfig { + class ProductionConfig { @Bean public String foo() { @@ -72,7 +72,7 @@ public String foo() { @Configuration @Profile("resolver") - static class ResolverConfig { + class ResolverConfig { @Bean public String foo() { @@ -80,7 +80,7 @@ public String foo() { } } - static class CustomResolver implements ActiveProfilesResolver { + class CustomResolver implements ActiveProfilesResolver { @Override public String[] resolve(Class testClass) { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.java index 342c4cb54b66..44684649b2e9 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/meta/ConfigClassesAndProfilesWithCustomDefaultsMetaConfig.java @@ -50,7 +50,7 @@ @Configuration @Profile("dev") - static class DevConfig { + class DevConfig { @Bean public String foo() { @@ -60,7 +60,7 @@ public String foo() { @Configuration @Profile("prod") - static class ProductionConfig { + class ProductionConfig { @Bean public String foo() { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/rules/ProgrammaticTxMgmtSpringRuleTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/rules/ProgrammaticTxMgmtSpringRuleTests.java index 5eea2e7270c2..86ac39677ef7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/rules/ProgrammaticTxMgmtSpringRuleTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/rules/ProgrammaticTxMgmtSpringRuleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -97,24 +97,11 @@ void beforeTransaction() { void afterTransaction() { String method = this.testName.getMethodName(); switch (method) { - case "commitTxAndStartNewTx": - case "commitTxButDoNotStartNewTx": { - assertUsers("Dogbert"); - break; - } - case "rollbackTxAndStartNewTx": - case "rollbackTxButDoNotStartNewTx": - case "startTxWithExistingTransaction": { - assertUsers("Dilbert"); - break; - } - case "rollbackTxAndStartNewTxWithDefaultCommitSemantics": { - assertUsers("Dilbert", "Dogbert"); - break; - } - default: { - fail("missing 'after transaction' assertion for test method: " + method); - } + case "commitTxAndStartNewTx", "commitTxButDoNotStartNewTx" -> assertUsers("Dogbert"); + case "rollbackTxAndStartNewTx", "rollbackTxButDoNotStartNewTx", "startTxWithExistingTransaction" -> + assertUsers("Dilbert"); + case "rollbackTxAndStartNewTxWithDefaultCommitSemantics" -> assertUsers("Dilbert", "Dogbert"); + default -> fail("missing 'after transaction' assertion for test method: " + method); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java index 8420cfd5bcb8..aa70c7c3e02e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,8 +16,6 @@ package org.springframework.test.context.junit4.spr9051; -import java.util.Arrays; -import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -93,9 +91,8 @@ public void testSPR_9051() throws Exception { assertThat(enigma).isNotNull(); assertThat(lifecycleBean).isNotNull(); assertThat(lifecycleBean.isInitialized()).isTrue(); - Set names = new HashSet<>(); - names.add(enigma.toString()); - names.add(lifecycleBean.getName()); - assertThat(new HashSet<>(Arrays.asList("enigma #1", "enigma #2"))).isEqualTo(names); + Set names = Set.of(enigma, lifecycleBean.getName()); + assertThat(names).containsExactlyInAnyOrder("enigma #1", "enigma #2"); } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtilsTests.java index dd0d5fe573ba..c895117b42cb 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtilsTests.java @@ -47,7 +47,7 @@ void detectDefaultConfigurationClassesWithNullDeclaringClass() { void detectDefaultConfigurationClassesWithoutConfigurationClass() { Class[] configClasses = detectDefaultConfigurationClasses(NoConfigTestCase.class); assertThat(configClasses).isNotNull(); - assertThat(configClasses.length).isEqualTo(0); + assertThat(configClasses).isEmpty(); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java index 6420c1aaa703..5ef2dd5b707d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 +72,7 @@ void resolveConfigAttributesWithBareAnnotations() { Class testClass = BareAnnotations.class; List attributesList = resolveContextConfigurationAttributes(testClass); assertThat(attributesList).isNotNull(); - assertThat(attributesList.size()).isEqualTo(1); + assertThat(attributesList).hasSize(1); assertAttributes(attributesList.get(0), testClass, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, ContextLoader.class, true); } @@ -81,7 +81,7 @@ void resolveConfigAttributesWithBareAnnotations() { void resolveConfigAttributesWithLocalAnnotationAndLocations() { List attributesList = resolveContextConfigurationAttributes(LocationsFoo.class); assertThat(attributesList).isNotNull(); - assertThat(attributesList.size()).isEqualTo(1); + assertThat(attributesList).hasSize(1); assertLocationsFooAttributes(attributesList.get(0)); } @@ -90,7 +90,7 @@ void resolveConfigAttributesWithMetaAnnotationAndLocations() { Class testClass = MetaLocationsFoo.class; List attributesList = resolveContextConfigurationAttributes(testClass); assertThat(attributesList).isNotNull(); - assertThat(attributesList.size()).isEqualTo(1); + assertThat(attributesList).hasSize(1); assertAttributes(attributesList.get(0), testClass, new String[] {"/foo.xml"}, EMPTY_CLASS_ARRAY, ContextLoader.class, true); } @@ -100,7 +100,7 @@ void resolveConfigAttributesWithMetaAnnotationAndLocationsAndOverrides() { Class testClass = MetaLocationsFooWithOverrides.class; List attributesList = resolveContextConfigurationAttributes(testClass); assertThat(attributesList).isNotNull(); - assertThat(attributesList.size()).isEqualTo(1); + assertThat(attributesList).hasSize(1); assertAttributes(attributesList.get(0), testClass, new String[] {"/foo.xml"}, EMPTY_CLASS_ARRAY, ContextLoader.class, true); } @@ -110,7 +110,7 @@ void resolveConfigAttributesWithMetaAnnotationAndLocationsAndOverriddenAttribute Class testClass = MetaLocationsFooWithOverriddenAttributes.class; List attributesList = resolveContextConfigurationAttributes(testClass); assertThat(attributesList).isNotNull(); - assertThat(attributesList.size()).isEqualTo(1); + assertThat(attributesList).hasSize(1); assertAttributes(attributesList.get(0), testClass, new String[] {"foo1.xml", "foo2.xml"}, EMPTY_CLASS_ARRAY, ContextLoader.class, true); } @@ -120,7 +120,7 @@ void resolveConfigAttributesWithMetaAnnotationAndLocationsInClassHierarchy() { Class testClass = MetaLocationsBar.class; List attributesList = resolveContextConfigurationAttributes(testClass); assertThat(attributesList).isNotNull(); - assertThat(attributesList.size()).isEqualTo(2); + assertThat(attributesList).hasSize(2); assertAttributes(attributesList.get(0), testClass, new String[] {"/bar.xml"}, EMPTY_CLASS_ARRAY, ContextLoader.class, true); assertAttributes(attributesList.get(1), @@ -131,7 +131,7 @@ void resolveConfigAttributesWithMetaAnnotationAndLocationsInClassHierarchy() { void resolveConfigAttributesWithLocalAnnotationAndClasses() { List attributesList = resolveContextConfigurationAttributes(ClassesFoo.class); assertThat(attributesList).isNotNull(); - assertThat(attributesList.size()).isEqualTo(1); + assertThat(attributesList).hasSize(1); assertClassesFooAttributes(attributesList.get(0)); } @@ -139,7 +139,7 @@ void resolveConfigAttributesWithLocalAnnotationAndClasses() { void resolveConfigAttributesWithLocalAndInheritedAnnotationsAndLocations() { List attributesList = resolveContextConfigurationAttributes(LocationsBar.class); assertThat(attributesList).isNotNull(); - assertThat(attributesList.size()).isEqualTo(2); + assertThat(attributesList).hasSize(2); assertLocationsBarAttributes(attributesList.get(0)); assertLocationsFooAttributes(attributesList.get(1)); } @@ -148,7 +148,7 @@ void resolveConfigAttributesWithLocalAndInheritedAnnotationsAndLocations() { void resolveConfigAttributesWithLocalAndInheritedAnnotationsAndClasses() { List attributesList = resolveContextConfigurationAttributes(ClassesBar.class); assertThat(attributesList).isNotNull(); - assertThat(attributesList.size()).isEqualTo(2); + assertThat(attributesList).hasSize(2); assertClassesBarAttributes(attributesList.get(0)); assertClassesFooAttributes(attributesList.get(1)); } @@ -161,7 +161,7 @@ void resolveConfigAttributesWithLocalAndInheritedAnnotationsAndClasses() { void resolveConfigAttributesWithLocationsAndClasses() { List attributesList = resolveContextConfigurationAttributes(LocationsAndClasses.class); assertThat(attributesList).isNotNull(); - assertThat(attributesList.size()).isEqualTo(1); + assertThat(attributesList).hasSize(1); } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java index 72188af6c378..a5ff240f2a05 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java @@ -67,18 +67,18 @@ void resolveContextHierarchyAttributesForSingleTestClassWithContextConfiguration @Test void resolveContextHierarchyAttributesForSingleTestClassWithImplicitSingleLevelContextHierarchy() { List> hierarchyAttributes = resolveContextHierarchyAttributes(BareAnnotations.class); - assertThat(hierarchyAttributes.size()).isEqualTo(1); + assertThat(hierarchyAttributes).hasSize(1); List configAttributesList = hierarchyAttributes.get(0); - assertThat(configAttributesList.size()).isEqualTo(1); + assertThat(configAttributesList).hasSize(1); debugConfigAttributes(configAttributesList); } @Test void resolveContextHierarchyAttributesForSingleTestClassWithSingleLevelContextHierarchy() { List> hierarchyAttributes = resolveContextHierarchyAttributes(SingleTestClassWithSingleLevelContextHierarchy.class); - assertThat(hierarchyAttributes.size()).isEqualTo(1); + assertThat(hierarchyAttributes).hasSize(1); List configAttributesList = hierarchyAttributes.get(0); - assertThat(configAttributesList.size()).isEqualTo(1); + assertThat(configAttributesList).hasSize(1); debugConfigAttributes(configAttributesList); } @@ -86,11 +86,11 @@ void resolveContextHierarchyAttributesForSingleTestClassWithSingleLevelContextHi void resolveContextHierarchyAttributesForSingleTestClassWithSingleLevelContextHierarchyFromMetaAnnotation() { Class testClass = SingleTestClassWithSingleLevelContextHierarchyFromMetaAnnotation.class; List> hierarchyAttributes = resolveContextHierarchyAttributes(testClass); - assertThat(hierarchyAttributes.size()).isEqualTo(1); + assertThat(hierarchyAttributes).hasSize(1); List configAttributesList = hierarchyAttributes.get(0); assertThat(configAttributesList).isNotNull(); - assertThat(configAttributesList.size()).isEqualTo(1); + assertThat(configAttributesList).hasSize(1); debugConfigAttributes(configAttributesList); assertAttributes(configAttributesList.get(0), testClass, new String[] { "A.xml" }, EMPTY_CLASS_ARRAY, ContextLoader.class, true); @@ -100,11 +100,11 @@ void resolveContextHierarchyAttributesForSingleTestClassWithSingleLevelContextHi void resolveContextHierarchyAttributesForSingleTestClassWithTripleLevelContextHierarchy() { Class testClass = SingleTestClassWithTripleLevelContextHierarchy.class; List> hierarchyAttributes = resolveContextHierarchyAttributes(testClass); - assertThat(hierarchyAttributes.size()).isEqualTo(1); + assertThat(hierarchyAttributes).hasSize(1); List configAttributesList = hierarchyAttributes.get(0); assertThat(configAttributesList).isNotNull(); - assertThat(configAttributesList.size()).isEqualTo(3); + assertThat(configAttributesList).hasSize(3); debugConfigAttributes(configAttributesList); assertAttributes(configAttributesList.get(0), testClass, new String[] { "A.xml" }, EMPTY_CLASS_ARRAY, ContextLoader.class, true); @@ -117,32 +117,32 @@ void resolveContextHierarchyAttributesForSingleTestClassWithTripleLevelContextHi @Test void resolveContextHierarchyAttributesForTestClassHierarchyWithSingleLevelContextHierarchies() { List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass3WithSingleLevelContextHierarchy.class); - assertThat(hierarchyAttributes.size()).isEqualTo(3); + assertThat(hierarchyAttributes).hasSize(3); List configAttributesListClassLevel1 = hierarchyAttributes.get(0); debugConfigAttributes(configAttributesListClassLevel1); - assertThat(configAttributesListClassLevel1.size()).isEqualTo(1); + assertThat(configAttributesListClassLevel1).hasSize(1); assertThat(configAttributesListClassLevel1.get(0).getLocations()[0]).isEqualTo("one.xml"); List configAttributesListClassLevel2 = hierarchyAttributes.get(1); debugConfigAttributes(configAttributesListClassLevel2); - assertThat(configAttributesListClassLevel2.size()).isEqualTo(1); + assertThat(configAttributesListClassLevel2).hasSize(1); assertThat(configAttributesListClassLevel2.get(0).getLocations()).isEqualTo(new String[] { "two-A.xml", "two-B.xml" }); List configAttributesListClassLevel3 = hierarchyAttributes.get(2); debugConfigAttributes(configAttributesListClassLevel3); - assertThat(configAttributesListClassLevel3.size()).isEqualTo(1); + assertThat(configAttributesListClassLevel3).hasSize(1); assertThat(configAttributesListClassLevel3.get(0).getLocations()[0]).isEqualTo("three.xml"); } @Test void resolveContextHierarchyAttributesForTestClassHierarchyWithSingleLevelContextHierarchiesAndMetaAnnotations() { List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass3WithSingleLevelContextHierarchyFromMetaAnnotation.class); - assertThat(hierarchyAttributes.size()).isEqualTo(3); + assertThat(hierarchyAttributes).hasSize(3); List configAttributesListClassLevel1 = hierarchyAttributes.get(0); debugConfigAttributes(configAttributesListClassLevel1); - assertThat(configAttributesListClassLevel1.size()).isEqualTo(1); + assertThat(configAttributesListClassLevel1).hasSize(1); assertThat(configAttributesListClassLevel1.get(0).getLocations()[0]).isEqualTo("A.xml"); assertAttributes(configAttributesListClassLevel1.get(0), TestClass1WithSingleLevelContextHierarchyFromMetaAnnotation.class, new String[] { "A.xml" }, @@ -150,7 +150,7 @@ void resolveContextHierarchyAttributesForTestClassHierarchyWithSingleLevelContex List configAttributesListClassLevel2 = hierarchyAttributes.get(1); debugConfigAttributes(configAttributesListClassLevel2); - assertThat(configAttributesListClassLevel2.size()).isEqualTo(1); + assertThat(configAttributesListClassLevel2).hasSize(1); assertThat(configAttributesListClassLevel2.get(0).getLocations()).isEqualTo(new String[] { "B-one.xml", "B-two.xml" }); assertAttributes(configAttributesListClassLevel2.get(0), TestClass2WithSingleLevelContextHierarchyFromMetaAnnotation.class, @@ -159,7 +159,7 @@ void resolveContextHierarchyAttributesForTestClassHierarchyWithSingleLevelContex List configAttributesListClassLevel3 = hierarchyAttributes.get(2); debugConfigAttributes(configAttributesListClassLevel3); - assertThat(configAttributesListClassLevel3.size()).isEqualTo(1); + assertThat(configAttributesListClassLevel3).hasSize(1); assertThat(configAttributesListClassLevel3.get(0).getLocations()[0]).isEqualTo("C.xml"); assertAttributes(configAttributesListClassLevel3.get(0), TestClass3WithSingleLevelContextHierarchyFromMetaAnnotation.class, new String[] { "C.xml" }, @@ -167,17 +167,17 @@ void resolveContextHierarchyAttributesForTestClassHierarchyWithSingleLevelContex } private void assertOneTwo(List> hierarchyAttributes) { - assertThat(hierarchyAttributes.size()).isEqualTo(2); + assertThat(hierarchyAttributes).hasSize(2); List configAttributesListClassLevel1 = hierarchyAttributes.get(0); List configAttributesListClassLevel2 = hierarchyAttributes.get(1); debugConfigAttributes(configAttributesListClassLevel1); debugConfigAttributes(configAttributesListClassLevel2); - assertThat(configAttributesListClassLevel1.size()).isEqualTo(1); + assertThat(configAttributesListClassLevel1).hasSize(1); assertThat(configAttributesListClassLevel1.get(0).getLocations()[0]).isEqualTo("one.xml"); - assertThat(configAttributesListClassLevel2.size()).isEqualTo(1); + assertThat(configAttributesListClassLevel2).hasSize(1); assertThat(configAttributesListClassLevel2.get(0).getLocations()[0]).isEqualTo("two.xml"); } @@ -204,23 +204,23 @@ void resolveContextHierarchyAttributesForTestClassHierarchyWithBareMetaContextCo @Test void resolveContextHierarchyAttributesForTestClassHierarchyWithMultiLevelContextHierarchies() { List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass3WithMultiLevelContextHierarchy.class); - assertThat(hierarchyAttributes.size()).isEqualTo(3); + assertThat(hierarchyAttributes).hasSize(3); List configAttributesListClassLevel1 = hierarchyAttributes.get(0); debugConfigAttributes(configAttributesListClassLevel1); - assertThat(configAttributesListClassLevel1.size()).isEqualTo(2); + assertThat(configAttributesListClassLevel1).hasSize(2); assertThat(configAttributesListClassLevel1.get(0).getLocations()[0]).isEqualTo("1-A.xml"); assertThat(configAttributesListClassLevel1.get(1).getLocations()[0]).isEqualTo("1-B.xml"); List configAttributesListClassLevel2 = hierarchyAttributes.get(1); debugConfigAttributes(configAttributesListClassLevel2); - assertThat(configAttributesListClassLevel2.size()).isEqualTo(2); + assertThat(configAttributesListClassLevel2).hasSize(2); assertThat(configAttributesListClassLevel2.get(0).getLocations()[0]).isEqualTo("2-A.xml"); assertThat(configAttributesListClassLevel2.get(1).getLocations()[0]).isEqualTo("2-B.xml"); List configAttributesListClassLevel3 = hierarchyAttributes.get(2); debugConfigAttributes(configAttributesListClassLevel3); - assertThat(configAttributesListClassLevel3.size()).isEqualTo(3); + assertThat(configAttributesListClassLevel3).hasSize(3); assertThat(configAttributesListClassLevel3.get(0).getLocations()[0]).isEqualTo("3-A.xml"); assertThat(configAttributesListClassLevel3.get(1).getLocations()[0]).isEqualTo("3-B.xml"); assertThat(configAttributesListClassLevel3.get(2).getLocations()[0]).isEqualTo("3-C.xml"); @@ -338,20 +338,20 @@ void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchi List alphaConfig = map.get("alpha"); assertThat(alphaConfig).hasSize(2); - assertThat(alphaConfig.get(0).getLocations().length).isEqualTo(1); + assertThat(alphaConfig.get(0).getLocations()).hasSize(1); assertThat(alphaConfig.get(0).getLocations()[0]).isEqualTo("1-A.xml"); - assertThat(alphaConfig.get(0).getInitializers().length).isEqualTo(0); - assertThat(alphaConfig.get(1).getLocations().length).isEqualTo(0); - assertThat(alphaConfig.get(1).getInitializers().length).isEqualTo(1); + assertThat(alphaConfig.get(0).getInitializers()).isEmpty(); + assertThat(alphaConfig.get(1).getLocations()).isEmpty(); + assertThat(alphaConfig.get(1).getInitializers()).hasSize(1); assertThat(alphaConfig.get(1).getInitializers()[0]).isEqualTo(DummyApplicationContextInitializer.class); List betaConfig = map.get("beta"); assertThat(betaConfig).hasSize(2); - assertThat(betaConfig.get(0).getLocations().length).isEqualTo(1); + assertThat(betaConfig.get(0).getLocations()).hasSize(1); assertThat(betaConfig.get(0).getLocations()[0]).isEqualTo("1-B.xml"); - assertThat(betaConfig.get(0).getInitializers().length).isEqualTo(0); - assertThat(betaConfig.get(1).getLocations().length).isEqualTo(0); - assertThat(betaConfig.get(1).getInitializers().length).isEqualTo(1); + assertThat(betaConfig.get(0).getInitializers()).isEmpty(); + assertThat(betaConfig.get(1).getLocations()).isEmpty(); + assertThat(betaConfig.get(1).getInitializers()).hasSize(1); assertThat(betaConfig.get(1).getInitializers()[0]).isEqualTo(DummyApplicationContextInitializer.class); } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java index cc33d62a4422..f7ee01e4c9ad 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java @@ -219,7 +219,7 @@ static class Config { @Bean public String foo() { - return new String("foo"); + return "foo"; } } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java index 7797a2455dce..5f6fb354a127 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -215,7 +215,7 @@ void addPropertiesFilesToEnvironmentWithSinglePropertyFromVirtualFile() { MutablePropertySources propertySources = environment.getPropertySources(); propertySources.remove(MockPropertySource.MOCK_PROPERTIES_PROPERTY_SOURCE_NAME); - assertThat(propertySources.size()).isEqualTo(0); + assertThat(propertySources).isEmpty(); String pair = "key = value"; ByteArrayResource resource = new ByteArrayResource(pair.getBytes(), "from inlined property: " + pair); @@ -223,7 +223,7 @@ void addPropertiesFilesToEnvironmentWithSinglePropertyFromVirtualFile() { given(resourceLoader.getResource(anyString())).willReturn(resource); addPropertiesFilesToEnvironment(environment, resourceLoader, FOO_LOCATIONS); - assertThat(propertySources.size()).isEqualTo(1); + assertThat(propertySources).hasSize(1); assertThat(environment.getProperty("key")).isEqualTo("value"); } @@ -275,10 +275,10 @@ void addInlinedPropertiesToEnvironmentWithEmptyProperty() { ConfigurableEnvironment environment = new MockEnvironment(); MutablePropertySources propertySources = environment.getPropertySources(); propertySources.remove(MockPropertySource.MOCK_PROPERTIES_PROPERTY_SOURCE_NAME); - assertThat(propertySources.size()).isEqualTo(0); + assertThat(propertySources).isEmpty(); addInlinedPropertiesToEnvironment(environment, asArray(" ")); - assertThat(propertySources.size()).isEqualTo(1); - assertThat(((Map) propertySources.iterator().next().getSource()).size()).isEqualTo(0); + assertThat(propertySources).hasSize(1); + assertThat(((Map) propertySources.iterator().next().getSource())).isEmpty(); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/transaction/programmatic/ProgrammaticTxMgmtTestNGTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/transaction/programmatic/ProgrammaticTxMgmtTestNGTests.java index 4a4e4cadbffb..1648516d0816 100644 --- a/spring-test/src/test/java/org/springframework/test/context/testng/transaction/programmatic/ProgrammaticTxMgmtTestNGTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/testng/transaction/programmatic/ProgrammaticTxMgmtTestNGTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,24 +73,11 @@ public void beforeTransaction() { @AfterTransaction public void afterTransaction() { switch (this.methodName) { - case "commitTxAndStartNewTx": - case "commitTxButDoNotStartNewTx": { - assertUsers("Dogbert"); - break; - } - case "rollbackTxAndStartNewTx": - case "rollbackTxButDoNotStartNewTx": - case "startTxWithExistingTransaction": { - assertUsers("Dilbert"); - break; - } - case "rollbackTxAndStartNewTxWithDefaultCommitSemantics": { - assertUsers("Dilbert", "Dogbert"); - break; - } - default: { - fail("missing 'after transaction' assertion for test method: " + this.methodName); - } + case "commitTxAndStartNewTx", "commitTxButDoNotStartNewTx" -> assertUsers("Dogbert"); + case "rollbackTxAndStartNewTx", "rollbackTxButDoNotStartNewTx", "startTxWithExistingTransaction" -> + assertUsers("Dilbert"); + case "rollbackTxAndStartNewTxWithDefaultCommitSemantics" -> assertUsers("Dilbert", "Dogbert"); + default -> fail("missing 'after transaction' assertion for test method: " + this.methodName); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/transaction/programmatic/ProgrammaticTxMgmtTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/programmatic/ProgrammaticTxMgmtTests.java index db72c16ed2d7..2e92710ea39c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/transaction/programmatic/ProgrammaticTxMgmtTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/programmatic/ProgrammaticTxMgmtTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -88,24 +88,11 @@ void beforeTransaction() { @AfterTransaction void afterTransaction() { switch (this.methodName) { - case "commitTxAndStartNewTx": - case "commitTxButDoNotStartNewTx": { - assertUsers("Dogbert"); - break; - } - case "rollbackTxAndStartNewTx": - case "rollbackTxButDoNotStartNewTx": - case "startTxWithExistingTransaction": { - assertUsers("Dilbert"); - break; - } - case "rollbackTxAndStartNewTxWithDefaultCommitSemantics": { - assertUsers("Dilbert", "Dogbert"); - break; - } - default: { - fail("missing 'after transaction' assertion for test method: " + this.methodName); - } + case "commitTxAndStartNewTx", "commitTxButDoNotStartNewTx" -> assertUsers("Dogbert"); + case "rollbackTxAndStartNewTx", "rollbackTxButDoNotStartNewTx", "startTxWithExistingTransaction" -> + assertUsers("Dilbert"); + case "rollbackTxAndStartNewTxWithDefaultCommitSemantics" -> assertUsers("Dilbert", "Dogbert"); + default -> fail("missing 'after transaction' assertion for test method: " + this.methodName); } } diff --git a/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java index 680ed0bf3737..fdea42df9919 100644 --- a/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java @@ -46,7 +46,7 @@ */ class ReflectionTestUtilsTests { - private static final Float PI = Float.valueOf((float) 22 / 7); + private static final Float PI = (float) 22 / 7; private final Person person = new PersonEntity(); @@ -64,7 +64,7 @@ void resetStaticFields() { @Test void setFieldWithNullTargetObject() throws Exception { assertThatIllegalArgumentException() - .isThrownBy(() -> setField((Object) null, "id", Long.valueOf(99))) + .isThrownBy(() -> setField((Object) null, "id", 99L)) .withMessageStartingWith("Either targetObject or targetClass"); } @@ -78,7 +78,7 @@ void getFieldWithNullTargetObject() throws Exception { @Test void setFieldWithNullTargetClass() throws Exception { assertThatIllegalArgumentException() - .isThrownBy(() -> setField((Class) null, "id", Long.valueOf(99))) + .isThrownBy(() -> setField((Class) null, "id", 99L)) .withMessageStartingWith("Either targetObject or targetClass"); } @@ -92,21 +92,21 @@ void getFieldWithNullTargetClass() throws Exception { @Test void setFieldWithNullNameAndNullType() throws Exception { assertThatIllegalArgumentException() - .isThrownBy(() -> setField(person, null, Long.valueOf(99), null)) + .isThrownBy(() -> setField(person, null, 99L, null)) .withMessageStartingWith("Either name or type"); } @Test void setFieldWithBogusName() throws Exception { assertThatIllegalArgumentException() - .isThrownBy(() -> setField(person, "bogus", Long.valueOf(99), long.class)) + .isThrownBy(() -> setField(person, "bogus", 99L, long.class)) .withMessageStartingWith("Could not find field 'bogus'"); } @Test void setFieldWithWrongType() throws Exception { assertThatIllegalArgumentException() - .isThrownBy(() -> setField(person, "id", Long.valueOf(99), String.class)) + .isThrownBy(() -> setField(person, "id", 99L, String.class)) .withMessageStartingWith("Could not find field"); } @@ -135,17 +135,17 @@ void setFieldAndGetFieldViaCglibProxy() throws Exception { private static void assertSetFieldAndGetFieldBehavior(Person person) { // Set reflectively - setField(person, "id", Long.valueOf(99), long.class); + setField(person, "id", 99L, long.class); setField(person, "name", "Tom"); - setField(person, "age", Integer.valueOf(42)); + setField(person, "age", 42); setField(person, "eyeColor", "blue", String.class); setField(person, "likesPets", Boolean.TRUE); setField(person, "favoriteNumber", PI, Number.class); // Get reflectively - assertThat(getField(person, "id")).isEqualTo(Long.valueOf(99)); + assertThat(getField(person, "id")).isEqualTo(99L); assertThat(getField(person, "name")).isEqualTo("Tom"); - assertThat(getField(person, "age")).isEqualTo(Integer.valueOf(42)); + assertThat(getField(person, "age")).isEqualTo(42); assertThat(getField(person, "eyeColor")).isEqualTo("blue"); assertThat(getField(person, "likesPets")).isEqualTo(Boolean.TRUE); assertThat(getField(person, "favoriteNumber")).isEqualTo(PI); @@ -249,33 +249,33 @@ void getStaticFieldViaInstance() throws Exception { @Test void invokeSetterMethodAndInvokeGetterMethodWithExplicitMethodNames() throws Exception { - invokeSetterMethod(person, "setId", Long.valueOf(1), long.class); + invokeSetterMethod(person, "setId", 1L, long.class); invokeSetterMethod(person, "setName", "Jerry", String.class); - invokeSetterMethod(person, "setAge", Integer.valueOf(33), int.class); + invokeSetterMethod(person, "setAge", 33, int.class); invokeSetterMethod(person, "setEyeColor", "green", String.class); invokeSetterMethod(person, "setLikesPets", Boolean.FALSE, boolean.class); - invokeSetterMethod(person, "setFavoriteNumber", Integer.valueOf(42), Number.class); + invokeSetterMethod(person, "setFavoriteNumber", 42, Number.class); assertThat(person.getId()).as("ID (protected method in a superclass)").isEqualTo(1); assertThat(person.getName()).as("name (private method)").isEqualTo("Jerry"); assertThat(person.getAge()).as("age (protected method)").isEqualTo(33); assertThat(person.getEyeColor()).as("eye color (package private method)").isEqualTo("green"); assertThat(person.likesPets()).as("'likes pets' flag (protected method for a boolean)").isFalse(); - assertThat(person.getFavoriteNumber()).as("'favorite number' (protected method for a Number)").isEqualTo(Integer.valueOf(42)); + assertThat(person.getFavoriteNumber()).as("'favorite number' (protected method for a Number)").isEqualTo(42); - assertThat(invokeGetterMethod(person, "getId")).isEqualTo(Long.valueOf(1)); + assertThat(invokeGetterMethod(person, "getId")).isEqualTo(1L); assertThat(invokeGetterMethod(person, "getName")).isEqualTo("Jerry"); - assertThat(invokeGetterMethod(person, "getAge")).isEqualTo(Integer.valueOf(33)); + assertThat(invokeGetterMethod(person, "getAge")).isEqualTo(33); assertThat(invokeGetterMethod(person, "getEyeColor")).isEqualTo("green"); assertThat(invokeGetterMethod(person, "likesPets")).isEqualTo(Boolean.FALSE); - assertThat(invokeGetterMethod(person, "getFavoriteNumber")).isEqualTo(Integer.valueOf(42)); + assertThat(invokeGetterMethod(person, "getFavoriteNumber")).isEqualTo(42); } @Test void invokeSetterMethodAndInvokeGetterMethodWithJavaBeanPropertyNames() throws Exception { - invokeSetterMethod(person, "id", Long.valueOf(99), long.class); + invokeSetterMethod(person, "id", 99L, long.class); invokeSetterMethod(person, "name", "Tom"); - invokeSetterMethod(person, "age", Integer.valueOf(42)); + invokeSetterMethod(person, "age", 42); invokeSetterMethod(person, "eyeColor", "blue", String.class); invokeSetterMethod(person, "likesPets", Boolean.TRUE); invokeSetterMethod(person, "favoriteNumber", PI, Number.class); @@ -287,9 +287,9 @@ void invokeSetterMethodAndInvokeGetterMethodWithJavaBeanPropertyNames() throws E assertThat(person.likesPets()).as("'likes pets' flag (protected method for a boolean)").isTrue(); assertThat(person.getFavoriteNumber()).as("'favorite number' (protected method for a Number)").isEqualTo(PI); - assertThat(invokeGetterMethod(person, "id")).isEqualTo(Long.valueOf(99)); + assertThat(invokeGetterMethod(person, "id")).isEqualTo(99L); assertThat(invokeGetterMethod(person, "name")).isEqualTo("Tom"); - assertThat(invokeGetterMethod(person, "age")).isEqualTo(Integer.valueOf(42)); + assertThat(invokeGetterMethod(person, "age")).isEqualTo(42); assertThat(invokeGetterMethod(person, "eyeColor")).isEqualTo("blue"); assertThat(invokeGetterMethod(person, "likesPets")).isEqualTo(Boolean.TRUE); assertThat(invokeGetterMethod(person, "favoriteNumber")).isEqualTo(PI); @@ -349,7 +349,7 @@ void invokeMethodSimulatingLifecycleEvents() { assertThat(component.getText()).as("text").isNull(); // Simulate autowiring a configuration method - invokeMethod(component, "configure", Integer.valueOf(42), "enigma"); + invokeMethod(component, "configure", 42, "enigma"); assertThat(component.getNumber()).as("number should have been configured").isEqualTo(Integer.valueOf(42)); assertThat(component.getText()).as("text should have been configured").isEqualTo("enigma"); @@ -380,14 +380,14 @@ void invokeMethodWithIncompatibleArgumentTypes() { @Test void invokeMethodWithTooFewArguments() { assertThatIllegalStateException() - .isThrownBy(() -> invokeMethod(component, "configure", Integer.valueOf(42))) + .isThrownBy(() -> invokeMethod(component, "configure", 42)) .withMessageStartingWith("Method not found"); } @Test void invokeMethodWithTooManyArguments() { assertThatIllegalStateException() - .isThrownBy(() -> invokeMethod(component, "configure", Integer.valueOf(42), "enigma", "baz", "quux")) + .isThrownBy(() -> invokeMethod(component, "configure", 42, "enigma", "baz", "quux")) .withMessageStartingWith("Method not found"); } @@ -406,7 +406,7 @@ void setFieldOnLegacyEntityWithSideEffectsInToString() { @Test // SPR-14363 void invokeMethodOnLegacyEntityWithSideEffectsInToString() { - invokeMethod(entity, "configure", Integer.valueOf(42), "enigma"); + invokeMethod(entity, "configure", 42, "enigma"); assertThat(entity.getNumber()).as("number should have been configured").isEqualTo(Integer.valueOf(42)); assertThat(entity.getText()).as("text should have been configured").isEqualTo("enigma"); } diff --git a/spring-test/src/test/java/org/springframework/test/util/TestSocketUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/TestSocketUtilsTests.java new file mode 100644 index 000000000000..8ebc85f85fd3 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/util/TestSocketUtilsTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2022 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.test.util; + +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Unit tests for {@link TestSocketUtils}. + * + * @author Sam Brannen + * @author Gary Russell + * @since 5.3.24 + */ +class TestSocketUtilsTests { + + @Test + void canBeInstantiated() { + // Just making sure somebody doesn't try to make SocketUtils abstract, + // since that would be a breaking change due to the intentional public + // constructor. + new TestSocketUtils(); + } + + @RepeatedTest(10) + void findAvailableTcpPort() { + assertThat(TestSocketUtils.findAvailableTcpPort()) + .isBetween(TestSocketUtils.PORT_RANGE_MIN, TestSocketUtils.PORT_RANGE_MAX); + } + + @Test + void findAvailableTcpPortWhenNoAvailablePortFoundInMaxAttempts() { + TestSocketUtils socketUtils = new TestSocketUtils() { + @Override + boolean isPortAvailable(int port) { + return false; + } + }; + assertThatIllegalStateException() + .isThrownBy(socketUtils::findAvailableTcpPortInternal) + .withMessage("Could not find an available TCP port in the range [1024, 65535] after 1000 attempts"); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/client/DefaultRequestExpectationTests.java b/spring-test/src/test/java/org/springframework/test/web/client/DefaultRequestExpectationTests.java index 2c15775637e7..e602c034de93 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/DefaultRequestExpectationTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/DefaultRequestExpectationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -17,7 +17,6 @@ package org.springframework.test.web.client; import java.net.URI; -import java.net.URISyntaxException; import org.junit.jupiter.api.Test; @@ -38,17 +37,16 @@ * Unit tests for {@link DefaultRequestExpectation}. * @author Rossen Stoyanchev */ -public class DefaultRequestExpectationTests { - +class DefaultRequestExpectationTests { @Test - public void match() throws Exception { + void match() throws Exception { RequestExpectation expectation = new DefaultRequestExpectation(once(), requestTo("/foo")); expectation.match(createRequest()); } @Test - public void matchWithFailedExpectation() { + void matchWithFailedExpectation() { RequestExpectation expectation = new DefaultRequestExpectation(once(), requestTo("/foo")); expectation.andExpect(method(POST)); assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> @@ -57,7 +55,7 @@ public void matchWithFailedExpectation() { } @Test - public void hasRemainingCount() { + void hasRemainingCount() { RequestExpectation expectation = new DefaultRequestExpectation(twice(), requestTo("/foo")); expectation.andRespond(withSuccess()); @@ -69,7 +67,7 @@ public void hasRemainingCount() { } @Test - public void isSatisfied() { + void isSatisfied() { RequestExpectation expectation = new DefaultRequestExpectation(twice(), requestTo("/foo")); expectation.andRespond(withSuccess()); @@ -82,12 +80,7 @@ public void isSatisfied() { private ClientHttpRequest createRequest() { - try { - return new MockClientHttpRequest(HttpMethod.GET, new URI("/foo")); - } - catch (URISyntaxException ex) { - throw new IllegalStateException(ex); - } + return new MockClientHttpRequest(HttpMethod.GET, URI.create("/foo")); } } diff --git a/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java b/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java index a9974bddadda..36c783f4279e 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -38,13 +38,13 @@ * * @author Rossen Stoyanchev */ -public class MockRestServiceServerTests { +class MockRestServiceServerTests { private final RestTemplate restTemplate = new RestTemplate(); @Test - public void buildMultipleTimes() { + void buildMultipleTimes() { MockRestServiceServerBuilder builder = MockRestServiceServer.bindTo(this.restTemplate); MockRestServiceServer server = builder.build(); @@ -66,7 +66,7 @@ public void buildMultipleTimes() { } @Test - public void exactExpectOrder() { + void exactExpectOrder() { MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate) .ignoreExpectOrder(false).build(); @@ -77,7 +77,7 @@ public void exactExpectOrder() { } @Test - public void ignoreExpectOrder() { + void ignoreExpectOrder() { MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate) .ignoreExpectOrder(true).build(); @@ -89,7 +89,7 @@ public void ignoreExpectOrder() { } @Test - public void resetAndReuseServer() { + void resetAndReuseServer() { MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate).build(); server.expect(requestTo("/foo")).andRespond(withSuccess()); @@ -103,7 +103,7 @@ public void resetAndReuseServer() { } @Test - public void resetAndReuseServerWithUnorderedExpectationManager() { + void resetAndReuseServerWithUnorderedExpectationManager() { MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate) .ignoreExpectOrder(true).build(); @@ -120,7 +120,7 @@ public void resetAndReuseServerWithUnorderedExpectationManager() { } @Test // gh-24486 - public void resetClearsRequestFailures() { + void resetClearsRequestFailures() { MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate).build(); server.expect(once(), requestTo("/remoteurl")).andRespond(withSuccess()); this.restTemplate.postForEntity("/remoteurl", null, String.class); @@ -138,7 +138,7 @@ public void resetClearsRequestFailures() { } @Test // SPR-16132 - public void followUpRequestAfterFailure() { + void followUpRequestAfterFailure() { MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate).build(); server.expect(requestTo("/some-service/some-endpoint")) @@ -159,7 +159,7 @@ public void followUpRequestAfterFailure() { } @Test // gh-21799 - public void verifyShouldFailIfRequestsFailed() { + void verifyShouldFailIfRequestsFailed() { MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate).build(); server.expect(once(), requestTo("/remoteurl")).andRespond(withSuccess()); @@ -174,7 +174,7 @@ public void verifyShouldFailIfRequestsFailed() { } @Test - public void verifyWithTimeout() { + void verifyWithTimeout() { MockRestServiceServerBuilder builder = MockRestServiceServer.bindTo(this.restTemplate); MockRestServiceServer server1 = builder.build(); @@ -182,10 +182,11 @@ public void verifyWithTimeout() { server1.expect(requestTo("/bar")).andRespond(withSuccess()); this.restTemplate.getForObject("/foo", Void.class); - assertThatThrownBy(() -> server1.verify(Duration.ofMillis(100))).hasMessage( - "Further request(s) expected leaving 1 unsatisfied expectation(s).\n" + - "1 request(s) executed:\n" + - "GET /foo, headers: [Accept:\"application/json, application/*+json\"]\n"); + assertThatThrownBy(() -> server1.verify(Duration.ofMillis(100))).hasMessage(""" + Further request(s) expected leaving 1 unsatisfied expectation(s). + 1 request(s) executed: + GET /foo, headers: [Accept:"application/json, application/*+json"] + """); MockRestServiceServer server2 = builder.build(); server2.expect(requestTo("/foo")).andRespond(withSuccess()); diff --git a/spring-test/src/test/java/org/springframework/test/web/client/SimpleRequestExpectationManagerTests.java b/spring-test/src/test/java/org/springframework/test/web/client/SimpleRequestExpectationManagerTests.java index 33efa8d17fb0..eed9e0415e3d 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/SimpleRequestExpectationManagerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/SimpleRequestExpectationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -18,7 +18,6 @@ import java.net.SocketException; import java.net.URI; -import java.net.URISyntaxException; import org.junit.jupiter.api.Test; @@ -26,7 +25,6 @@ import org.springframework.http.client.ClientHttpRequest; import org.springframework.mock.http.client.MockClientHttpRequest; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; @@ -44,29 +42,28 @@ * * @author Rossen Stoyanchev */ -public class SimpleRequestExpectationManagerTests { +class SimpleRequestExpectationManagerTests { private final SimpleRequestExpectationManager manager = new SimpleRequestExpectationManager(); @Test - public void unexpectedRequest() throws Exception { - try { - this.manager.validateRequest(createRequest(GET, "/foo")); - } - catch (AssertionError error) { - assertThat(error.getMessage()).isEqualTo(("No further requests expected: HTTP GET /foo\n" + - "0 request(s) executed.\n")); - } + void unexpectedRequest() throws Exception { + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> this.manager.validateRequest(createRequest(GET, "/foo"))) + .withMessage(""" + No further requests expected: HTTP GET /foo + 0 request(s) executed. + """); } @Test - public void zeroExpectedRequests() throws Exception { + void zeroExpectedRequests() throws Exception { this.manager.verify(); } @Test - public void sequentialRequests() throws Exception { + void sequentialRequests() throws Exception { this.manager.expectRequest(once(), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(once(), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); @@ -76,32 +73,37 @@ public void sequentialRequests() throws Exception { } @Test - public void sequentialRequestsTooMany() throws Exception { + void sequentialRequestsTooMany() throws Exception { this.manager.expectRequest(max(1), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(max(1), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.validateRequest(createRequest(GET, "/foo")); this.manager.validateRequest(createRequest(GET, "/bar")); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - this.manager.validateRequest(createRequest(GET, "/baz"))) - .withMessage("No further requests expected: HTTP GET /baz\n" + - "2 request(s) executed:\n" + - "GET /foo\n" + - "GET /bar\n"); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> this.manager.validateRequest(createRequest(GET, "/baz"))) + .withMessage(""" + No further requests expected: HTTP GET /baz + 2 request(s) executed: + GET /foo + GET /bar + """); } @Test - public void sequentialRequestsTooFew() throws Exception { + void sequentialRequestsTooFew() throws Exception { this.manager.expectRequest(min(1), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(min(1), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.validateRequest(createRequest(GET, "/foo")); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - this.manager.verify()) - .withMessage("Further request(s) expected leaving 1 unsatisfied expectation(s).\n" + - "1 request(s) executed:\nGET /foo\n"); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> this.manager.verify()) + .withMessage(""" + Further request(s) expected leaving 1 unsatisfied expectation(s). + 1 request(s) executed: + GET /foo + """); } @Test - public void repeatedRequests() throws Exception { + void repeatedRequests() throws Exception { this.manager.expectRequest(times(3), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(times(3), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); @@ -115,50 +117,54 @@ public void repeatedRequests() throws Exception { } @Test - public void repeatedRequestsTooMany() throws Exception { + void repeatedRequestsTooMany() throws Exception { this.manager.expectRequest(max(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(max(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.validateRequest(createRequest(GET, "/foo")); this.manager.validateRequest(createRequest(GET, "/bar")); this.manager.validateRequest(createRequest(GET, "/foo")); this.manager.validateRequest(createRequest(GET, "/bar")); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - this.manager.validateRequest(createRequest(GET, "/foo"))) - .withMessage("No further requests expected: HTTP GET /foo\n" + - "4 request(s) executed:\n" + - "GET /foo\n" + - "GET /bar\n" + - "GET /foo\n" + - "GET /bar\n"); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> this.manager.validateRequest(createRequest(GET, "/foo"))) + .withMessage(""" + No further requests expected: HTTP GET /foo + 4 request(s) executed: + GET /foo + GET /bar + GET /foo + GET /bar + """); } @Test - public void repeatedRequestsTooFew() throws Exception { + void repeatedRequestsTooFew() throws Exception { this.manager.expectRequest(min(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(min(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.validateRequest(createRequest(GET, "/foo")); this.manager.validateRequest(createRequest(GET, "/bar")); this.manager.validateRequest(createRequest(GET, "/foo")); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - this.manager.verify()) - .withMessageContaining("3 request(s) executed:\n" + - "GET /foo\n" + - "GET /bar\n" + - "GET /foo\n"); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> this.manager.verify()) + .withMessageContaining(""" + 3 request(s) executed: + GET /foo + GET /bar + GET /foo + """); } @Test - public void repeatedRequestsNotInOrder() throws Exception { + void repeatedRequestsNotInOrder() throws Exception { this.manager.expectRequest(twice(), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(twice(), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(twice(), requestTo("/baz")).andExpect(method(GET)).andRespond(withSuccess()); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - this.manager.validateRequest(createRequest(POST, "/foo"))) + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> this.manager.validateRequest(createRequest(POST, "/foo"))) .withMessage("Unexpected HttpMethod expected: but was:"); } @Test // SPR-15672 - public void sequentialRequestsWithDifferentCount() throws Exception { + void sequentialRequestsWithDifferentCount() throws Exception { this.manager.expectRequest(times(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(once(), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); @@ -168,7 +174,7 @@ public void sequentialRequestsWithDifferentCount() throws Exception { } @Test // SPR-15719 - public void repeatedRequestsInSequentialOrder() throws Exception { + void repeatedRequestsInSequentialOrder() throws Exception { this.manager.expectRequest(times(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(times(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); @@ -179,7 +185,7 @@ public void repeatedRequestsInSequentialOrder() throws Exception { } @Test // SPR-16132 - public void sequentialRequestsWithFirstFailing() throws Exception { + void sequentialRequestsWithFirstFailing() throws Exception { this.manager.expectRequest(once(), requestTo("/foo")). andExpect(method(GET)).andRespond(request -> { throw new SocketException("pseudo network error"); }); this.manager.expectRequest(once(), requestTo("/handle-error")). @@ -192,12 +198,7 @@ public void sequentialRequestsWithFirstFailing() throws Exception { private ClientHttpRequest createRequest(HttpMethod method, String url) { - try { - return new MockClientHttpRequest(method, new URI(url)); - } - catch (URISyntaxException ex) { - throw new IllegalStateException(ex); - } + return new MockClientHttpRequest(method, URI.create(url)); } } diff --git a/spring-test/src/test/java/org/springframework/test/web/client/UnorderedRequestExpectationManagerTests.java b/spring-test/src/test/java/org/springframework/test/web/client/UnorderedRequestExpectationManagerTests.java index 973a44df586e..433183c61004 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/UnorderedRequestExpectationManagerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/UnorderedRequestExpectationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -17,7 +17,6 @@ package org.springframework.test.web.client; import java.net.URI; -import java.net.URISyntaxException; import org.junit.jupiter.api.Test; @@ -25,7 +24,6 @@ import org.springframework.http.client.ClientHttpRequest; import org.springframework.mock.http.client.MockClientHttpRequest; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.http.HttpMethod.GET; import static org.springframework.test.web.client.ExpectedCount.max; @@ -41,29 +39,28 @@ * * @author Rossen Stoyanchev */ -public class UnorderedRequestExpectationManagerTests { +class UnorderedRequestExpectationManagerTests { private final UnorderedRequestExpectationManager manager = new UnorderedRequestExpectationManager(); @Test - public void unexpectedRequest() throws Exception { - try { - this.manager.validateRequest(createRequest(GET, "/foo")); - } - catch (AssertionError error) { - assertThat(error.getMessage()).isEqualTo(("No further requests expected: HTTP GET /foo\n" + - "0 request(s) executed.\n")); - } + void unexpectedRequest() { + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> this.manager.validateRequest(createRequest(GET, "/foo"))) + .withMessage(""" + No further requests expected: HTTP GET /foo + 0 request(s) executed. + """); } @Test - public void zeroExpectedRequests() { + void zeroExpectedRequests() { this.manager.verify(); } @Test - public void multipleRequests() throws Exception { + void multipleRequests() throws Exception { this.manager.expectRequest(once(), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(once(), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); @@ -73,7 +70,7 @@ public void multipleRequests() throws Exception { } @Test - public void repeatedRequests() throws Exception { + void repeatedRequests() throws Exception { this.manager.expectRequest(twice(), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(twice(), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); @@ -85,46 +82,45 @@ public void repeatedRequests() throws Exception { } @Test - public void repeatedRequestsTooMany() throws Exception { + void repeatedRequestsTooMany() throws Exception { this.manager.expectRequest(max(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(max(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.validateRequest(createRequest(GET, "/bar")); this.manager.validateRequest(createRequest(GET, "/foo")); this.manager.validateRequest(createRequest(GET, "/bar")); this.manager.validateRequest(createRequest(GET, "/foo")); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - this.manager.validateRequest(createRequest(GET, "/foo"))) - .withMessage("No further requests expected: HTTP GET /foo\n" + - "4 request(s) executed:\n" + - "GET /bar\n" + - "GET /foo\n" + - "GET /bar\n" + - "GET /foo\n"); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> this.manager.validateRequest(createRequest(GET, "/foo"))) + .withMessage(""" + No further requests expected: HTTP GET /foo + 4 request(s) executed: + GET /bar + GET /foo + GET /bar + GET /foo + """); } @Test - public void repeatedRequestsTooFew() throws Exception { + void repeatedRequestsTooFew() throws Exception { this.manager.expectRequest(min(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.expectRequest(min(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); this.manager.validateRequest(createRequest(GET, "/bar")); this.manager.validateRequest(createRequest(GET, "/foo")); this.manager.validateRequest(createRequest(GET, "/foo")); assertThatExceptionOfType(AssertionError.class) - .isThrownBy(this.manager::verify) - .withMessageContaining("3 request(s) executed:\n" + - "GET /bar\n" + - "GET /foo\n" + - "GET /foo\n"); + .isThrownBy(this.manager::verify) + .withMessageContaining(""" + 3 request(s) executed: + GET /bar + GET /foo + GET /foo + """); } private ClientHttpRequest createRequest(HttpMethod method, String url) { - try { - return new MockClientHttpRequest(method, new URI(url)); - } - catch (URISyntaxException ex) { - throw new IllegalStateException(ex); - } + return new MockClientHttpRequest(method, URI.create(url)); } } diff --git a/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java index 6481a3313bd7..562324593966 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -36,28 +36,28 @@ * @author Rossen Stoyanchev * @author Sam Brannen */ -public class MockRestRequestMatchersTests { +class MockRestRequestMatchersTests { private final MockClientHttpRequest request = new MockClientHttpRequest(); @Test - public void requestTo() throws Exception { - this.request.setURI(new URI("http://www.foo.example/bar")); + void requestTo() throws Exception { + this.request.setURI(URI.create("http://www.foo.example/bar")); MockRestRequestMatchers.requestTo("http://www.foo.example/bar").match(this.request); } @Test // SPR-15819 - public void requestToUriTemplate() throws Exception { - this.request.setURI(new URI("http://www.foo.example/bar")); + void requestToUriTemplate() throws Exception { + this.request.setURI(URI.create("http://www.foo.example/bar")); MockRestRequestMatchers.requestToUriTemplate("http://www.foo.example/{bar}", "bar").match(this.request); } @Test - public void requestToNoMatch() throws Exception { - this.request.setURI(new URI("http://www.foo.example/bar")); + void requestToNoMatch() { + this.request.setURI(URI.create("http://www.foo.example/bar")); assertThatThrownBy( () -> MockRestRequestMatchers.requestTo("http://www.foo.example/wrong").match(this.request)) @@ -65,21 +65,21 @@ public void requestToNoMatch() throws Exception { } @Test - public void requestToContains() throws Exception { - this.request.setURI(new URI("http://www.foo.example/bar")); + void requestToContains() throws Exception { + this.request.setURI(URI.create("http://www.foo.example/bar")); MockRestRequestMatchers.requestTo(containsString("bar")).match(this.request); } @Test - public void method() throws Exception { + void method() throws Exception { this.request.setMethod(HttpMethod.GET); MockRestRequestMatchers.method(HttpMethod.GET).match(this.request); } @Test - public void methodNoMatch() throws Exception { + void methodNoMatch() { this.request.setMethod(HttpMethod.POST); assertThatThrownBy(() -> MockRestRequestMatchers.method(HttpMethod.GET).match(this.request)) @@ -88,14 +88,14 @@ public void methodNoMatch() throws Exception { } @Test - public void header() throws Exception { + void header() throws Exception { this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); MockRestRequestMatchers.header("foo", "bar", "baz").match(this.request); } @Test - public void headerDoesNotExist() throws Exception { + void headerDoesNotExist() throws Exception { MockRestRequestMatchers.headerDoesNotExist(null).match(this.request); MockRestRequestMatchers.headerDoesNotExist("").match(this.request); MockRestRequestMatchers.headerDoesNotExist("foo").match(this.request); @@ -108,14 +108,14 @@ public void headerDoesNotExist() throws Exception { } @Test - public void headerMissing() throws Exception { + void headerMissing() { assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bar").match(this.request)) .isInstanceOf(AssertionError.class) .hasMessageContaining("was null"); } @Test - public void headerMissingValue() throws Exception { + void headerMissingValue() { this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bad").match(this.request)) @@ -124,21 +124,21 @@ public void headerMissingValue() throws Exception { } @Test - public void headerContains() throws Exception { + void headerContains() throws Exception { this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); MockRestRequestMatchers.header("foo", containsString("ba")).match(this.request); } @Test - public void headerContainsWithMissingHeader() throws Exception { + void headerContainsWithMissingHeader() { assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", containsString("baz")).match(this.request)) .isInstanceOf(AssertionError.class) .hasMessageContaining("but was null"); } @Test - public void headerContainsWithMissingValue() throws Exception { + void headerContainsWithMissingValue() { this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", containsString("bx")).match(this.request)) @@ -147,21 +147,21 @@ public void headerContainsWithMissingValue() throws Exception { } @Test - public void headers() throws Exception { + void headers() throws Exception { this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); MockRestRequestMatchers.header("foo", "bar", "baz").match(this.request); } @Test - public void headersWithMissingHeader() throws Exception { + void headersWithMissingHeader() { assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bar").match(this.request)) .isInstanceOf(AssertionError.class) .hasMessageContaining("but was null"); } @Test - public void headersWithMissingValue() throws Exception { + void headersWithMissingValue() { this.request.getHeaders().put("foo", Collections.singletonList("bar")); assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bar", "baz").match(this.request)) @@ -170,15 +170,15 @@ public void headersWithMissingValue() throws Exception { } @Test - public void queryParam() throws Exception { - this.request.setURI(new URI("http://www.foo.example/a?foo=bar&foo=baz")); + void queryParam() throws Exception { + this.request.setURI(URI.create("http://www.foo.example/a?foo=bar&foo=baz")); MockRestRequestMatchers.queryParam("foo", "bar", "baz").match(this.request); } @Test - public void queryParamMissing() throws Exception { - this.request.setURI(new URI("http://www.foo.example/a")); + void queryParamMissing() { + this.request.setURI(URI.create("http://www.foo.example/a")); assertThatThrownBy(() -> MockRestRequestMatchers.queryParam("foo", "bar").match(this.request)) .isInstanceOf(AssertionError.class) @@ -186,8 +186,8 @@ public void queryParamMissing() throws Exception { } @Test - public void queryParamMissingValue() throws Exception { - this.request.setURI(new URI("http://www.foo.example/a?foo=bar&foo=baz")); + void queryParamMissingValue() { + this.request.setURI(URI.create("http://www.foo.example/a?foo=bar&foo=baz")); assertThatThrownBy(() -> MockRestRequestMatchers.queryParam("foo", "bad").match(this.request)) .isInstanceOf(AssertionError.class) @@ -195,15 +195,15 @@ public void queryParamMissingValue() throws Exception { } @Test - public void queryParamContains() throws Exception { - this.request.setURI(new URI("http://www.foo.example/a?foo=bar&foo=baz")); + void queryParamContains() throws Exception { + this.request.setURI(URI.create("http://www.foo.example/a?foo=bar&foo=baz")); MockRestRequestMatchers.queryParam("foo", containsString("ba")).match(this.request); } @Test - public void queryParamContainsWithMissingValue() throws Exception { - this.request.setURI(new URI("http://www.foo.example/a?foo=bar&foo=baz")); + void queryParamContainsWithMissingValue() { + this.request.setURI(URI.create("http://www.foo.example/a?foo=bar&foo=baz")); assertThatThrownBy(() -> MockRestRequestMatchers.queryParam("foo", containsString("bx")).match(this.request)) .isInstanceOf(AssertionError.class) diff --git a/spring-test/src/test/java/org/springframework/test/web/client/response/ResponseCreatorsTests.java b/spring-test/src/test/java/org/springframework/test/web/client/response/ResponseCreatorsTests.java index 9a043b73e31c..5a0ffe66258f 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/response/ResponseCreatorsTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/response/ResponseCreatorsTests.java @@ -45,8 +45,8 @@ void success() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) MockRestResponseCreators.withSuccess().createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(response.getHeaders().isEmpty()).isTrue(); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(response.getHeaders()).isEmpty(); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -71,13 +71,13 @@ void successWithContentWithoutContentType() throws Exception { @Test void created() throws Exception { - URI location = new URI("/foo"); + URI location = URI.create("/foo"); DefaultResponseCreator responseCreator = MockRestResponseCreators.withCreatedEntity(location); MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); assertThat(response.getHeaders().getLocation()).isEqualTo(location); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -86,7 +86,7 @@ void accepted() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -95,8 +95,8 @@ void noContent() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); - assertThat(response.getHeaders().isEmpty()).isTrue(); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(response.getHeaders()).isEmpty(); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -105,8 +105,8 @@ void badRequest() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - assertThat(response.getHeaders().isEmpty()).isTrue(); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(response.getHeaders()).isEmpty(); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -115,8 +115,8 @@ void unauthorized() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); - assertThat(response.getHeaders().isEmpty()).isTrue(); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(response.getHeaders()).isEmpty(); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -125,7 +125,7 @@ void forbiddenRequest() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -134,7 +134,7 @@ void resourceNotFound() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -143,7 +143,7 @@ void requestConflict() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CONFLICT); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -153,7 +153,7 @@ void tooManyRequests() throws Exception { assertThat(response.getStatusCode()).isEqualTo(HttpStatus.TOO_MANY_REQUESTS); assertThat(response.getHeaders()).doesNotContainKey(HttpHeaders.RETRY_AFTER); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -163,7 +163,7 @@ void tooManyRequestsWithRetryAfter() throws Exception { assertThat(response.getStatusCode()).isEqualTo(HttpStatus.TOO_MANY_REQUESTS); assertThat(response.getHeaders().getFirst(HttpHeaders.RETRY_AFTER)).isEqualTo("512"); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -172,8 +172,8 @@ void serverError() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); - assertThat(response.getHeaders().isEmpty()).isTrue(); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(response.getHeaders()).isEmpty(); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -182,7 +182,7 @@ void badGateway() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_GATEWAY); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -191,7 +191,7 @@ void serviceUnavailable() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -200,7 +200,7 @@ void gatewayTimeout() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.GATEWAY_TIMEOUT); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test @@ -209,8 +209,8 @@ void withStatus() throws Exception { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - assertThat(response.getHeaders().isEmpty()).isTrue(); - assertThat(StreamUtils.copyToByteArray(response.getBody()).length).isEqualTo(0); + assertThat(response.getHeaders()).isEmpty(); + assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/ContentRequestMatchersIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/ContentRequestMatchersIntegrationTests.java index f2601e1eaf9d..98ddc7110ae9 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/ContentRequestMatchersIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/ContentRequestMatchersIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -17,21 +17,18 @@ package org.springframework.test.web.client.samples.matchers; import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.test.web.Person; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestTemplate; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.hamcrest.Matchers.startsWith; import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; @@ -40,68 +37,59 @@ * Examples of defining expectations on request content and content type. * * @author Rossen Stoyanchev + * @author Sam Brannen * @see JsonPathRequestMatchersIntegrationTests * @see XmlContentRequestMatchersIntegrationTests * @see XpathRequestMatchersIntegrationTests */ -public class ContentRequestMatchersIntegrationTests { +class ContentRequestMatchersIntegrationTests { - private MockRestServiceServer mockServer; + private final RestTemplate restTemplate = new RestTemplate(); - private RestTemplate restTemplate; + private final MockRestServiceServer mockServer = MockRestServiceServer.createServer(this.restTemplate); @BeforeEach - public void setup() { - List> converters = new ArrayList<>(); - converters.add(new StringHttpMessageConverter()); - converters.add(new MappingJackson2HttpMessageConverter()); - - this.restTemplate = new RestTemplate(); - this.restTemplate.setMessageConverters(converters); - - this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + void setup() { + this.restTemplate.setMessageConverters( + List.of(new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter())); } @Test - public void contentType() throws Exception { + void contentType() { this.mockServer.expect(content().contentType("application/json")).andRespond(withSuccess()); executeAndVerify(new Person()); } @Test - public void contentTypeNoMatch() throws Exception { + void contentTypeNoMatch() { this.mockServer.expect(content().contentType("application/json;charset=UTF-8")).andRespond(withSuccess()); - try { - executeAndVerify("foo"); - } - catch (AssertionError error) { - String message = error.getMessage(); - assertThat(message.startsWith("Content type expected:")).as(message).isTrue(); - } + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> executeAndVerify("foo")) + .withMessageStartingWith("Content type expected:"); } @Test - public void contentAsString() throws Exception { + void contentAsString() { this.mockServer.expect(content().string("foo")).andRespond(withSuccess()); executeAndVerify("foo"); } @Test - public void contentStringStartsWith() throws Exception { + void contentStringStartsWith() { this.mockServer.expect(content().string(startsWith("foo"))).andRespond(withSuccess()); executeAndVerify("foo123"); } @Test - public void contentAsBytes() throws Exception { + void contentAsBytes() { this.mockServer.expect(content().bytes("foo".getBytes())).andRespond(withSuccess()); executeAndVerify("foo"); } - private void executeAndVerify(Object body) throws URISyntaxException { - this.restTemplate.put(new URI("/foo"), body); + private void executeAndVerify(Object body) { + this.restTemplate.put(URI.create("/foo"), body); this.mockServer.verify(); } diff --git a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java index d3e29e510a8b..1ea1fe609f95 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -17,15 +17,12 @@ package org.springframework.test.web.client.samples.matchers; import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.test.web.Person; @@ -42,31 +39,24 @@ * * @author Rossen Stoyanchev */ -public class HeaderRequestMatchersIntegrationTests { +class HeaderRequestMatchersIntegrationTests { private static final String RESPONSE_BODY = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}"; + private final RestTemplate restTemplate = new RestTemplate(); - private MockRestServiceServer mockServer; - - private RestTemplate restTemplate; + private final MockRestServiceServer mockServer = MockRestServiceServer.createServer(this.restTemplate); @BeforeEach - public void setup() { - List> converters = new ArrayList<>(); - converters.add(new StringHttpMessageConverter()); - converters.add(new MappingJackson2HttpMessageConverter()); - - this.restTemplate = new RestTemplate(); - this.restTemplate.setMessageConverters(converters); - - this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + void setup() { + this.restTemplate.setMessageConverters( + List.of(new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter())); } @Test - public void testString() throws Exception { + void testString() { this.mockServer.expect(requestTo("/person/1")) .andExpect(header("Accept", "application/json, application/*+json")) .andRespond(withSuccess(RESPONSE_BODY, MediaType.APPLICATION_JSON)); @@ -75,7 +65,7 @@ public void testString() throws Exception { } @Test - public void testStringContains() throws Exception { + void testStringContains() { this.mockServer.expect(requestTo("/person/1")) .andExpect(header("Accept", containsString("json"))) .andRespond(withSuccess(RESPONSE_BODY, MediaType.APPLICATION_JSON)); @@ -83,8 +73,8 @@ public void testStringContains() throws Exception { executeAndVerify(); } - private void executeAndVerify() throws URISyntaxException { - this.restTemplate.getForObject(new URI("/person/1"), Person.class); + private void executeAndVerify() { + this.restTemplate.getForObject(URI.create("/person/1"), Person.class); this.mockServer.verify(); } diff --git a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/JsonPathRequestMatchersIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/JsonPathRequestMatchersIntegrationTests.java index 16ab4c0c7714..85b857f2bb07 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/JsonPathRequestMatchersIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/JsonPathRequestMatchersIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -17,7 +17,6 @@ package org.springframework.test.web.client.samples.matchers; import java.net.URI; -import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collections; @@ -51,7 +50,7 @@ * @see org.springframework.test.web.client.match.JsonPathRequestMatchers * @see org.springframework.test.web.client.match.JsonPathRequestMatchersTests */ -public class JsonPathRequestMatchersIntegrationTests { +class JsonPathRequestMatchersIntegrationTests { private static final MultiValueMap people = new LinkedMultiValueMap<>(); @@ -72,7 +71,7 @@ public class JsonPathRequestMatchersIntegrationTests { @Test - public void exists() throws Exception { + void exists() { this.mockServer.expect(requestTo("/composers")) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.composers[0]").exists()) @@ -85,7 +84,7 @@ public void exists() throws Exception { } @Test - public void doesNotExist() throws Exception { + void doesNotExist() { this.mockServer.expect(requestTo("/composers")) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.composers[?(@.name == 'Edvard Grieeeeeeg')]").doesNotExist()) @@ -97,7 +96,7 @@ public void doesNotExist() throws Exception { } @Test - public void value() throws Exception { + void value() { this.mockServer.expect(requestTo("/composers")) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.composers[0].name").value("Johann Sebastian Bach")) @@ -108,7 +107,7 @@ public void value() throws Exception { } @Test - public void hamcrestMatchers() throws Exception { + void hamcrestMatchers() { this.mockServer.expect(requestTo("/composers")) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.composers[0].name").value(equalTo("Johann Sebastian Bach"))) @@ -124,7 +123,7 @@ public void hamcrestMatchers() throws Exception { } @Test - public void hamcrestMatchersWithParameterizedJsonPaths() throws Exception { + void hamcrestMatchersWithParameterizedJsonPaths() { String composerName = "$.composers[%s].name"; String performerName = "$.performers[%s].name"; @@ -140,7 +139,7 @@ public void hamcrestMatchersWithParameterizedJsonPaths() throws Exception { } @Test - public void isArray() throws Exception { + void isArray() { this.mockServer.expect(requestTo("/composers")) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.composers").isArray()) @@ -150,7 +149,7 @@ public void isArray() throws Exception { } @Test - public void isString() throws Exception { + void isString() { this.mockServer.expect(requestTo("/composers")) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.composers[0].name").isString()) @@ -160,7 +159,7 @@ public void isString() throws Exception { } @Test - public void isNumber() throws Exception { + void isNumber() { this.mockServer.expect(requestTo("/composers")) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.composers[0].someDouble").isNumber()) @@ -170,7 +169,7 @@ public void isNumber() throws Exception { } @Test - public void isBoolean() throws Exception { + void isBoolean() { this.mockServer.expect(requestTo("/composers")) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.composers[0].someBoolean").isBoolean()) @@ -179,8 +178,8 @@ public void isBoolean() throws Exception { executeAndVerify(); } - private void executeAndVerify() throws URISyntaxException { - this.restTemplate.put(new URI("/composers"), people); + private void executeAndVerify() { + this.restTemplate.put(URI.create("/composers"), people); this.mockServer.verify(); } diff --git a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/XmlContentRequestMatchersIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/XmlContentRequestMatchersIntegrationTests.java index 23994b1b9852..72b98273ae06 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/XmlContentRequestMatchersIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/XmlContentRequestMatchersIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -17,7 +17,6 @@ package org.springframework.test.web.client.samples.matchers; import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -48,7 +47,7 @@ * @see ContentRequestMatchersIntegrationTests * @see XpathRequestMatchersIntegrationTests */ -public class XmlContentRequestMatchersIntegrationTests { +class XmlContentRequestMatchersIntegrationTests { private static final String PEOPLE_XML = "" + @@ -68,7 +67,7 @@ public class XmlContentRequestMatchersIntegrationTests { @BeforeEach - public void setup() { + void setup() { List composers = Arrays.asList( new Person("Johann Sebastian Bach").setSomeDouble(21), new Person("Johannes Brahms").setSomeDouble(.0025), @@ -87,7 +86,7 @@ public void setup() { } @Test - public void testXmlEqualTo() throws Exception { + void testXmlEqualTo() { this.mockServer.expect(requestTo("/composers")) .andExpect(content().contentType("application/xml")) .andExpect(content().xml(PEOPLE_XML)) @@ -97,7 +96,7 @@ public void testXmlEqualTo() throws Exception { } @Test - public void testHamcrestNodeMatcher() throws Exception { + void testHamcrestNodeMatcher() { this.mockServer.expect(requestTo("/composers")) .andExpect(content().contentType("application/xml")) .andExpect(content().node(hasXPath("/people/composers/composer[1]"))) @@ -106,8 +105,8 @@ public void testHamcrestNodeMatcher() throws Exception { executeAndVerify(); } - private void executeAndVerify() throws URISyntaxException { - this.restTemplate.put(new URI("/composers"), this.people); + private void executeAndVerify() { + this.restTemplate.put(URI.create("/composers"), this.people); this.mockServer.verify(); } diff --git a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/XpathRequestMatchersIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/XpathRequestMatchersIntegrationTests.java index d48d6d881ad7..d1b01cf769e3 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/XpathRequestMatchersIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/XpathRequestMatchersIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -191,7 +191,7 @@ public void testNodeCount() throws Exception { } private void executeAndVerify() throws URISyntaxException { - this.restTemplate.put(new URI("/composers"), this.people); + this.restTemplate.put(URI.create("/composers"), this.people); this.mockServer.verify(); } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/MockServerTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/MockServerTests.java index 0e87e7e3cc54..9bf980ce5036 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/MockServerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/MockServerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -99,17 +99,17 @@ public void mutateDoesCopy() { mutatedBuilder.defaultCookie("baz", "qux"); WebTestClient clientFromMutatedBuilder = mutatedBuilder.build(); - client1.mutate().filters(filters -> assertThat(filters.size()).isEqualTo(1)); - client1.mutate().defaultHeaders(headers -> assertThat(headers.size()).isEqualTo(1)); - client1.mutate().defaultCookies(cookies -> assertThat(cookies.size()).isEqualTo(1)); + client1.mutate().filters(filters -> assertThat(filters).hasSize(1)); + client1.mutate().defaultHeaders(headers -> assertThat(headers).hasSize(1)); + client1.mutate().defaultCookies(cookies -> assertThat(cookies).hasSize(1)); - client2.mutate().filters(filters -> assertThat(filters.size()).isEqualTo(2)); - client2.mutate().defaultHeaders(headers -> assertThat(headers.size()).isEqualTo(2)); - client2.mutate().defaultCookies(cookies -> assertThat(cookies.size()).isEqualTo(2)); + client2.mutate().filters(filters -> assertThat(filters).hasSize(2)); + client2.mutate().defaultHeaders(headers -> assertThat(headers).hasSize(2)); + client2.mutate().defaultCookies(cookies -> assertThat(cookies).hasSize(2)); - clientFromMutatedBuilder.mutate().filters(filters -> assertThat(filters.size()).isEqualTo(2)); - clientFromMutatedBuilder.mutate().defaultHeaders(headers -> assertThat(headers.size()).isEqualTo(2)); - clientFromMutatedBuilder.mutate().defaultCookies(cookies -> assertThat(cookies.size()).isEqualTo(2)); + clientFromMutatedBuilder.mutate().filters(filters -> assertThat(filters).hasSize(2)); + clientFromMutatedBuilder.mutate().defaultHeaders(headers -> assertThat(headers).hasSize(2)); + clientFromMutatedBuilder.mutate().defaultCookies(cookies -> assertThat(cookies).hasSize(2)); } @Test // SPR-16124 diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/SoftAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/SoftAssertionTests.java index db7b242fffd9..f0114200e436 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/SoftAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/SoftAssertionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -56,9 +56,10 @@ void expectAllWithMultipleFailures() { responseSpec -> responseSpec.expectBody(String.class).isEqualTo("bogus") ) ) - .withMessage("Multiple Exceptions (2):\n" + - "Status expected:<400 BAD_REQUEST> but was:<200 OK>\n" + - "Response body expected: but was:"); + .withMessage(""" + Multiple Exceptions (2): + Status expected:<400 BAD_REQUEST> but was:<200 OK> + Response body expected: but was:"""); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java index 05506cfbe3b2..25220ea31e2c 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -38,6 +38,7 @@ import org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection.DelegateWebConnection; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; @@ -51,7 +52,7 @@ * @since 4.2 */ @ExtendWith(MockitoExtension.class) -public class DelegatingWebConnectionTests { +class DelegatingWebConnectionTests { private DelegatingWebConnection webConnection; @@ -77,9 +78,9 @@ public class DelegatingWebConnectionTests { @BeforeEach - public void setup() throws Exception { + void setup() throws Exception { request = new WebRequest(new URL("http://localhost/")); - WebResponseData data = new WebResponseData("".getBytes("UTF-8"), 200, "", Collections.emptyList()); + WebResponseData data = new WebResponseData("".getBytes(UTF_8), 200, "", Collections.emptyList()); expectedResponse = new WebResponse(data, request, 100L); webConnection = new DelegatingWebConnection(defaultConnection, new DelegateWebConnection(matcher1, connection1), new DelegateWebConnection(matcher2, connection2)); @@ -87,7 +88,7 @@ public void setup() throws Exception { @Test - public void getResponseDefault() throws Exception { + void getResponseDefault() throws Exception { given(defaultConnection.getResponse(request)).willReturn(expectedResponse); WebResponse response = webConnection.getResponse(request); @@ -99,7 +100,7 @@ public void getResponseDefault() throws Exception { } @Test - public void getResponseAllMatches() throws Exception { + void getResponseAllMatches() throws Exception { given(matcher1.matches(request)).willReturn(true); given(connection1.getResponse(request)).willReturn(expectedResponse); WebResponse response = webConnection.getResponse(request); @@ -111,7 +112,7 @@ public void getResponseAllMatches() throws Exception { } @Test - public void getResponseSecondMatches() throws Exception { + void getResponseSecondMatches() throws Exception { given(matcher2.matches(request)).willReturn(true); given(connection2.getResponse(request)).willReturn(expectedResponse); WebResponse response = webConnection.getResponse(request); @@ -125,7 +126,7 @@ public void getResponseSecondMatches() throws Exception { @Test @EnabledForTestGroups(LONG_RUNNING) - public void verifyExampleInClassLevelJavadoc() throws Exception { + void verifyExampleInClassLevelJavadoc() throws Exception { WebClient webClient = new WebClient(); MockMvc mockMvc = MockMvcBuilders.standaloneSetup().build(); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java index cecedb54c464..8005cba61e5b 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java @@ -230,7 +230,7 @@ void buildRequestCookiesSingle() { MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext); Cookie[] cookies = actualRequest.getCookies(); - assertThat(cookies.length).isEqualTo(1); + assertThat(cookies).hasSize(1); assertThat(cookies[0].getName()).isEqualTo("name"); assertThat(cookies[0].getValue()).isEqualTo("value"); } @@ -242,7 +242,7 @@ void buildRequestCookiesMulti() { MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext); Cookie[] cookies = actualRequest.getCookies(); - assertThat(cookies.length).isEqualTo(2); + assertThat(cookies).hasSize(2); Cookie cookie = cookies[0]; assertThat(cookie.getName()).isEqualTo("name"); assertThat(cookie.getValue()).isEqualTo("value"); @@ -403,7 +403,7 @@ void buildRequestParameterMapFromSingleQueryParamWithoutValueAndWithoutEqualsSig MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext); - assertThat(actualRequest.getParameterMap().size()).isEqualTo(1); + assertThat(actualRequest.getParameterMap()).hasSize(1); assertThat(actualRequest.getParameter("name")).isEqualTo(""); } @@ -413,7 +413,7 @@ void buildRequestParameterMapFromSingleQueryParamWithoutValueButWithEqualsSign() MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext); - assertThat(actualRequest.getParameterMap().size()).isEqualTo(1); + assertThat(actualRequest.getParameterMap()).hasSize(1); assertThat(actualRequest.getParameter("name")).isEqualTo(""); } @@ -423,7 +423,7 @@ void buildRequestParameterMapFromSingleQueryParamWithValueSetToEncodedSpace() th MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext); - assertThat(actualRequest.getParameterMap().size()).isEqualTo(1); + assertThat(actualRequest.getParameterMap()).hasSize(1); assertThat(actualRequest.getParameter("name")).isEqualTo(" "); } @@ -857,7 +857,7 @@ void mergeCookie() throws Exception { Cookie[] cookies = mockMvc.perform(requestBuilder).andReturn().getRequest().getCookies(); assertThat(cookies).isNotNull(); - assertThat(cookies.length).isEqualTo(1); + assertThat(cookies).hasSize(1); Cookie cookie = cookies[0]; assertThat(cookie.getName()).isEqualTo(cookieName); assertThat(cookie.getValue()).isEqualTo(cookieValue); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java index 9b6a102a2f4b..dd72b0d7b232 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -110,7 +110,7 @@ public void buildResponseHeaders() throws Exception { WebResponse webResponse = this.responseBuilder.build(); List responseHeaders = webResponse.getResponseHeaders(); - assertThat(responseHeaders.size()).isEqualTo(3); + assertThat(responseHeaders).hasSize(3); NameValuePair header = responseHeaders.get(0); assertThat(header.getName()).isEqualTo("Content-Type"); assertThat(header.getValue()).isEqualTo("text/html"); @@ -132,7 +132,7 @@ public void buildResponseHeadersNullDomainDefaulted() throws Exception { WebResponse webResponse = this.responseBuilder.build(); List responseHeaders = webResponse.getResponseHeaders(); - assertThat(responseHeaders.size()).isEqualTo(1); + assertThat(responseHeaders).hasSize(1); NameValuePair header = responseHeaders.get(0); assertThat(header.getName()).isEqualTo("Set-Cookie"); assertThat(header.getValue()).isEqualTo("cookieA=valueA"); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java index 2f5a0f8ecea9..092f19142bb1 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; @@ -34,7 +33,6 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; @@ -46,8 +44,12 @@ import org.springframework.web.servlet.support.SessionFlashMapManager; import org.springframework.web.util.UriComponentsBuilder; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.POST; /** * Unit tests for building a {@link MockHttpServletRequest} with @@ -60,7 +62,7 @@ class MockHttpServletRequestBuilderTests { private final ServletContext servletContext = new MockServletContext(); - private MockHttpServletRequestBuilder builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/foo/bar"); + private MockHttpServletRequestBuilder builder = new MockHttpServletRequestBuilder(GET, "/foo/bar"); @Test @@ -73,7 +75,7 @@ void method() { @Test void uri() { String uri = "https://java.sun.com:8080/javase/6/docs/api/java/util/BitSet.html?foo=bar#and(java.util.BitSet)"; - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, uri); + this.builder = new MockHttpServletRequestBuilder(GET, uri); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); assertThat(request.getScheme()).isEqualTo("https"); @@ -87,7 +89,7 @@ void uri() { @Test void requestUriWithEncoding() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/foo bar"); + this.builder = new MockHttpServletRequestBuilder(GET, "/foo bar"); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); assertThat(request.getRequestURI()).isEqualTo("/foo%20bar"); @@ -95,7 +97,7 @@ void requestUriWithEncoding() { @Test // SPR-13435 void requestUriWithDoubleSlashes() throws URISyntaxException { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, new URI("/test//currentlyValid/0")); + this.builder = new MockHttpServletRequestBuilder(GET, URI.create("/test//currentlyValid/0")); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); assertThat(request.getRequestURI()).isEqualTo("/test//currentlyValid/0"); @@ -109,7 +111,7 @@ void requestUriWithoutScheme() { @Test void contextPathEmpty() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/foo"); + this.builder = new MockHttpServletRequestBuilder(GET, "/foo"); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); assertThat(request.getContextPath()).isEqualTo(""); @@ -119,7 +121,7 @@ void contextPathEmpty() { @Test void contextPathServletPathEmpty() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/travel/hotels/42"); + this.builder = new MockHttpServletRequestBuilder(GET, "/travel/hotels/42"); this.builder.contextPath("/travel"); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); @@ -130,7 +132,7 @@ void contextPathServletPathEmpty() { @Test void contextPathServletPath() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/travel/main/hotels/42"); + this.builder = new MockHttpServletRequestBuilder(GET, "/travel/main/hotels/42"); this.builder.contextPath("/travel"); this.builder.servletPath("/main"); @@ -143,7 +145,7 @@ void contextPathServletPath() { @Test void contextPathServletPathInfoEmpty() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/travel/hotels/42"); + this.builder = new MockHttpServletRequestBuilder(GET, "/travel/hotels/42"); this.builder.contextPath("/travel"); this.builder.servletPath("/hotels/42"); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); @@ -155,7 +157,7 @@ void contextPathServletPathInfoEmpty() { @Test void contextPathServletPathInfo() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/"); + this.builder = new MockHttpServletRequestBuilder(GET, "/"); this.builder.servletPath("/index.html"); this.builder.pathInfo(null); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); @@ -167,7 +169,7 @@ void contextPathServletPathInfo() { @Test // gh-28823 void emptyPath() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, ""); + this.builder = new MockHttpServletRequestBuilder(GET, ""); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); assertThat(request.getRequestURI()).isEqualTo(""); @@ -178,7 +180,7 @@ void emptyPath() { @Test // SPR-16453 void pathInfoIsDecoded() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/travel/hotels 42"); + this.builder = new MockHttpServletRequestBuilder(GET, "/travel/hotels 42"); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); assertThat(request.getPathInfo()).isEqualTo("/travel/hotels 42"); @@ -208,7 +210,7 @@ private void testContextPathServletPathInvalid(String contextPath, String servle @Test void requestUriAndFragment() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/foo#bar"); + this.builder = new MockHttpServletRequestBuilder(GET, "/foo#bar"); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); assertThat(request.getRequestURI()).isEqualTo("/foo"); @@ -226,7 +228,7 @@ void requestParameter() { @Test void requestParameterFromQuery() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/?foo=bar&foo=baz"); + this.builder = new MockHttpServletRequestBuilder(GET, "/?foo=bar&foo=baz"); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); Map parameterMap = request.getParameterMap(); @@ -237,7 +239,7 @@ void requestParameterFromQuery() { @Test void requestParameterFromQueryList() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/?foo[0]=bar&foo[1]=baz"); + this.builder = new MockHttpServletRequestBuilder(GET, "/?foo[0]=bar&foo[1]=baz"); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); @@ -248,7 +250,7 @@ void requestParameterFromQueryList() { @Test void queryParameter() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/"); + this.builder = new MockHttpServletRequestBuilder(GET, "/"); this.builder.queryParam("foo", "bar"); this.builder.queryParam("foo", "baz"); @@ -260,7 +262,7 @@ void queryParameter() { @Test void queryParameterMap() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/"); + this.builder = new MockHttpServletRequestBuilder(GET, "/"); MultiValueMap queryParams = new LinkedMultiValueMap<>(); List values = new ArrayList<>(); values.add("bar"); @@ -276,7 +278,7 @@ void queryParameterMap() { @Test void queryParameterList() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/"); + this.builder = new MockHttpServletRequestBuilder(GET, "/"); this.builder.queryParam("foo[0]", "bar"); this.builder.queryParam("foo[1]", "baz"); @@ -289,7 +291,7 @@ void queryParameterList() { @Test void requestParameterFromQueryWithEncoding() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/?foo={value}", "bar=baz"); + this.builder = new MockHttpServletRequestBuilder(GET, "/?foo={value}", "bar=baz"); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); @@ -299,7 +301,7 @@ void requestParameterFromQueryWithEncoding() { @Test // SPR-11043 void requestParameterFromQueryNull() { - this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/?foo"); + this.builder = new MockHttpServletRequestBuilder(GET, "/?foo"); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); Map parameterMap = request.getParameterMap(); @@ -313,7 +315,7 @@ void requestParameterFromMultiValueMap() throws Exception { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("foo", "bar"); params.add("foo", "baz"); - this.builder = new MockHttpServletRequestBuilder(HttpMethod.POST, "/foo"); + this.builder = new MockHttpServletRequestBuilder(POST, "/foo"); this.builder.params(params); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); @@ -326,8 +328,8 @@ void requestParameterFromRequestBodyFormData() throws Exception { String contentType = "application/x-www-form-urlencoded;charset=UTF-8"; String body = "name+1=value+1&name+2=value+A&name+2=value+B&name+3"; - MockHttpServletRequest request = new MockHttpServletRequestBuilder(HttpMethod.POST, "/foo") - .contentType(contentType).content(body.getBytes(StandardCharsets.UTF_8)) + MockHttpServletRequest request = new MockHttpServletRequestBuilder(POST, "/foo") + .contentType(contentType).content(body.getBytes(UTF_8)) .buildRequest(this.servletContext); assertThat(request.getParameterMap().get("name 1")).containsExactly("value 1"); @@ -343,7 +345,7 @@ void acceptHeader() { List accept = Collections.list(request.getHeaders("Accept")); List result = MediaType.parseMediaTypes(accept.get(0)); - assertThat(accept.size()).isEqualTo(1); + assertThat(accept).hasSize(1); assertThat(result.get(0).toString()).isEqualTo("text/html"); assertThat(result.get(1).toString()).isEqualTo("application/xml"); } @@ -364,7 +366,7 @@ void contentType() { List contentTypes = Collections.list(request.getHeaders("Content-Type")); assertThat(contentType).isEqualTo("text/html"); - assertThat(contentTypes.size()).isEqualTo(1); + assertThat(contentTypes).hasSize(1); assertThat(contentTypes.get(0)).isEqualTo("text/html"); } @@ -377,7 +379,7 @@ void contentTypeViaString() { List contentTypes = Collections.list(request.getHeaders("Content-Type")); assertThat(contentType).isEqualTo("text/html"); - assertThat(contentTypes.size()).isEqualTo(1); + assertThat(contentTypes).hasSize(1); assertThat(contentTypes.get(0)).isEqualTo("text/html"); } @@ -414,7 +416,7 @@ void contentTypeViaMultipleHeaderValues() { @Test void body() throws IOException { - byte[] body = "Hello World".getBytes("UTF-8"); + byte[] body = "Hello World".getBytes(UTF_8); this.builder.content(body); MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); @@ -431,7 +433,7 @@ void header() { MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); List headers = Collections.list(request.getHeaders("foo")); - assertThat(headers.size()).isEqualTo(2); + assertThat(headers).hasSize(2); assertThat(headers.get(0)).isEqualTo("bar"); assertThat(headers.get(1)).isEqualTo("baz"); } @@ -446,7 +448,7 @@ void headers() { MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); List headers = Collections.list(request.getHeaders("foo")); - assertThat(headers.size()).isEqualTo(2); + assertThat(headers).hasSize(2); assertThat(headers.get(0)).isEqualTo("bar"); assertThat(headers.get(1)).isEqualTo("baz"); assertThat(request.getHeader("Content-Type")).isEqualTo(MediaType.APPLICATION_JSON.toString()); @@ -461,7 +463,7 @@ void cookie() { MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); Cookie[] cookies = request.getCookies(); - assertThat(cookies.length).isEqualTo(2); + assertThat(cookies).hasSize(2); assertThat(cookies[0].getName()).isEqualTo("foo"); assertThat(cookies[0].getValue()).isEqualTo("bar"); assertThat(cookies[1].getName()).isEqualTo("baz"); @@ -492,9 +494,9 @@ void characterEncoding() { MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); assertThat(request.getCharacterEncoding()).isEqualTo(encoding); - this.builder.characterEncoding(StandardCharsets.ISO_8859_1); + this.builder.characterEncoding(ISO_8859_1); request = this.builder.buildRequest(this.servletContext); - assertThat(request.getCharacterEncoding()).isEqualTo(StandardCharsets.ISO_8859_1.name()); + assertThat(request.getCharacterEncoding()).isEqualTo(ISO_8859_1.name()); } @Test @@ -563,7 +565,7 @@ void mergeInvokesDefaultRequestPostProcessorFirst() { final String EXPECTED = "override"; MockHttpServletRequestBuilder defaultBuilder = - new MockHttpServletRequestBuilder(HttpMethod.GET, "/foo/bar") + new MockHttpServletRequestBuilder(GET, "/foo/bar") .with(requestAttr(ATTR).value("default")) .with(requestAttr(ATTR).value(EXPECTED)); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/ContentResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/ContentResultMatchersTests.java index 6884bdf15f1c..e8453b5620e5 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/ContentResultMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/ContentResultMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,94 +16,93 @@ package org.springframework.test.web.servlet.result; -import java.nio.charset.StandardCharsets; - import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; -import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.web.servlet.StubMvcResult; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; /** * @author Rossen Stoyanchev * @author Sebastien Deleuze */ -public class ContentResultMatchersTests { +class ContentResultMatchersTests { @Test - public void typeMatches() throws Exception { - new ContentResultMatchers().contentType(MediaType.APPLICATION_JSON_VALUE).match(getStubMvcResult(CONTENT)); + void typeMatches() throws Exception { + new ContentResultMatchers().contentType(APPLICATION_JSON_VALUE).match(getStubMvcResult(CONTENT)); } @Test - public void typeNoMatch() throws Exception { + void typeNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new ContentResultMatchers().contentType("text/plain").match(getStubMvcResult(CONTENT))); } @Test - public void string() throws Exception { - new ContentResultMatchers().string(new String(CONTENT.getBytes("UTF-8"))).match(getStubMvcResult(CONTENT)); + void string() throws Exception { + new ContentResultMatchers().string(new String(CONTENT.getBytes(UTF_8))).match(getStubMvcResult(CONTENT)); } @Test - public void stringNoMatch() throws Exception { + void stringNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new ContentResultMatchers().encoding("bogus").match(getStubMvcResult(CONTENT))); } @Test - public void stringMatcher() throws Exception { - String content = new String(CONTENT.getBytes("UTF-8")); + void stringMatcher() throws Exception { + String content = new String(CONTENT.getBytes(UTF_8)); new ContentResultMatchers().string(Matchers.equalTo(content)).match(getStubMvcResult(CONTENT)); } @Test - public void stringMatcherNoMatch() throws Exception { + void stringMatcherNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new ContentResultMatchers().string(Matchers.equalTo("bogus")).match(getStubMvcResult(CONTENT))); } @Test - public void bytes() throws Exception { - new ContentResultMatchers().bytes(CONTENT.getBytes("UTF-8")).match(getStubMvcResult(CONTENT)); + void bytes() throws Exception { + new ContentResultMatchers().bytes(CONTENT.getBytes(UTF_8)).match(getStubMvcResult(CONTENT)); } @Test - public void bytesNoMatch() throws Exception { + void bytesNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new ContentResultMatchers().bytes("bogus".getBytes()).match(getStubMvcResult(CONTENT))); } @Test - public void jsonLenientMatch() throws Exception { + void jsonLenientMatch() throws Exception { new ContentResultMatchers().json("{\n \"foo\" : \"bar\" \n}").match(getStubMvcResult(CONTENT)); new ContentResultMatchers().json("{\n \"foo\" : \"bar\" \n}", false).match(getStubMvcResult(CONTENT)); } @Test - public void jsonStrictMatch() throws Exception { + void jsonStrictMatch() throws Exception { new ContentResultMatchers().json("{\n \"foo\":\"bar\", \"foo array\":[\"foo\",\"bar\"] \n}", true).match(getStubMvcResult(CONTENT)); new ContentResultMatchers().json("{\n \"foo array\":[\"foo\",\"bar\"], \"foo\":\"bar\" \n}", true).match(getStubMvcResult(CONTENT)); } @Test - public void jsonLenientNoMatch() throws Exception { + void jsonLenientNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new ContentResultMatchers().json("{\n\"fooo\":\"bar\"\n}").match(getStubMvcResult(CONTENT))); } @Test - public void jsonStrictNoMatch() throws Exception { + void jsonStrictNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new ContentResultMatchers().json("{\"foo\":\"bar\", \"foo array\":[\"bar\",\"foo\"]}", true).match(getStubMvcResult(CONTENT))); } @Test // gh-23622 - public void jsonUtf8Match() throws Exception { + void jsonUtf8Match() throws Exception { new ContentResultMatchers().json("{\"name\":\"Jürgen\"}").match(getStubMvcResult(UTF8_CONTENT)); } @@ -113,8 +112,8 @@ public void jsonUtf8Match() throws Exception { private StubMvcResult getStubMvcResult(String content) throws Exception { MockHttpServletResponse response = new MockHttpServletResponse(); - response.addHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE); - response.getOutputStream().write(content.getBytes(StandardCharsets.UTF_8)); + response.addHeader("Content-Type", APPLICATION_JSON_VALUE); + response.getOutputStream().write(content.getBytes(UTF_8)); return new StubMvcResult(null, null, null, null, null, null, response); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java index 03767591f66b..c32f9c384dea 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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,14 +16,14 @@ package org.springframework.test.web.servlet.result; -import java.nio.charset.StandardCharsets; - import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.web.servlet.StubMvcResult; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** @@ -35,19 +35,20 @@ * @author Brian Clozel * @author Sebastien Deleuze */ -public class JsonPathResultMatchersTests { - - private static final String RESPONSE_CONTENT = "{" + // - "'str': 'foo', " + // - "'utf8Str': 'Příliš', " + // - "'num': 5, " + // - "'bool': true, " + // - "'arr': [42], " + // - "'colorMap': {'red': 'rojo'}, " + // - "'emptyString': '', " + // - "'emptyArray': [], " + // - "'emptyMap': {} " + // - "}"; +class JsonPathResultMatchersTests { + + private static final String RESPONSE_CONTENT = """ + { + 'str': 'foo', + 'utf8Str': 'Příliš', + 'num': 5, + 'bool': true, + 'arr': [42], + 'colorMap': {'red': 'rojo'}, + 'emptyString': '', + 'emptyArray': [], + 'emptyMap': {} + }"""; private static final StubMvcResult stubMvcResult; @@ -55,7 +56,7 @@ public class JsonPathResultMatchersTests { try { MockHttpServletResponse response = new MockHttpServletResponse(); response.addHeader("Content-Type", "application/json"); - response.getOutputStream().write(RESPONSE_CONTENT.getBytes(StandardCharsets.UTF_8)); + response.getOutputStream().write(RESPONSE_CONTENT.getBytes(UTF_8)); stubMvcResult = new StubMvcResult(null, null, null, null, null, null, response); } catch (Exception e) { @@ -63,220 +64,221 @@ public class JsonPathResultMatchersTests { } } + @Test - public void valueWithValueMismatch() throws Exception { + void valueWithValueMismatch() throws Exception { assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> new JsonPathResultMatchers("$.str").value("bogus").match(stubMvcResult)) .withMessage("JSON path \"$.str\" expected: but was:"); } @Test - public void valueWithTypeMismatch() throws Exception { + void valueWithTypeMismatch() throws Exception { assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> new JsonPathResultMatchers("$.str").value("bogus".getBytes()).match(stubMvcResult)) .withMessage("At JSON path \"$.str\", value of type cannot be converted to type "); } @Test - public void valueWithDirectMatch() throws Exception { + void valueWithDirectMatch() throws Exception { new JsonPathResultMatchers("$.str").value("foo").match(stubMvcResult); } @Test // gh-23219 - public void utf8ValueWithDirectMatch() throws Exception { + void utf8ValueWithDirectMatch() throws Exception { new JsonPathResultMatchers("$.utf8Str").value("Příliš").match(stubMvcResult); } @Test // SPR-16587 - public void valueWithNumberConversion() throws Exception { + void valueWithNumberConversion() throws Exception { new JsonPathResultMatchers("$.num").value(5.0f).match(stubMvcResult); } @Test - public void valueWithMatcher() throws Exception { + void valueWithMatcher() throws Exception { new JsonPathResultMatchers("$.str").value(Matchers.equalTo("foo")).match(stubMvcResult); } @Test // SPR-16587 - public void valueWithMatcherAndNumberConversion() throws Exception { + void valueWithMatcherAndNumberConversion() throws Exception { new JsonPathResultMatchers("$.num").value(Matchers.equalTo(5.0f), Float.class).match(stubMvcResult); } @Test - public void valueWithMatcherAndMismatch() throws Exception { + void valueWithMatcherAndMismatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.str").value(Matchers.equalTo("bogus")).match(stubMvcResult)); } @Test - public void exists() throws Exception { + void exists() throws Exception { new JsonPathResultMatchers("$.str").exists().match(stubMvcResult); } @Test - public void existsForAnEmptyArray() throws Exception { + void existsForAnEmptyArray() throws Exception { new JsonPathResultMatchers("$.emptyArray").exists().match(stubMvcResult); } @Test - public void existsForAnEmptyMap() throws Exception { + void existsForAnEmptyMap() throws Exception { new JsonPathResultMatchers("$.emptyMap").exists().match(stubMvcResult); } @Test - public void existsNoMatch() throws Exception { + void existsNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.bogus").exists().match(stubMvcResult)); } @Test - public void doesNotExist() throws Exception { + void doesNotExist() throws Exception { new JsonPathResultMatchers("$.bogus").doesNotExist().match(stubMvcResult); } @Test - public void doesNotExistNoMatch() throws Exception { + void doesNotExistNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.str").doesNotExist().match(stubMvcResult)); } @Test - public void doesNotExistForAnEmptyArray() throws Exception { + void doesNotExistForAnEmptyArray() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.emptyArray").doesNotExist().match(stubMvcResult)); } @Test - public void doesNotExistForAnEmptyMap() throws Exception { + void doesNotExistForAnEmptyMap() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.emptyMap").doesNotExist().match(stubMvcResult)); } @Test - public void isEmptyForAnEmptyString() throws Exception { + void isEmptyForAnEmptyString() throws Exception { new JsonPathResultMatchers("$.emptyString").isEmpty().match(stubMvcResult); } @Test - public void isEmptyForAnEmptyArray() throws Exception { + void isEmptyForAnEmptyArray() throws Exception { new JsonPathResultMatchers("$.emptyArray").isEmpty().match(stubMvcResult); } @Test - public void isEmptyForAnEmptyMap() throws Exception { + void isEmptyForAnEmptyMap() throws Exception { new JsonPathResultMatchers("$.emptyMap").isEmpty().match(stubMvcResult); } @Test - public void isNotEmptyForString() throws Exception { + void isNotEmptyForString() throws Exception { new JsonPathResultMatchers("$.str").isNotEmpty().match(stubMvcResult); } @Test - public void isNotEmptyForNumber() throws Exception { + void isNotEmptyForNumber() throws Exception { new JsonPathResultMatchers("$.num").isNotEmpty().match(stubMvcResult); } @Test - public void isNotEmptyForBoolean() throws Exception { + void isNotEmptyForBoolean() throws Exception { new JsonPathResultMatchers("$.bool").isNotEmpty().match(stubMvcResult); } @Test - public void isNotEmptyForArray() throws Exception { + void isNotEmptyForArray() throws Exception { new JsonPathResultMatchers("$.arr").isNotEmpty().match(stubMvcResult); } @Test - public void isNotEmptyForMap() throws Exception { + void isNotEmptyForMap() throws Exception { new JsonPathResultMatchers("$.colorMap").isNotEmpty().match(stubMvcResult); } @Test - public void isNotEmptyForAnEmptyString() throws Exception { + void isNotEmptyForAnEmptyString() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.emptyString").isNotEmpty().match(stubMvcResult)); } @Test - public void isNotEmptyForAnEmptyArray() throws Exception { + void isNotEmptyForAnEmptyArray() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.emptyArray").isNotEmpty().match(stubMvcResult)); } @Test - public void isNotEmptyForAnEmptyMap() throws Exception { + void isNotEmptyForAnEmptyMap() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.emptyMap").isNotEmpty().match(stubMvcResult)); } @Test - public void isArray() throws Exception { + void isArray() throws Exception { new JsonPathResultMatchers("$.arr").isArray().match(stubMvcResult); } @Test - public void isArrayForAnEmptyArray() throws Exception { + void isArrayForAnEmptyArray() throws Exception { new JsonPathResultMatchers("$.emptyArray").isArray().match(stubMvcResult); } @Test - public void isArrayNoMatch() throws Exception { + void isArrayNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.bar").isArray().match(stubMvcResult)); } @Test - public void isMap() throws Exception { + void isMap() throws Exception { new JsonPathResultMatchers("$.colorMap").isMap().match(stubMvcResult); } @Test - public void isMapForAnEmptyMap() throws Exception { + void isMapForAnEmptyMap() throws Exception { new JsonPathResultMatchers("$.emptyMap").isMap().match(stubMvcResult); } @Test - public void isMapNoMatch() throws Exception { + void isMapNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.str").isMap().match(stubMvcResult)); } @Test - public void isBoolean() throws Exception { + void isBoolean() throws Exception { new JsonPathResultMatchers("$.bool").isBoolean().match(stubMvcResult); } @Test - public void isBooleanNoMatch() throws Exception { + void isBooleanNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.str").isBoolean().match(stubMvcResult)); } @Test - public void isNumber() throws Exception { + void isNumber() throws Exception { new JsonPathResultMatchers("$.num").isNumber().match(stubMvcResult); } @Test - public void isNumberNoMatch() throws Exception { + void isNumberNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.str").isNumber().match(stubMvcResult)); } @Test - public void isString() throws Exception { + void isString() throws Exception { new JsonPathResultMatchers("$.str").isString().match(stubMvcResult); } @Test - public void isStringNoMatch() throws Exception { + void isStringNoMatch() throws Exception { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> new JsonPathResultMatchers("$.arr").isString().match(stubMvcResult)); } @Test - public void valueWithJsonPrefixNotConfigured() throws Exception { + void valueWithJsonPrefixNotConfigured() throws Exception { String jsonPrefix = "prefix"; StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix); assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> @@ -284,7 +286,7 @@ public void valueWithJsonPrefixNotConfigured() throws Exception { } @Test - public void valueWithJsonWrongPrefix() throws Exception { + void valueWithJsonWrongPrefix() throws Exception { String jsonPrefix = "prefix"; StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix); assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> @@ -292,17 +294,17 @@ public void valueWithJsonWrongPrefix() throws Exception { } @Test - public void valueWithJsonPrefix() throws Exception { + void valueWithJsonPrefix() throws Exception { String jsonPrefix = "prefix"; StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix); new JsonPathResultMatchers("$.str").prefix(jsonPrefix).value("foo").match(result); } @Test - public void prefixWithPayloadNotLongEnough() throws Exception { + void prefixWithPayloadNotLongEnough() throws Exception { MockHttpServletResponse response = new MockHttpServletResponse(); response.addHeader("Content-Type", "application/json"); - response.getWriter().print(new String("test".getBytes("ISO-8859-1"))); + response.getWriter().print(new String("test".getBytes(ISO_8859_1))); StubMvcResult result = new StubMvcResult(null, null, null, null, null, null, response); assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> @@ -312,7 +314,7 @@ public void prefixWithPayloadNotLongEnough() throws Exception { private StubMvcResult createPrefixedStubMvcResult(String jsonPrefix) throws Exception { MockHttpServletResponse response = new MockHttpServletResponse(); response.addHeader("Content-Type", "application/json"); - response.getWriter().print(jsonPrefix + new String(RESPONSE_CONTENT.getBytes("ISO-8859-1"))); + response.getWriter().print(jsonPrefix + new String(RESPONSE_CONTENT.getBytes(ISO_8859_1))); return new StubMvcResult(null, null, null, null, null, null, response); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/PrintingResultHandlerTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/PrintingResultHandlerTests.java index 7860292b6a3e..7d3d647f6363 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/PrintingResultHandlerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/PrintingResultHandlerTests.java @@ -42,6 +42,8 @@ import org.springframework.web.servlet.FlashMap; import org.springframework.web.servlet.ModelAndView; +import static java.nio.charset.StandardCharsets.UTF_16; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; /** @@ -51,7 +53,7 @@ * @author Sam Brannen * @see org.springframework.test.web.servlet.samples.standalone.resulthandlers.PrintingResultHandlerSmokeTests */ -public class PrintingResultHandlerTests { +class PrintingResultHandlerTests { private final TestPrintingResultHandler handler = new TestPrintingResultHandler(); @@ -69,12 +71,12 @@ public boolean isAsyncStarted() { @Test - public void printRequest() throws Exception { + void printRequest() throws Exception { this.request.addParameter("param", "paramValue"); this.request.addHeader("header", "headerValue"); this.request.setCharacterEncoding("UTF-16"); String palindrome = "ablE was I ere I saw Elba"; - byte[] bytes = palindrome.getBytes("UTF-16"); + byte[] bytes = palindrome.getBytes(UTF_16); this.request.setContent(bytes); this.request.getSession().setAttribute("foo", "bar"); @@ -95,12 +97,12 @@ public void printRequest() throws Exception { } @Test - public void printRequestWithoutSession() throws Exception { + void printRequestWithoutSession() throws Exception { this.request.addParameter("param", "paramValue"); this.request.addHeader("header", "headerValue"); this.request.setCharacterEncoding("UTF-16"); String palindrome = "ablE was I ere I saw Elba"; - byte[] bytes = palindrome.getBytes("UTF-16"); + byte[] bytes = palindrome.getBytes(UTF_16); this.request.setContent(bytes); this.handler.handle(this.mvcResult); @@ -119,12 +121,12 @@ public void printRequestWithoutSession() throws Exception { } @Test - public void printRequestWithEmptySessionMock() throws Exception { + void printRequestWithEmptySessionMock() throws Exception { this.request.addParameter("param", "paramValue"); this.request.addHeader("header", "headerValue"); this.request.setCharacterEncoding("UTF-16"); String palindrome = "ablE was I ere I saw Elba"; - byte[] bytes = palindrome.getBytes("UTF-16"); + byte[] bytes = palindrome.getBytes(UTF_16); this.request.setContent(bytes); this.request.setSession(Mockito.mock(HttpSession.class)); @@ -145,7 +147,7 @@ public void printRequestWithEmptySessionMock() throws Exception { @Test @SuppressWarnings("removal") - public void printResponse() throws Exception { + void printResponse() throws Exception { Cookie enigmaCookie = new Cookie("enigma", "42"); enigmaCookie.setHttpOnly(true); enigmaCookie.setMaxAge(1234); @@ -166,7 +168,7 @@ public void printResponse() throws Exception { // Manually validate cookie values since maxAge changes... List cookieValues = this.response.getHeaders("Set-Cookie"); - assertThat(cookieValues.size()).isEqualTo(2); + assertThat(cookieValues).hasSize(2); assertThat(cookieValues.get(0)).isEqualTo("cookie=cookieValue"); assertThat(cookieValues.get(1).startsWith( "enigma=42; Path=/crumbs; Domain=.example.com; Max-Age=1234; Expires=")).as("Actual: " + cookieValues.get(1)).isTrue(); @@ -174,7 +176,7 @@ public void printResponse() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.set("header", "headerValue"); headers.setContentType(MediaType.TEXT_PLAIN); - headers.setLocation(new URI("/redirectFoo")); + headers.setLocation(URI.create("/redirectFoo")); headers.put("Set-Cookie", cookieValues); String heading = "MockHttpServletResponse"; @@ -188,7 +190,7 @@ public void printResponse() throws Exception { Map> printedValues = this.handler.getPrinter().printedValues; String[] cookies = (String[]) printedValues.get(heading).get("Cookies"); - assertThat(cookies.length).isEqualTo(2); + assertThat(cookies).hasSize(2); String cookie1 = cookies[0]; String cookie2 = cookies[1]; assertThat(cookie1.startsWith("[" + Cookie.class.getSimpleName())).isTrue(); @@ -202,9 +204,9 @@ public void printResponse() throws Exception { } @Test - public void printRequestWithCharacterEncoding() throws Exception { + void printRequestWithCharacterEncoding() throws Exception { this.request.setCharacterEncoding("UTF-8"); - this.request.setContent("text".getBytes("UTF-8")); + this.request.setContent("text".getBytes(UTF_8)); this.handler.handle(this.mvcResult); @@ -212,14 +214,14 @@ public void printRequestWithCharacterEncoding() throws Exception { } @Test - public void printRequestWithoutCharacterEncoding() throws Exception { + void printRequestWithoutCharacterEncoding() throws Exception { this.handler.handle(this.mvcResult); assertValue("MockHttpServletRequest", "Body", ""); } @Test - public void printResponseWithCharacterEncoding() throws Exception { + void printResponseWithCharacterEncoding() throws Exception { this.response.setCharacterEncoding("UTF-8"); this.response.getWriter().print("text"); @@ -228,7 +230,7 @@ public void printResponseWithCharacterEncoding() throws Exception { } @Test - public void printResponseWithDefaultCharacterEncoding() throws Exception { + void printResponseWithDefaultCharacterEncoding() throws Exception { this.response.getWriter().print("text"); this.handler.handle(this.mvcResult); @@ -237,7 +239,7 @@ public void printResponseWithDefaultCharacterEncoding() throws Exception { } @Test - public void printHandlerNull() throws Exception { + void printHandlerNull() throws Exception { StubMvcResult mvcResult = new StubMvcResult(this.request, null, null, null, null, null, this.response); this.handler.handle(mvcResult); @@ -245,7 +247,7 @@ public void printHandlerNull() throws Exception { } @Test - public void printHandler() throws Exception { + void printHandler() throws Exception { this.mvcResult.setHandler(new Object()); this.handler.handle(this.mvcResult); @@ -253,7 +255,7 @@ public void printHandler() throws Exception { } @Test - public void printHandlerMethod() throws Exception { + void printHandlerMethod() throws Exception { HandlerMethod handlerMethod = new HandlerMethod(this, "handle"); this.mvcResult.setHandler(handlerMethod); this.handler.handle(mvcResult); @@ -263,14 +265,14 @@ public void printHandlerMethod() throws Exception { } @Test - public void resolvedExceptionNull() throws Exception { + void resolvedExceptionNull() throws Exception { this.handler.handle(this.mvcResult); assertValue("Resolved Exception", "Type", null); } @Test - public void resolvedException() throws Exception { + void resolvedException() throws Exception { this.mvcResult.setResolvedException(new Exception()); this.handler.handle(this.mvcResult); @@ -278,7 +280,7 @@ public void resolvedException() throws Exception { } @Test - public void modelAndViewNull() throws Exception { + void modelAndViewNull() throws Exception { this.handler.handle(this.mvcResult); assertValue("ModelAndView", "View name", null); @@ -287,7 +289,7 @@ public void modelAndViewNull() throws Exception { } @Test - public void modelAndView() throws Exception { + void modelAndView() throws Exception { BindException bindException = new BindException(new Object(), "target"); bindException.reject("errorCode"); @@ -306,14 +308,14 @@ public void modelAndView() throws Exception { } @Test - public void flashMapNull() throws Exception { + void flashMapNull() throws Exception { this.handler.handle(mvcResult); assertValue("FlashMap", "Type", null); } @Test - public void flashMap() throws Exception { + void flashMap() throws Exception { FlashMap flashMap = new FlashMap(); flashMap.put("attrName", "attrValue"); this.request.setAttribute(DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP", flashMap); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java index c3a82c1502f8..3787da9a22cd 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -85,23 +85,12 @@ public void statusRanges() throws Exception { response.setStatus(status.value()); MvcResult mvcResult = new StubMvcResult(request, null, null, null, null, null, response); switch (status.series().value()) { - case 1: - this.matchers.is1xxInformational().match(mvcResult); - break; - case 2: - this.matchers.is2xxSuccessful().match(mvcResult); - break; - case 3: - this.matchers.is3xxRedirection().match(mvcResult); - break; - case 4: - this.matchers.is4xxClientError().match(mvcResult); - break; - case 5: - this.matchers.is5xxServerError().match(mvcResult); - break; - default: - fail("Unexpected range for status code value " + status); + case 1 -> this.matchers.is1xxInformational().match(mvcResult); + case 2 -> this.matchers.is2xxSuccessful().match(mvcResult); + case 3 -> this.matchers.is3xxRedirection().match(mvcResult); + case 4 -> this.matchers.is4xxClientError().match(mvcResult); + case 5 -> this.matchers.is5xxServerError().match(mvcResult); + default -> fail("Unexpected range for status code value " + status); } } } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/ExceptionHandlerTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/ExceptionHandlerTests.java index 75c8041464b1..06b30d0ab24d 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/ExceptionHandlerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/ExceptionHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -173,14 +173,11 @@ private static class RestPersonController { @GetMapping("/person/{name}") Person get(@PathVariable String name) { - switch (name) { - case "Luke": - throw new IllegalArgumentException(); - case "Leia": - throw new IllegalStateException(); - default: - return new Person("Yoda"); - } + return switch (name) { + case "Luke" -> throw new IllegalArgumentException(); + case "Leia" -> throw new IllegalStateException(); + default -> new Person("Yoda"); + }; } @ExceptionHandler diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/ContentAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/ContentAssertionTests.java index fd921973bbea..36eb11149b0f 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/ContentAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/ContentAssertionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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,8 +16,6 @@ package org.springframework.test.web.servlet.samples.client.standalone.resultmatches; -import java.nio.charset.StandardCharsets; - import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; @@ -27,8 +25,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.springframework.http.MediaType.TEXT_PLAIN; /** * {@link MockMvcWebTestClient} equivalent of the MockMvc @@ -36,20 +37,20 @@ * * @author Rossen Stoyanchev */ -public class ContentAssertionTests { +class ContentAssertionTests { private final WebTestClient testClient = MockMvcWebTestClient.bindToController(new SimpleController()).build(); @Test - public void testContentType() { - testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN) + void contentType() { + testClient.get().uri("/handle").accept(TEXT_PLAIN) .exchange() .expectStatus().isOk() .expectHeader().contentType(MediaType.valueOf("text/plain;charset=ISO-8859-1")) .expectHeader().contentType("text/plain;charset=ISO-8859-1") .expectHeader().contentTypeCompatibleWith("text/plain") - .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_PLAIN); + .expectHeader().contentTypeCompatibleWith(TEXT_PLAIN); testClient.get().uri("/handleUtf8") .exchange() @@ -57,24 +58,23 @@ public void testContentType() { .expectHeader().contentType(MediaType.valueOf("text/plain;charset=UTF-8")) .expectHeader().contentType("text/plain;charset=UTF-8") .expectHeader().contentTypeCompatibleWith("text/plain") - .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_PLAIN); + .expectHeader().contentTypeCompatibleWith(TEXT_PLAIN); } @Test - public void testContentAsString() { - - testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN) + void contentAsString() { + testClient.get().uri("/handle").accept(TEXT_PLAIN) .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello world!"); - testClient.get().uri("/handleUtf8").accept(MediaType.TEXT_PLAIN) + testClient.get().uri("/handleUtf8").accept(TEXT_PLAIN) .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01"); // Hamcrest matchers... - testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN) + testClient.get().uri("/handle").accept(TEXT_PLAIN) .exchange() .expectStatus().isOk() .expectBody(String.class).value(equalTo("Hello world!")); @@ -85,33 +85,31 @@ public void testContentAsString() { } @Test - public void testContentAsBytes() { - - testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN) + void contentAsBytes() { + testClient.get().uri("/handle").accept(TEXT_PLAIN) .exchange() .expectStatus().isOk() .expectBody(byte[].class).isEqualTo( - "Hello world!".getBytes(StandardCharsets.ISO_8859_1)); + "Hello world!".getBytes(ISO_8859_1)); testClient.get().uri("/handleUtf8") .exchange() .expectStatus().isOk() .expectBody(byte[].class).isEqualTo( - "\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes(StandardCharsets.UTF_8)); + "\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes(UTF_8)); } @Test - public void testContentStringMatcher() { - testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN) + void contentStringMatcher() { + testClient.get().uri("/handle").accept(TEXT_PLAIN) .exchange() .expectStatus().isOk() .expectBody(String.class).value(containsString("world")); } @Test - public void testCharacterEncoding() { - - testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN) + void characterEncoding() { + testClient.get().uri("/handle").accept(TEXT_PLAIN) .exchange() .expectStatus().isOk() .expectHeader().contentType("text/plain;charset=ISO-8859-1") @@ -122,22 +120,22 @@ public void testCharacterEncoding() { .expectStatus().isOk() .expectHeader().contentType("text/plain;charset=UTF-8") .expectBody(byte[].class) - .isEqualTo("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes(StandardCharsets.UTF_8)); + .isEqualTo("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes(UTF_8)); } @Controller private static class SimpleController { - @RequestMapping(value="/handle", produces="text/plain") + @RequestMapping(path="/handle", produces="text/plain") @ResponseBody - public String handle() { + String handle() { return "Hello world!"; } - @RequestMapping(value="/handleUtf8", produces="text/plain;charset=UTF-8") + @RequestMapping(path="/handleUtf8", produces="text/plain;charset=UTF-8") @ResponseBody - public String handleWithCharset() { + String handleWithCharset() { return "\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01"; // "Hello world! (Japanese) } } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/XpathAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/XpathAssertionTests.java index e7f50190afcf..aff406bfdf5b 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/XpathAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/resultmatches/XpathAssertionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -224,11 +224,12 @@ public class BlogFeedController { @RequestMapping(value = "/blog.atom", method = {GET, HEAD}) @ResponseBody public String listPublishedPosts() { - return "\r\n" - + "\r\n" - + " Test Feed\r\n" - + " https://www.example.com/favicon.ico\r\n" - + "\r\n\r\n"; + return """ + + + Test Feed + https://www.example.com/favicon.ico + """.replaceAll("\n", "\r\n"); } } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/JavaConfigTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/JavaConfigTests.java index 48646aa92a3a..67bd61b27719 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/JavaConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/JavaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -141,8 +141,7 @@ private void verifyRootWacSupport() { ApplicationContext parent = wac.getParent(); assertThat(parent).isNotNull(); - boolean condition = parent instanceof WebApplicationContext; - assertThat(condition).isTrue(); + assertThat(parent).isInstanceOf(WebApplicationContext.class); WebApplicationContext root = (WebApplicationContext) parent; ServletContext childServletContext = wac.getServletContext(); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/MockMvcBuilderMethodChainTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/MockMvcBuilderMethodChainTests.java index 6d4fc912c796..c30dc7eeafc8 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/MockMvcBuilderMethodChainTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/MockMvcBuilderMethodChainTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -17,19 +17,15 @@ package org.springframework.test.web.servlet.samples.spr; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; /** @@ -37,26 +33,23 @@ * * @author Wesley Hall */ -@ExtendWith(SpringExtension.class) -@WebAppConfiguration -@ContextConfiguration -public class MockMvcBuilderMethodChainTests { - - @Autowired - private WebApplicationContext wac; +@SpringJUnitWebConfig +class MockMvcBuilderMethodChainTests { @Test - public void chainMultiple() { - MockMvcBuilders + void chainMultiple(WebApplicationContext wac) { + assertThatNoException().isThrownBy(() -> + MockMvcBuilders .webAppContextSetup(wac) .addFilter(new CharacterEncodingFilter() ) .defaultRequest(get("/").contextPath("/mywebapp")) - .build(); + .build() + ); } @Configuration @EnableWebMvc - static class WebConfig implements WebMvcConfigurer { + static class WebConfig { } } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ExceptionHandlerTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ExceptionHandlerTests.java index d6b026bed5cb..059a8138cc3b 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ExceptionHandlerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ExceptionHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -152,14 +152,11 @@ private static class RestPersonController { @GetMapping("/person/{name}") Person get(@PathVariable String name) { - switch (name) { - case "Luke": - throw new IllegalArgumentException(); - case "Leia": - throw new IllegalStateException(); - default: - return new Person("Yoda"); - } + return switch (name) { + case "Luke" -> throw new IllegalArgumentException(); + case "Leia" -> throw new IllegalStateException(); + default -> new Person("Yoda"); + }; } @ExceptionHandler diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/MultipartControllerTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/MultipartControllerTests.java index 2d57e1ab6494..4a9f6c678f1e 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/MultipartControllerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/MultipartControllerTests.java @@ -70,18 +70,12 @@ void multipartRequestWithSingleFileOrPart(String url) throws Exception { byte[] json = "{\"name\":\"yeeeah\"}".getBytes(StandardCharsets.UTF_8); MockMultipartFile jsonPart = new MockMultipartFile("json", "json", "application/json", json); - MockMultipartHttpServletRequestBuilder requestBuilder; - switch (url) { - case "/multipartfile": - requestBuilder = multipart(url).file(new MockMultipartFile("file", "orig", null, fileContent)); - break; - case "/multipartfile-via-put": - requestBuilder = multipart(HttpMethod.PUT, url).file(new MockMultipartFile("file", "orig", null, fileContent)); - break; - default: - requestBuilder = multipart(url).part(new MockPart("part", "orig", fileContent)); - break; - } + MockMultipartHttpServletRequestBuilder requestBuilder = switch (url) { + case "/multipartfile" -> multipart(url).file(new MockMultipartFile("file", "orig", null, fileContent)); + case "/multipartfile-via-put" -> + multipart(HttpMethod.PUT, url).file(new MockMultipartFile("file", "orig", null, fileContent)); + default -> multipart(url).part(new MockPart("part", "orig", fileContent)); + }; standaloneSetup(new MultipartController()).build() .perform(requestBuilder.file(jsonPart)) diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resulthandlers/PrintingResultHandlerSmokeTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resulthandlers/PrintingResultHandlerSmokeTests.java index 87986b2a0145..6c768fe016ce 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resulthandlers/PrintingResultHandlerSmokeTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resulthandlers/PrintingResultHandlerSmokeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -68,7 +68,7 @@ public void testPrint() throws Exception { System.out.println(); System.out.println("==============================================================="); - System.out.println(writer.toString()); + System.out.println(writer); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java index 2efc51e4afb5..98bc13599ca0 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -79,10 +79,10 @@ void contentAsString() throws Exception { @Test void contentAsBytes() throws Exception { this.mockMvc.perform(get("/handle").accept(MediaType.TEXT_PLAIN)) - .andExpect(content().bytes("Hello world!".getBytes("ISO-8859-1"))); + .andExpect(content().bytes("Hello world!".getBytes(StandardCharsets.ISO_8859_1))); this.mockMvc.perform(get("/handleUtf8")) - .andExpect(content().bytes("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes("UTF-8"))); + .andExpect(content().bytes("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes(StandardCharsets.UTF_8))); } @Test @@ -103,11 +103,11 @@ void characterEncoding() throws Exception { this.mockMvc.perform(get("/handleUtf8")) .andExpect(content().encoding("UTF-8")) - .andExpect(content().bytes("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes("UTF-8"))); + .andExpect(content().bytes("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes(StandardCharsets.UTF_8))); this.mockMvc.perform(get("/handleUtf8")) .andExpect(content().encoding(StandardCharsets.UTF_8)) - .andExpect(content().bytes("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes("UTF-8"))); + .andExpect(content().bytes("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes(StandardCharsets.UTF_8))); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java index 3bece04be76d..7ea60cbb964f 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -155,13 +155,9 @@ public void testNodeCount() throws Exception { .andExpect(xpath("/ns:people/performers/performer", musicNamespace).nodeCount(equalTo(2))); } - // SPR-10704 - @Test + @Test // SPR-10704 public void testFeedWithLinefeedChars() throws Exception { - -// Map namespace = Collections.singletonMap("ns", ""); - standaloneSetup(new BlogFeedController()).build() .perform(get("/blog.atom").accept(MediaType.APPLICATION_ATOM_XML)) .andExpect(status().isOk()) @@ -228,11 +224,12 @@ public class BlogFeedController { @RequestMapping(value="/blog.atom", method = { GET, HEAD }) @ResponseBody public String listPublishedPosts() { - return "\r\n" - + "\r\n" - + " Test Feed\r\n" - + " https://www.example.com/favicon.ico\r\n" - + "\r\n\r\n"; + return """ + + + Test Feed + https://www.example.com/favicon.ico + """.replaceAll("\n", "\r\n"); } } diff --git a/spring-test/src/test/kotlin/org/springframework/test/web/servlet/MockMvcExtensionsTests.kt b/spring-test/src/test/kotlin/org/springframework/test/web/servlet/MockMvcExtensionsTests.kt index 72d750e41341..d1e731f3bc27 100644 --- a/spring-test/src/test/kotlin/org/springframework/test/web/servlet/MockMvcExtensionsTests.kt +++ b/spring-test/src/test/kotlin/org/springframework/test/web/servlet/MockMvcExtensionsTests.kt @@ -16,21 +16,27 @@ package org.springframework.test.web.servlet -import org.assertj.core.api.Assertions.* +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.hamcrest.CoreMatchers -import org.hamcrest.Matcher -import org.hamcrest.Matchers import org.junit.jupiter.api.Test import org.springframework.http.HttpMethod import org.springframework.http.HttpStatus -import org.springframework.http.MediaType.* +import org.springframework.http.MediaType.APPLICATION_ATOM_XML +import org.springframework.http.MediaType.APPLICATION_JSON +import org.springframework.http.MediaType.APPLICATION_XML import org.springframework.test.web.Person import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController import org.springframework.web.servlet.ModelAndView import reactor.core.publisher.Mono import java.security.Principal -import java.util.* +import java.util.Locale /** * [MockMvc] DSL tests that verifies builder, actions and expect blocks. diff --git a/spring-tx/src/main/java/org/springframework/dao/CannotAcquireLockException.java b/spring-tx/src/main/java/org/springframework/dao/CannotAcquireLockException.java index d5b113e9f5e2..5a36c2381545 100644 --- a/spring-tx/src/main/java/org/springframework/dao/CannotAcquireLockException.java +++ b/spring-tx/src/main/java/org/springframework/dao/CannotAcquireLockException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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,9 @@ * Exception thrown on failure to acquire a lock during an update, * for example during a "select for update" statement. * + *

    Consider handling the general {@link PessimisticLockingFailureException} + * instead, semantically including a wider range of locking-related failures. + * * @author Rod Johnson */ @SuppressWarnings("serial") diff --git a/spring-tx/src/main/java/org/springframework/dao/CannotSerializeTransactionException.java b/spring-tx/src/main/java/org/springframework/dao/CannotSerializeTransactionException.java index 1dfe7f541674..6968981c05a6 100644 --- a/spring-tx/src/main/java/org/springframework/dao/CannotSerializeTransactionException.java +++ b/spring-tx/src/main/java/org/springframework/dao/CannotSerializeTransactionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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,8 +20,14 @@ * Exception thrown on failure to complete a transaction in serialized mode * due to update conflicts. * + *

    Consider handling the general {@link PessimisticLockingFailureException} + * instead, semantically including a wider range of locking-related failures. + * * @author Rod Johnson + * @deprecated as of 6.0.3, in favor of + * {@link PessimisticLockingFailureException}/{@link CannotAcquireLockException} */ +@Deprecated(since = "6.0.3") @SuppressWarnings("serial") public class CannotSerializeTransactionException extends PessimisticLockingFailureException { diff --git a/spring-tx/src/main/java/org/springframework/dao/CleanupFailureDataAccessException.java b/spring-tx/src/main/java/org/springframework/dao/CleanupFailureDataAccessException.java index 32dfd248f479..525a771f53ee 100644 --- a/spring-tx/src/main/java/org/springframework/dao/CleanupFailureDataAccessException.java +++ b/spring-tx/src/main/java/org/springframework/dao/CleanupFailureDataAccessException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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. @@ -28,7 +28,9 @@ * to keep the original data access exception, if any. * * @author Rod Johnson + * @deprecated as of 6.0.3 since it is not in use within core JDBC/ORM support */ +@Deprecated(since = "6.0.3") @SuppressWarnings("serial") public class CleanupFailureDataAccessException extends NonTransientDataAccessException { diff --git a/spring-tx/src/main/java/org/springframework/dao/ConcurrencyFailureException.java b/spring-tx/src/main/java/org/springframework/dao/ConcurrencyFailureException.java index cc990803f483..4f1031ce3f14 100644 --- a/spring-tx/src/main/java/org/springframework/dao/ConcurrencyFailureException.java +++ b/spring-tx/src/main/java/org/springframework/dao/ConcurrencyFailureException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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,17 +19,15 @@ import org.springframework.lang.Nullable; /** - * Exception thrown on concurrency failure. + * Exception thrown on various data access concurrency failures. * - *

    This exception should be subclassed to indicate the type of failure: - * optimistic locking, failure to acquire lock, etc. + *

    This exception provides subclasses for specific types of failure, + * in particular optimistic locking versus pessimistic locking. * * @author Thomas Risberg * @since 1.1 * @see OptimisticLockingFailureException * @see PessimisticLockingFailureException - * @see CannotAcquireLockException - * @see DeadlockLoserDataAccessException */ @SuppressWarnings("serial") public class ConcurrencyFailureException extends TransientDataAccessException { diff --git a/spring-tx/src/main/java/org/springframework/dao/DataIntegrityViolationException.java b/spring-tx/src/main/java/org/springframework/dao/DataIntegrityViolationException.java index b1347e1ee946..0e561e04e238 100644 --- a/spring-tx/src/main/java/org/springframework/dao/DataIntegrityViolationException.java +++ b/spring-tx/src/main/java/org/springframework/dao/DataIntegrityViolationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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,8 +19,13 @@ /** * Exception thrown when an attempt to insert or update data * results in violation of an integrity constraint. Note that this - * is not purely a relational concept; unique primary keys are - * required by most database types. + * is not purely a relational concept; integrity constraints such + * as unique primary keys are required by most database types. + * + *

    Serves as a superclass for more specific exceptions, e.g. + * {@link DuplicateKeyException}. However, it is generally + * recommended to handle {@code DataIntegrityViolationException} + * itself instead of relying on specific exception subclasses. * * @author Rod Johnson */ diff --git a/spring-tx/src/main/java/org/springframework/dao/DeadlockLoserDataAccessException.java b/spring-tx/src/main/java/org/springframework/dao/DeadlockLoserDataAccessException.java index 3c946cd56408..0f41fa3505da 100644 --- a/spring-tx/src/main/java/org/springframework/dao/DeadlockLoserDataAccessException.java +++ b/spring-tx/src/main/java/org/springframework/dao/DeadlockLoserDataAccessException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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,8 +20,14 @@ * Generic exception thrown when the current process was * a deadlock loser, and its transaction rolled back. * + *

    Consider handling the general {@link PessimisticLockingFailureException} + * instead, semantically including a wider range of locking-related failures. + * * @author Rod Johnson + * @deprecated as of 6.0.3, in favor of + * {@link PessimisticLockingFailureException}/{@link CannotAcquireLockException} */ +@Deprecated(since = "6.0.3") @SuppressWarnings("serial") public class DeadlockLoserDataAccessException extends PessimisticLockingFailureException { diff --git a/spring-tx/src/main/java/org/springframework/dao/DuplicateKeyException.java b/spring-tx/src/main/java/org/springframework/dao/DuplicateKeyException.java index b406391a3ff3..36ee084c9e7b 100644 --- a/spring-tx/src/main/java/org/springframework/dao/DuplicateKeyException.java +++ b/spring-tx/src/main/java/org/springframework/dao/DuplicateKeyException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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,9 @@ * Note that this is not necessarily a purely relational concept; * unique primary keys are required by most database types. * + *

    Consider handling the general {@link DataIntegrityViolationException} + * instead, semantically including a wider range of constraint violations. + * * @author Thomas Risberg */ @SuppressWarnings("serial") diff --git a/spring-tx/src/main/java/org/springframework/dao/PessimisticLockingFailureException.java b/spring-tx/src/main/java/org/springframework/dao/PessimisticLockingFailureException.java index 3e60eb141bca..31599203a997 100644 --- a/spring-tx/src/main/java/org/springframework/dao/PessimisticLockingFailureException.java +++ b/spring-tx/src/main/java/org/springframework/dao/PessimisticLockingFailureException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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. @@ -21,13 +21,13 @@ * Thrown by Spring's SQLException translation mechanism * if a corresponding database error is encountered. * - *

    Serves as superclass for more specific exceptions, like - * CannotAcquireLockException and DeadlockLoserDataAccessException. + *

    Serves as a superclass for more specific exceptions, e.g. + * {@link CannotAcquireLockException}. However, it is generally + * recommended to handle {@code PessimisticLockingFailureException} + * itself instead of relying on specific exception subclasses. * * @author Thomas Risberg * @since 1.2 - * @see CannotAcquireLockException - * @see DeadlockLoserDataAccessException * @see OptimisticLockingFailureException */ @SuppressWarnings("serial") diff --git a/spring-tx/src/main/java/org/springframework/transaction/TransactionDefinition.java b/spring-tx/src/main/java/org/springframework/transaction/TransactionDefinition.java index 1aa8901f3f7e..bd373201a608 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/TransactionDefinition.java +++ b/spring-tx/src/main/java/org/springframework/transaction/TransactionDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,13 +24,13 @@ * *

    Note that isolation level and timeout settings will not get applied unless * an actual new transaction gets started. As only {@link #PROPAGATION_REQUIRED}, - * {@link #PROPAGATION_REQUIRES_NEW} and {@link #PROPAGATION_NESTED} can cause + * {@link #PROPAGATION_REQUIRES_NEW}, and {@link #PROPAGATION_NESTED} can cause * that, it usually doesn't make sense to specify those settings in other cases. * Furthermore, be aware that not all transaction managers will support those * advanced features and thus might throw corresponding exceptions when given * non-default values. * - *

    The {@link #isReadOnly() read-only flag} applies to any transaction context, + *

    The {@linkplain #isReadOnly() read-only flag} applies to any transaction context, * whether backed by an actual resource transaction or operating non-transactionally * at the resource level. In the latter case, the flag will only apply to managed * resources within the application, such as a Hibernate {@code Session}. @@ -46,7 +46,7 @@ public interface TransactionDefinition { /** * Support a current transaction; create a new one if none exists. * Analogous to the EJB transaction attribute of the same name. - *

    This is typically the default setting of a transaction definition, + *

    This is typically the default setting of a transaction definition * and typically defines a transaction synchronization scope. */ int PROPAGATION_REQUIRED = 0; @@ -60,8 +60,8 @@ public interface TransactionDefinition { * As a consequence, the same resources (a JDBC {@code Connection}, a * Hibernate {@code Session}, etc) will be shared for the entire specified * scope. Note that the exact behavior depends on the actual synchronization - * configuration of the transaction manager! - *

    In general, use {@code PROPAGATION_SUPPORTS} with care! In particular, do + * configuration of the transaction manager. + *

    In general, use {@code PROPAGATION_SUPPORTS} with care. In particular, do * not rely on {@code PROPAGATION_REQUIRED} or {@code PROPAGATION_REQUIRES_NEW} * within a {@code PROPAGATION_SUPPORTS} scope (which may lead to * synchronization conflicts at runtime). If such nesting is unavoidable, make sure @@ -87,7 +87,7 @@ public interface TransactionDefinition { * on all transaction managers. This in particular applies to * {@link org.springframework.transaction.jta.JtaTransactionManager}, * which requires the {@code jakarta.transaction.TransactionManager} to be - * made available it to it (which is server-specific in standard Jakarta EE). + * made available to it (which is server-specific in standard Jakarta EE). *

    A {@code PROPAGATION_REQUIRES_NEW} scope always defines its own * transaction synchronizations. Existing synchronizations will be suspended * and resumed appropriately. @@ -102,7 +102,7 @@ public interface TransactionDefinition { * on all transaction managers. This in particular applies to * {@link org.springframework.transaction.jta.JtaTransactionManager}, * which requires the {@code jakarta.transaction.TransactionManager} to be - * made available it to it (which is server-specific in standard Jakarta EE). + * made available to it (which is server-specific in standard Jakarta EE). *

    Note that transaction synchronization is not available within a * {@code PROPAGATION_NOT_SUPPORTED} scope. Existing synchronizations * will be suspended and resumed appropriately. @@ -120,7 +120,7 @@ public interface TransactionDefinition { /** * Execute within a nested transaction if a current transaction exists, - * behave like {@link #PROPAGATION_REQUIRED} otherwise. There is no + * behaving like {@link #PROPAGATION_REQUIRED} otherwise. There is no * analogous feature in EJB. *

    NOTE: Actual creation of a nested transaction will only work on * specific transaction managers. Out of the box, this only applies to the JDBC @@ -134,13 +134,13 @@ public interface TransactionDefinition { /** * Use the default isolation level of the underlying datastore. - * All other levels correspond to the JDBC isolation levels. + *

    All other levels correspond to the JDBC isolation levels. * @see java.sql.Connection */ int ISOLATION_DEFAULT = -1; /** - * Indicates that dirty reads, non-repeatable reads and phantom reads + * Indicates that dirty reads, non-repeatable reads, and phantom reads * can occur. *

    This level allows a row changed by one transaction to be read by another * transaction before any changes in that row have been committed (a "dirty read"). @@ -153,8 +153,8 @@ public interface TransactionDefinition { /** * Indicates that dirty reads are prevented; non-repeatable reads and * phantom reads can occur. - *

    This level only prohibits a transaction from reading a row - * with uncommitted changes in it. + *

    This level only prohibits a transaction from reading a row with uncommitted + * changes in it. * @see java.sql.Connection#TRANSACTION_READ_COMMITTED */ int ISOLATION_READ_COMMITTED = 2; // same as java.sql.Connection.TRANSACTION_READ_COMMITTED; @@ -171,7 +171,7 @@ public interface TransactionDefinition { int ISOLATION_REPEATABLE_READ = 4; // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ; /** - * Indicates that dirty reads, non-repeatable reads and phantom reads + * Indicates that dirty reads, non-repeatable reads, and phantom reads * are prevented. *

    This level includes the prohibitions in {@link #ISOLATION_REPEATABLE_READ} * and further prohibits the situation where one transaction reads all rows that diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/Isolation.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/Isolation.java index 6bc6e2125361..5982d8bfabc3 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/Isolation.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/Isolation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,8 +19,8 @@ import org.springframework.transaction.TransactionDefinition; /** - * Enumeration that represents transaction isolation levels for use - * with the {@link Transactional} annotation, corresponding to the + * Enumeration that represents transaction isolation levels for use with the + * {@link Transactional @Transactional} annotation, corresponding to the * {@link TransactionDefinition} interface. * * @author Colin Sampaleanu @@ -30,15 +30,16 @@ public enum Isolation { /** - * Use the default isolation level of the underlying datastore. - * All other levels correspond to the JDBC isolation levels. + * Use the default isolation level of the underlying data store. + *

    All other levels correspond to the JDBC isolation levels. * @see java.sql.Connection */ DEFAULT(TransactionDefinition.ISOLATION_DEFAULT), /** - * A constant indicating that dirty reads, non-repeatable reads and phantom reads - * can occur. This level allows a row changed by one transaction to be read by + * A constant indicating that dirty reads, non-repeatable reads, and phantom reads + * can occur. + *

    This level allows a row changed by one transaction to be read by * another transaction before any changes in that row have been committed * (a "dirty read"). If any of the changes are rolled back, the second * transaction will have retrieved an invalid row. @@ -48,31 +49,33 @@ public enum Isolation { /** * A constant indicating that dirty reads are prevented; non-repeatable reads - * and phantom reads can occur. This level only prohibits a transaction - * from reading a row with uncommitted changes in it. + * and phantom reads can occur. + *

    This level only prohibits a transaction from reading a row with uncommitted + * changes in it. * @see java.sql.Connection#TRANSACTION_READ_COMMITTED */ READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), /** * A constant indicating that dirty reads and non-repeatable reads are - * prevented; phantom reads can occur. This level prohibits a transaction - * from reading a row with uncommitted changes in it, and it also prohibits - * the situation where one transaction reads a row, a second transaction - * alters the row, and the first transaction rereads the row, getting - * different values the second time (a "non-repeatable read"). + * prevented; phantom reads can occur. + *

    This level prohibits a transaction from reading a row with uncommitted changes + * in it, and it also prohibits the situation where one transaction reads a row, + * a second transaction alters the row, and the first transaction re-reads the row, + * getting different values the second time (a "non-repeatable read"). * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ */ REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), /** - * A constant indicating that dirty reads, non-repeatable reads and phantom - * reads are prevented. This level includes the prohibitions in - * {@code ISOLATION_REPEATABLE_READ} and further prohibits the situation - * where one transaction reads all rows that satisfy a {@code WHERE} - * condition, a second transaction inserts a row that satisfies that - * {@code WHERE} condition, and the first transaction rereads for the - * same condition, retrieving the additional "phantom" row in the second read. + * A constant indicating that dirty reads, non-repeatable reads, and phantom + * reads are prevented. + *

    This level includes the prohibitions in {@link #ISOLATION_REPEATABLE_READ} + * and further prohibits the situation where one transaction reads all rows that + * satisfy a {@code WHERE} condition, a second transaction inserts a row + * that satisfies that {@code WHERE} condition, and the first transaction + * re-reads for the same condition, retrieving the additional "phantom" row + * in the second read. * @see java.sql.Connection#TRANSACTION_SERIALIZABLE */ SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurer.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurer.java index bf709e52401c..c6c4e37ff41d 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurer.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -51,7 +51,7 @@ public interface TransactionManagementConfigurer { * Return the default transaction manager bean to use for annotation-driven database * transaction management, i.e. when processing {@code @Transactional} methods. *

    There are two basic approaches to implementing this method: - *

    1. Implement the method and annotate it with {@code @Bean}

    + *

    1. Implement the method and annotate it with {@code @Bean}

    * In this case, the implementing {@code @Configuration} class implements this method, * marks it with {@code @Bean}, and configures and returns the transaction manager * directly within the method body: @@ -61,8 +61,8 @@ public interface TransactionManagementConfigurer { * public PlatformTransactionManager annotationDrivenTransactionManager() { * return new DataSourceTransactionManager(dataSource()); * } - *

    2. Implement the method without {@code @Bean} and delegate to another existing - * {@code @Bean} method

    + *

    2. Implement the method without {@code @Bean} and delegate to another existing + * {@code @Bean} method

    *
     	 * @Bean
     	 * public PlatformTransactionManager txManager() {
    diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourceEditor.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourceEditor.java
    index eef4a8cc9fff..f1d8c1d33324 100644
    --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourceEditor.java
    +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourceEditor.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2012 the original author or authors.
    + * Copyright 2002-2022 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.
    diff --git a/spring-tx/src/main/java/org/springframework/transaction/reactive/TransactionalOperatorImpl.java b/spring-tx/src/main/java/org/springframework/transaction/reactive/TransactionalOperatorImpl.java
    index ece11c5ec1f6..9a1c8a6b4be5 100644
    --- a/spring-tx/src/main/java/org/springframework/transaction/reactive/TransactionalOperatorImpl.java
    +++ b/spring-tx/src/main/java/org/springframework/transaction/reactive/TransactionalOperatorImpl.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2019 the original author or authors.
    + * Copyright 2002-2022 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,7 +57,7 @@ final class TransactionalOperatorImpl implements TransactionalOperator {
     	 */
     	TransactionalOperatorImpl(ReactiveTransactionManager transactionManager, TransactionDefinition transactionDefinition) {
     		Assert.notNull(transactionManager, "ReactiveTransactionManager must not be null");
    -		Assert.notNull(transactionManager, "TransactionDefinition must not be null");
    +		Assert.notNull(transactionDefinition, "TransactionDefinition must not be null");
     		this.transactionManager = transactionManager;
     		this.transactionDefinition = transactionDefinition;
     	}
    diff --git a/spring-tx/src/test/java/org/springframework/dao/support/DataAccessUtilsTests.java b/spring-tx/src/test/java/org/springframework/dao/support/DataAccessUtilsTests.java
    index 8d5ac6e9412f..cee7165746a1 100644
    --- a/spring-tx/src/test/java/org/springframework/dao/support/DataAccessUtilsTests.java
    +++ b/spring-tx/src/test/java/org/springframework/dao/support/DataAccessUtilsTests.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2020 the original author or authors.
    + * Copyright 2002-2022 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.
    @@ -121,7 +121,7 @@ public void withSameIntegerInstanceTwice() {
     
     	@Test
     	public void withEquivalentIntegerInstanceTwice() {
    -		Collection col = Arrays.asList(Integer.valueOf(555), Integer.valueOf(555));
    +		Collection col = Arrays.asList(555, 555);
     
     		assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class)
     			.isThrownBy(() -> DataAccessUtils.uniqueResult(col))
    diff --git a/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionNamespaceHandlerTests.java b/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionNamespaceHandlerTests.java
    index 3f4814bd9e24..a8ff355dfc64 100644
    --- a/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionNamespaceHandlerTests.java
    +++ b/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionNamespaceHandlerTests.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2019 the original author or authors.
    + * Copyright 2002-2022 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.
    @@ -107,7 +107,7 @@ public void mBeanExportAlsoWorks() throws Exception {
     	public void transactionalEventListenerRegisteredProperly() {
     		assertThat(this.context.containsBean(TransactionManagementConfigUtils
     				.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME)).isTrue();
    -		assertThat(this.context.getBeansOfType(TransactionalEventListenerFactory.class).size()).isEqualTo(1);
    +		assertThat(this.context.getBeansOfType(TransactionalEventListenerFactory.class)).hasSize(1);
     	}
     
     	private TransactionalTestBean getTestBean() {
    diff --git a/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java b/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java
    index dfb3dae19532..8f33660d36ff 100644
    --- a/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java
    +++ b/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2020 the original author or authors.
    + * Copyright 2002-2022 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.
    @@ -216,7 +216,7 @@ public void proxyTypeAspectJCausesRegistrationOfAnnotationTransactionAspect() {
     	public void transactionalEventListenerRegisteredProperly() {
     		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(EnableTxConfig.class);
     		assertThat(ctx.containsBean(TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME)).isTrue();
    -		assertThat(ctx.getBeansOfType(TransactionalEventListenerFactory.class).size()).isEqualTo(1);
    +		assertThat(ctx.getBeansOfType(TransactionalEventListenerFactory.class)).hasSize(1);
     		ctx.close();
     	}
     
    diff --git a/spring-web/spring-web.gradle b/spring-web/spring-web.gradle
    index e0652e525339..4f04cfc6b4fa 100644
    --- a/spring-web/spring-web.gradle
    +++ b/spring-web/spring-web.gradle
    @@ -23,11 +23,11 @@ dependencies {
     	optional("io.netty:netty-handler")
     	optional("io.netty:netty-codec-http")
     	optional("io.netty:netty-transport")
    -	optional("io.projectreactor.netty:reactor-netty-http")
     	optional("io.netty:netty5-buffer")
     	optional("io.netty:netty5-handler")
     	optional("io.netty:netty5-codec-http")
     	optional("io.netty:netty5-transport")
    +	optional("io.projectreactor.netty:reactor-netty-http")
     	optional("io.projectreactor.netty:reactor-netty5-http")
     	optional("io.undertow:undertow-core")
     	optional("org.apache.tomcat.embed:tomcat-embed-core")
    @@ -37,6 +37,12 @@ dependencies {
     	optional("org.eclipse.jetty:jetty-servlet") {
     		exclude group: "jakarta.servlet", module: "jakarta.servlet-api"
     	}
    +	/* Jetty 12: see org.springframework.http.server.reactive.JettyHttpHandlerAdapter
    +	optional("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.0.alpha2") {
    +		exclude group: "jakarta.servlet", module: "jakarta.servlet-api"
    +		exclude group: "org.eclipse.jetty", module: "jetty-session"
    +	}
    +	*/
     	optional("org.eclipse.jetty:jetty-reactive-httpclient")
     	optional('org.apache.httpcomponents.client5:httpclient5')
     	optional('org.apache.httpcomponents.core5:httpcore5-reactive')
    diff --git a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java
    index ea97e92e6c22..843286b1a8f9 100644
    --- a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java
    +++ b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java
    @@ -236,9 +236,9 @@ public int hashCode() {
     		result = 31 * result + ObjectUtils.nullSafeHashCode(this.filename);
     		result = 31 * result + ObjectUtils.nullSafeHashCode(this.charset);
     		result = 31 * result + ObjectUtils.nullSafeHashCode(this.size);
    -		result = 31 * result + (this.creationDate != null ? this.creationDate.hashCode() : 0);
    -		result = 31 * result + (this.modificationDate != null ? this.modificationDate.hashCode() : 0);
    -		result = 31 * result + (this.readDate != null ? this.readDate.hashCode() : 0);
    +		result = 31 * result + ObjectUtils.nullSafeHashCode(this.creationDate);
    +		result = 31 * result + ObjectUtils.nullSafeHashCode(this.modificationDate);
    +		result = 31 * result + ObjectUtils.nullSafeHashCode(this.readDate);
     		return result;
     	}
     
    @@ -363,7 +363,7 @@ else if (attribute.equals("filename*") ) {
     					if (idx1 != -1 && idx2 != -1) {
     						charset = Charset.forName(value.substring(0, idx1).trim());
     						Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset),
    -								"Charset should be UTF-8 or ISO-8859-1");
    +								"Charset must be UTF-8 or ISO-8859-1");
     						filename = decodeFilename(value.substring(idx2 + 1), charset);
     					}
     					else {
    @@ -492,8 +492,9 @@ else if (!escaped && ch == '"') {
     	 * @see RFC 5987
     	 */
     	private static String decodeFilename(String filename, Charset charset) {
    -		Assert.notNull(filename, "'input' String should not be null");
    -		Assert.notNull(charset, "'charset' should not be null");
    +		Assert.notNull(filename, "'filename' must not be null");
    +		Assert.notNull(charset, "'charset' must not be null");
    +
     		byte[] value = filename.getBytes(charset);
     		ByteArrayOutputStream baos = new ByteArrayOutputStream();
     		int index = 0;
    @@ -534,8 +535,8 @@ private static boolean isRFC5987AttrChar(byte c) {
     	 * @see RFC 2047
     	 */
     	private static String decodeQuotedPrintableFilename(String filename, Charset charset) {
    -		Assert.notNull(filename, "'input' String should not be null");
    -		Assert.notNull(charset, "'charset' should not be null");
    +		Assert.notNull(filename, "'filename' must not be null");
    +		Assert.notNull(charset, "'charset' must not be null");
     
     		byte[] value = filename.getBytes(US_ASCII);
     		ByteArrayOutputStream baos = new ByteArrayOutputStream();
    @@ -603,10 +604,11 @@ private static String decodeQuotedPairs(String filename) {
     	 * @see RFC 5987
     	 */
     	private static String encodeFilename(String input, Charset charset) {
    -		Assert.notNull(input, "'input' is required");
    -		Assert.notNull(charset, "'charset' is required");
    +		Assert.notNull(input, "'input' must not be null");
    +		Assert.notNull(charset, "'charset' must not be null");
     		Assert.isTrue(!StandardCharsets.US_ASCII.equals(charset), "ASCII does not require encoding");
     		Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset), "Only UTF-8 and ISO-8859-1 are supported");
    +
     		byte[] source = input.getBytes(charset);
     		int len = source.length;
     		StringBuilder sb = new StringBuilder(len << 1);
    diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
    index 4c05c9e18017..83116cae22fe 100644
    --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
    +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2002-2021 the original author or authors.
    + * Copyright 2002-2022 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.
    @@ -990,7 +990,8 @@ public void setContentType(@Nullable MediaType mediaType) {
     	/**
     	 * Return the {@linkplain MediaType media type} of the body, as specified
     	 * by the {@code Content-Type} header.
    -	 * 

    Returns {@code null} when the content-type is unknown. + *

    Returns {@code null} when the {@code Content-Type} header is not set. + * @throws InvalidMediaTypeException if the media type value cannot be parsed */ @Nullable public MediaType getContentType() { diff --git a/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java b/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java index ce048d12a6d6..380ba2aa57cb 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java +++ b/spring-web/src/main/java/org/springframework/http/HttpStatusCode.java @@ -85,7 +85,8 @@ public sealed interface HttpStatusCode extends Serializable permits DefaultHttpS * positive number */ static HttpStatusCode valueOf(int code) { - Assert.isTrue(code >= 100 && code <= 999, () -> "Code '" + code + "' should be a three-digit positive integer"); + Assert.isTrue(code >= 100 && code <= 999, + () -> "Status code '" + code + "' should be a three-digit positive integer"); HttpStatus status = HttpStatus.resolve(code); if (status != null) { return status; 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 7ae2229f1df6..149805b87916 100644 --- a/spring-web/src/main/java/org/springframework/http/MediaType.java +++ b/spring-web/src/main/java/org/springframework/http/MediaType.java @@ -98,16 +98,34 @@ public class MediaType extends MimeType implements Serializable { /** * Public constant media type for {@code application/graphql+json}. * @since 5.3.19 - * @see GraphQL over HTTP spec + * @see GraphQL over HTTP spec change + * @deprecated as of 6.0.3, in favor of {@link MediaType#APPLICATION_GRAPHQL_RESPONSE} */ + @Deprecated(since = "6.0.3", forRemoval = true) public static final MediaType APPLICATION_GRAPHQL; /** * A String equivalent of {@link MediaType#APPLICATION_GRAPHQL}. * @since 5.3.19 + * @deprecated as of 6.0.3, in favor of {@link MediaType#APPLICATION_GRAPHQL_RESPONSE_VALUE} */ + @Deprecated(since = "6.0.3", forRemoval = true) public static final String APPLICATION_GRAPHQL_VALUE = "application/graphql+json"; + /** + * Public constant media type for {@code application/graphql-response+json}. + * @since 6.0.3 + * @see GraphQL over HTTP spec + */ + public static final MediaType APPLICATION_GRAPHQL_RESPONSE; + + /** + * A String equivalent of {@link MediaType#APPLICATION_GRAPHQL_RESPONSE}. + * @since 6.0.3 + */ + public static final String APPLICATION_GRAPHQL_RESPONSE_VALUE = "application/graphql-response+json"; + + /** * Public constant media type for {@code application/json}. */ @@ -422,6 +440,7 @@ public class MediaType extends MimeType implements Serializable { APPLICATION_CBOR = new MediaType("application", "cbor"); APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded"); APPLICATION_GRAPHQL = new MediaType("application", "graphql+json"); + APPLICATION_GRAPHQL_RESPONSE = new MediaType("application", "graphql-response+json"); APPLICATION_JSON = new MediaType("application", "json"); APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8); APPLICATION_NDJSON = new MediaType("application", "x-ndjson"); @@ -547,7 +566,7 @@ protected void checkParameters(String parameter, String value) { String unquotedValue = unquote(value); double d = Double.parseDouble(unquotedValue); Assert.isTrue(d >= 0D && d <= 1D, - () -> "Invalid quality value \"" + value + "\": should be between 0.0 and 1.0"); + () -> "Invalid quality value \"" + unquotedValue + "\": should be between 0.0 and 1.0"); } } @@ -836,7 +855,7 @@ public static String toString(Collection mediaTypes) { * @param mediaTypes the list of media types to be sorted * @deprecated As of 6.0, in favor of {@link MimeTypeUtils#sortBySpecificity(List)} */ - @Deprecated(since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) public static void sortBySpecificity(List mediaTypes) { Assert.notNull(mediaTypes, "'mediaTypes' must not be null"); if (mediaTypes.size() > 1) { @@ -865,7 +884,7 @@ public static void sortBySpecificity(List mediaTypes) { * @see #getQualityValue() * @deprecated As of 6.0, with no direct replacement */ - @Deprecated(since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) public static void sortByQualityValue(List mediaTypes) { Assert.notNull(mediaTypes, "'mediaTypes' must not be null"); if (mediaTypes.size() > 1) { @@ -891,7 +910,7 @@ public static void sortBySpecificityAndQuality(List mediaTypes) { * Comparator used by {@link #sortByQualityValue(List)}. * @deprecated As of 6.0, with no direct replacement */ - @Deprecated(since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) public static final Comparator QUALITY_VALUE_COMPARATOR = (mediaType1, mediaType2) -> { double quality1 = mediaType1.getQualityValue(); double quality2 = mediaType2.getQualityValue(); @@ -931,7 +950,8 @@ else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) { // audio/b * Comparator used by {@link #sortBySpecificity(List)}. * @deprecated As of 6.0, with no direct replacement */ - @Deprecated(since = "6.0") + @Deprecated(since = "6.0", forRemoval = true) + @SuppressWarnings("removal") public static final Comparator SPECIFICITY_COMPARATOR = new SpecificityComparator<>() { @Override diff --git a/spring-web/src/main/java/org/springframework/http/ProblemDetail.java b/spring-web/src/main/java/org/springframework/http/ProblemDetail.java index 3e45dfb10e0a..e6e6ea96474a 100644 --- a/spring-web/src/main/java/org/springframework/http/ProblemDetail.java +++ b/spring-web/src/main/java/org/springframework/http/ProblemDetail.java @@ -22,6 +22,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; /** * Representation for an RFC 7807 problem detail. Includes spec-defined @@ -40,8 +41,8 @@ * {@link org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler}. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 6.0 - * * @see RFC 7807 * @see org.springframework.web.ErrorResponse * @see org.springframework.web.ErrorResponseException @@ -108,6 +109,13 @@ public void setType(URI type) { this.type = type; } + /** + * Return the configured {@link #setType(URI) problem type}. + */ + public URI getType() { + return this.type; + } + /** * Setter for the {@link #getTitle() problem title}. *

    By default, if not explicitly set and the status is well-known, this @@ -118,6 +126,20 @@ public void setTitle(@Nullable String title) { this.title = title; } + /** + * Return the configured {@link #setTitle(String) problem title}. + */ + @Nullable + public String getTitle() { + if (this.title == null) { + HttpStatus httpStatus = HttpStatus.resolve(this.status); + if (httpStatus != null) { + return httpStatus.getReasonPhrase(); + } + } + return this.title; + } + /** * Setter for the {@link #getStatus() problem status}. * @param httpStatus the problem status @@ -134,6 +156,14 @@ public void setStatus(int status) { this.status = status; } + /** + * Return the status associated with the problem, provided either to the + * constructor or configured via {@link #setStatus(int)}. + */ + public int getStatus() { + return this.status; + } + /** * Setter for the {@link #getDetail() problem detail}. *

    By default, this is not set. @@ -143,6 +173,14 @@ public void setDetail(@Nullable String detail) { this.detail = detail; } + /** + * Return the configured {@link #setDetail(String) problem detail}. + */ + @Nullable + public String getDetail() { + return this.detail; + } + /** * Setter for the {@link #getInstance() problem instance}. *

    By default, when {@code ProblemDetail} is returned from an @@ -153,6 +191,14 @@ public void setInstance(@Nullable URI instance) { this.instance = instance; } + /** + * Return the configured {@link #setInstance(URI) problem instance}. + */ + @Nullable + public URI getInstance() { + return this.instance; + } + /** * Set a "dynamic" property to be added to a generic {@link #getProperties() * properties map}. @@ -168,52 +214,6 @@ public void setProperty(String name, Object value) { this.properties.put(name, value); } - - /** - * Return the configured {@link #setType(URI) problem type}. - */ - public URI getType() { - return this.type; - } - - /** - * Return the configured {@link #setTitle(String) problem title}. - */ - @Nullable - public String getTitle() { - if (this.title == null) { - HttpStatus httpStatus = HttpStatus.resolve(this.status); - if (httpStatus != null) { - return httpStatus.getReasonPhrase(); - } - } - return this.title; - } - - /** - * Return the status associated with the problem, provided either to the - * constructor or configured via {@link #setStatus(int)}. - */ - public int getStatus() { - return this.status; - } - - /** - * Return the configured {@link #setDetail(String) problem detail}. - */ - @Nullable - public String getDetail() { - return this.detail; - } - - /** - * Return the configured {@link #setInstance(URI) problem instance}. - */ - @Nullable - public URI getInstance() { - return this.instance; - } - /** * Return a generic map of properties that are not known ahead of time, * possibly {@code null} if no properties have been added. To add a property, @@ -229,6 +229,33 @@ public Map getProperties() { } + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ProblemDetail otherDetail)) { + return false; + } + return (this.type.equals(otherDetail.type) && + ObjectUtils.nullSafeEquals(this.title, otherDetail.title) && + this.status == otherDetail.status && + ObjectUtils.nullSafeEquals(this.detail, otherDetail.detail) && + ObjectUtils.nullSafeEquals(this.instance, otherDetail.instance) && + ObjectUtils.nullSafeEquals(this.properties, otherDetail.properties)); + } + + @Override + public int hashCode() { + int result = this.type.hashCode(); + result = 31 * result + ObjectUtils.nullSafeHashCode(this.title); + result = 31 * result + this.status; + result = 31 * result + ObjectUtils.nullSafeHashCode(this.detail); + result = 31 * result + ObjectUtils.nullSafeHashCode(this.instance); + result = 31 * result + ObjectUtils.nullSafeHashCode(this.properties); + return result; + } + @Override public String toString() { return getClass().getSimpleName() + "[" + initToStringContent() + "]"; @@ -239,7 +266,7 @@ public String toString() { * Subclasses can override this to append additional fields. */ protected String initToStringContent() { - return "type='" + this.type + "'" + + return "type='" + getType() + "'" + ", title='" + getTitle() + "'" + ", status=" + getStatus() + ", detail='" + getDetail() + "'" + diff --git a/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java b/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java index 2d95a9db16c8..6cd7fc4fdc76 100644 --- a/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/ResponseEntity.java b/spring-web/src/main/java/org/springframework/http/ResponseEntity.java index dd3899adfc68..38321c8da520 100644 --- a/spring-web/src/main/java/org/springframework/http/ResponseEntity.java +++ b/spring-web/src/main/java/org/springframework/http/ResponseEntity.java @@ -263,13 +263,13 @@ public static ResponseEntity of(Optional body) { } /** - * Create a builder for a {@code ResponseEntity} with the given - * {@link ProblemDetail} as the body, and its - * {@link ProblemDetail#getStatus() status} as the status. - *

    Note that {@code ProblemDetail} is supported as a return value from - * controller methods and from {@code @ExceptionHandler} methods. The method - * here is convenient to also add response headers. - * @param body the details for an HTTP error response + * Create a new {@link HeadersBuilder} with its status set to + * {@link ProblemDetail#getStatus()} and its body is set to + * {@link ProblemDetail}. + *

    Note: If there are no headers to add, there is usually + * no need to create a {@link ResponseEntity} since {@code ProblemDetail} + * is also supported as a return value from controller methods. + * @param body the problem detail to use * @return the created builder * @since 6.0 */ diff --git a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java index ee52dc429370..64903b154c2a 100644 --- a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequest.java index 17dc755a0bc3..12c4958838fb 100644 --- a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequest.java @@ -82,6 +82,7 @@ HttpContext getHttpContext() { } + @SuppressWarnings("deprecation") // execute(ClassicHttpRequest, HttpContext) @Override protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException { addHeaders(this.httpRequest, headers); diff --git a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java index 00d068231dc2..f1bc8145d5b1 100644 --- a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -149,8 +149,7 @@ public void setConnectionRequestTimeout(int connectionRequestTimeout) { /** * As of version 6.0, setting this property has no effect. - * - *

    To change the socket read timeout, use {@link SocketConfig.Builder#setSoTimeout(Timeout)}, + *

    To change the socket read timeout, use {@link SocketConfig.Builder#setSoTimeout(Timeout)}, * supply the resulting {@link SocketConfig} to * {@link org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder#setDefaultSocketConfig(SocketConfig)}, * use the resulting connection manager for @@ -202,8 +201,8 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) { // Use request configuration given by the user, when available RequestConfig config = null; - if (httpRequest instanceof Configurable) { - config = ((Configurable) httpRequest).getConfig(); + if (httpRequest instanceof Configurable configurable) { + config = configurable.getConfig(); } if (config == null) { config = createRequestConfig(client); @@ -249,6 +248,7 @@ protected RequestConfig createRequestConfig(Object client) { * @return the merged request config * @since 4.2 */ + @SuppressWarnings("deprecation") // setConnectTimeout protected RequestConfig mergeRequestConfig(RequestConfig clientConfig) { if (this.connectTimeout == -1 && this.connectionRequestTimeout == -1) { // nothing to merge return clientConfig; @@ -328,8 +328,8 @@ protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) { @Override public void destroy() throws Exception { HttpClient httpClient = getHttpClient(); - if (httpClient instanceof Closeable) { - ((Closeable) httpClient).close(); + if (httpClient instanceof Closeable closeable) { + closeable.close(); } } diff --git a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsStreamingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsStreamingClientHttpRequest.java index 708303eb66a7..9c12d187b4b1 100644 --- a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsStreamingClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsStreamingClientHttpRequest.java @@ -94,6 +94,7 @@ protected OutputStream getBodyInternal(HttpHeaders headers) { throw new UnsupportedOperationException("getBody not supported"); } + @SuppressWarnings("deprecation") // execute(ClassicHttpRequest, HttpContext) @Override protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException { HttpComponentsClientHttpRequest.addHeaders(this.httpRequest, headers); diff --git a/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java index 37d0dce1bd75..dd570cc5a24c 100644 --- a/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java index 2d7e19a73afa..55602f8b8aa5 100644 --- a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java index 5d4c37640476..ec5e8b927bcb 100644 --- a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java index 01c3d36874b2..d11663138b46 100644 --- a/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/SimpleBufferingClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java index 7411639e537c..d7f9097c4c33 100644 --- a/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/SimpleStreamingClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java b/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java index 71d6e5300090..f3e26c60bd6a 100644 --- a/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java +++ b/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java @@ -24,11 +24,10 @@ import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.observation.ClientHttpObservationDocumentation.HighCardinalityKeyNames; +import org.springframework.http.client.observation.ClientHttpObservationDocumentation.LowCardinalityKeyNames; import org.springframework.util.StringUtils; -import static org.springframework.http.client.observation.ClientHttpObservationDocumentation.HighCardinalityKeyNames; -import static org.springframework.http.client.observation.ClientHttpObservationDocumentation.LowCardinalityKeyNames; - /** * Default implementation for a {@link ClientRequestObservationConvention}, * extracting information from the {@link ClientRequestObservationContext}. diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponseDecorator.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponseDecorator.java index 1c9e6214c3a3..47ac73b91084 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponseDecorator.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponseDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpConnector.java b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpConnector.java index 612da6ec3480..8cea9d4cdca8 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpConnector.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpConnector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -158,11 +158,7 @@ public void completed(Message> result) { @Override public void failed(Exception ex) { - Throwable t = ex; - if (t instanceof HttpStreamResetException) { - HttpStreamResetException httpStreamResetException = (HttpStreamResetException) ex; - t = httpStreamResetException.getCause(); - } + Throwable t = (ex instanceof HttpStreamResetException hsre ? hsre.getCause() : ex); this.sink.error(t); } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java index c4bce7b21e31..45cd63bd7907 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpResponse.java index 38c724594259..e0872ffd3c94 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java index 11dfcb30fcf3..675267a1b348 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyHeadersAdapter.java index b0d2a8424adf..e874cac13da4 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyHeadersAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyHeadersAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -18,7 +18,6 @@ import java.util.AbstractSet; import java.util.Collection; -import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -186,7 +185,6 @@ public Set>> entrySet() { public Iterator>> iterator() { return new EntryIterator(); } - @Override public int size() { return headers.size(); @@ -203,16 +201,16 @@ public String toString() { private class EntryIterator implements Iterator>> { - private final Enumeration names = headers.getFieldNames(); + private final Iterator names = headers.getFieldNamesCollection().iterator(); @Override public boolean hasNext() { - return this.names.hasMoreElements(); + return this.names.hasNext(); } @Override public Entry> next() { - return new HeaderEntry(this.names.nextElement()); + return new HeaderEntry(this.names.next()); } } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyResourceFactory.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyResourceFactory.java index 415cdfcdbb7d..e0564dac820c 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyResourceFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyResourceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -131,16 +131,16 @@ public void afterPropertiesSet() throws Exception { } if (this.byteBufferPool == null) { this.byteBufferPool = new MappedByteBufferPool(2048, - this.executor instanceof ThreadPool.SizedThreadPool - ? ((ThreadPool.SizedThreadPool) this.executor).getMaxThreads() / 2 - : ProcessorUtils.availableProcessors() * 2); + this.executor instanceof ThreadPool.SizedThreadPool sizedThreadPool ? + sizedThreadPool.getMaxThreads() / 2 : + ProcessorUtils.availableProcessors() * 2); } if (this.scheduler == null) { this.scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false); } - if (this.executor instanceof LifeCycle) { - ((LifeCycle)this.executor).start(); + if (this.executor instanceof LifeCycle lifeCycle) { + lifeCycle.start(); } this.scheduler.start(); } @@ -148,8 +148,8 @@ public void afterPropertiesSet() throws Exception { @Override public void destroy() throws Exception { try { - if (this.executor instanceof LifeCycle) { - ((LifeCycle)this.executor).stop(); + if (this.executor instanceof LifeCycle lifeCycle) { + lifeCycle.stop(); } } catch (Throwable ex) { diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/NettyHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/client/reactive/NettyHeadersAdapter.java index 76073dfb0fae..3f1661153ab0 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/NettyHeadersAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/NettyHeadersAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java b/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java index a38f7a65e1ee..dad91a6b1b0c 100644 --- a/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java b/spring-web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java index a8977cce81ad..86dfcf9b9939 100644 --- a/spring-web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java +++ b/spring-web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -54,6 +54,10 @@ public abstract class InterceptingHttpAccessor extends HttpAccessor { * Set the request interceptors that this accessor should use. *

    The interceptors will get immediately sorted according to their * {@linkplain AnnotationAwareOrderComparator#sort(List) order}. + *

    Note: This method does not support concurrent changes, + * and in most cases should not be called after initialization on startup. + * See also related note on {@link org.springframework.web.client.RestTemplate} + * regarding concurrent configuration changes. * @see #getRequestFactory() * @see AnnotationAwareOrderComparator */ diff --git a/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java index 070b0610ee1f..e2760e2182d9 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -17,7 +17,6 @@ package org.springframework.http.codec; import org.springframework.core.codec.Decoder; -import org.springframework.core.codec.Encoder; /** * Extension of {@link CodecConfigurer} for HTTP message reader and writer @@ -83,45 +82,16 @@ static ClientCodecConfigurer create() { */ interface ClientDefaultCodecs extends DefaultCodecs { - /** - * Configure encoders or writers for use with - * {@link org.springframework.http.codec.multipart.MultipartHttpMessageWriter - * MultipartHttpMessageWriter}. - */ - MultipartCodecs multipartCodecs(); - /** * Configure the {@code Decoder} to use for Server-Sent Events. - *

    By default if this is not set, and Jackson is available, the - * {@link #jackson2JsonDecoder} override is used instead. Use this property - * if you want to further customize the SSE decoder. - *

    Note that {@link #maxInMemorySize(int)}, if configured, will be - * applied to the given decoder. + *

    By default if this is not set, and Jackson is available, + * the {@link #jackson2JsonDecoder} override is used instead. + * Use this method to customize the SSE decoder. + *

    Note that {@link #maxInMemorySize(int)}, if configured, + * will be applied to the given decoder. * @param decoder the decoder to use */ void serverSentEventDecoder(Decoder decoder); } - - /** - * Registry and container for multipart HTTP message writers. - */ - interface MultipartCodecs { - - /** - * Add a Part {@code Encoder}, internally wrapped with - * {@link EncoderHttpMessageWriter}. - * @param encoder the encoder to add - */ - MultipartCodecs encoder(Encoder encoder); - - /** - * Add a Part {@link HttpMessageWriter}. For writers of type - * {@link EncoderHttpMessageWriter} consider using the shortcut - * {@link #encoder(Encoder)} instead. - * @param writer the writer to add - */ - MultipartCodecs writer(HttpMessageWriter writer); - } - } diff --git a/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java index a27b0709a3aa..a5906a49b697 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java @@ -258,6 +258,24 @@ interface DefaultCodecs { * @since 5.1 */ void enableLoggingRequestDetails(boolean enable); + + /** + * Configure encoders or writers for use with + * {@link org.springframework.http.codec.multipart.MultipartHttpMessageWriter + * MultipartHttpMessageWriter}. + * @since 6.0.3 + */ + MultipartCodecs multipartCodecs(); + + /** + * Configure the {@code HttpMessageReader} to use for multipart requests. + *

    Note that {@link #maxInMemorySize(int)} and/or + * {@link #enableLoggingRequestDetails(boolean)}, if configured, will be + * applied to the given reader, if applicable. + * @param reader the message reader to use for multipart requests. + * @since 6.0.3 + */ + void multipartReader(HttpMessageReader reader); } @@ -389,4 +407,27 @@ interface DefaultCodecConfig { Boolean isEnableLoggingRequestDetails(); } + + /** + * Registry and container for multipart HTTP message writers. + * @since 6.0.3 + */ + interface MultipartCodecs { + + /** + * Add a Part {@code Encoder}, internally wrapped with + * {@link EncoderHttpMessageWriter}. + * @param encoder the encoder to add + */ + MultipartCodecs encoder(Encoder encoder); + + /** + * Add a Part {@link HttpMessageWriter}. For writers of type + * {@link EncoderHttpMessageWriter} consider using the shortcut + * {@link #encoder(Encoder)} instead. + * @param writer the writer to add + */ + MultipartCodecs writer(HttpMessageWriter writer); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java index d3500ad6cc52..978e7f04c071 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -67,10 +67,10 @@ public DecoderHttpMessageReader(Decoder decoder) { } private static void initLogger(Decoder decoder) { - if (decoder instanceof AbstractDecoder && + if (decoder instanceof AbstractDecoder abstractDecoder && decoder.getClass().getName().startsWith("org.springframework.core.codec")) { - Log logger = HttpLogging.forLog(((AbstractDecoder) decoder).getLogger()); - ((AbstractDecoder) decoder).setLogger(logger); + Log logger = HttpLogging.forLog(abstractDecoder.getLogger()); + abstractDecoder.setLogger(logger); } } @@ -163,9 +163,8 @@ public Mono readMono(ResolvableType actualType, ResolvableType elementType, protected Map getReadHints(ResolvableType actualType, ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response) { - if (this.decoder instanceof HttpMessageDecoder) { - HttpMessageDecoder decoder = (HttpMessageDecoder) this.decoder; - return decoder.getDecodeHints(actualType, elementType, request, response); + if (this.decoder instanceof HttpMessageDecoder httpMessageDecoder) { + return httpMessageDecoder.getDecodeHints(actualType, elementType, request, response); } return Hints.none(); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index 9c213a29167d..239ddf0a69f9 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -79,10 +79,10 @@ public EncoderHttpMessageWriter(Encoder encoder) { } private static void initLogger(Encoder encoder) { - if (encoder instanceof AbstractEncoder && + if (encoder instanceof AbstractEncoder abstractEncoder && encoder.getClass().getName().startsWith("org.springframework.core.codec")) { - Log logger = HttpLogging.forLog(((AbstractEncoder) encoder).getLogger()); - ((AbstractEncoder) encoder).setLogger(logger); + Log logger = HttpLogging.forLog(abstractEncoder.getLogger()); + abstractEncoder.setLogger(logger); } } @@ -180,10 +180,10 @@ private static MediaType addDefaultCharset(MediaType main, @Nullable MediaType d } private boolean isStreamingMediaType(@Nullable MediaType mediaType) { - if (mediaType == null || !(this.encoder instanceof HttpMessageEncoder)) { + if (mediaType == null || !(this.encoder instanceof HttpMessageEncoder httpMessageEncoder)) { return false; } - for (MediaType streamingMediaType : ((HttpMessageEncoder) this.encoder).getStreamingMediaTypes()) { + for (MediaType streamingMediaType : httpMessageEncoder.getStreamingMediaTypes()) { if (mediaType.isCompatibleWith(streamingMediaType) && matchParameters(mediaType, streamingMediaType)) { return true; } @@ -224,9 +224,8 @@ public Mono write(Publisher inputStream, ResolvableType actua protected Map getWriteHints(ResolvableType streamType, ResolvableType elementType, @Nullable MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) { - if (this.encoder instanceof HttpMessageEncoder) { - HttpMessageEncoder encoder = (HttpMessageEncoder) this.encoder; - return encoder.getEncodeHints(streamType, elementType, mediaType, request, response); + if (this.encoder instanceof HttpMessageEncoder httpMessageEncoder) { + return httpMessageEncoder.getEncodeHints(streamType, elementType, mediaType, request, response); } return Hints.none(); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java index a1ca47d5ab74..12ec8b8f35af 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -179,7 +179,7 @@ private static long lengthOf(Resource resource) { private static Optional> zeroCopy(Resource resource, @Nullable ResourceRegion region, ReactiveHttpOutputMessage message, Map hints) { - if (message instanceof ZeroCopyHttpOutputMessage && resource.isFile()) { + if (message instanceof ZeroCopyHttpOutputMessage zeroCopyHttpOutputMessage && resource.isFile()) { try { File file = resource.getFile(); long pos = region != null ? region.getPosition() : 0; @@ -188,7 +188,7 @@ private static Optional> zeroCopy(Resource resource, @Nullable Resour String formatted = region != null ? "region " + pos + "-" + (count) + " of " : ""; logger.debug(Hints.getLogPrefix(hints) + "Zero-copy " + formatted + "[" + resource + "]"); } - return Optional.of(((ZeroCopyHttpOutputMessage) message).writeWith(file, pos, count)); + return Optional.of(zeroCopyHttpOutputMessage.writeWith(file, pos, count)); } catch (IOException ex) { // should not happen diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java index c6f9222a19c0..864ea6dc1121 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerCodecConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -78,25 +78,15 @@ static ServerCodecConfigurer create() { /** - * {@link CodecConfigurer.DefaultCodecs} extension with extra client-side options. + * {@link CodecConfigurer.DefaultCodecs} extension with extra server-side options. */ interface ServerDefaultCodecs extends DefaultCodecs { - /** - * Configure the {@code HttpMessageReader} to use for multipart requests. - *

    Note that {@link #maxInMemorySize(int)} and/or - * {@link #enableLoggingRequestDetails(boolean)}, if configured, will be - * applied to the given reader, if applicable. - * @param reader the message reader to use for multipart requests. - * @since 5.1.11 - */ - void multipartReader(HttpMessageReader reader); - /** * Configure the {@code Encoder} to use for Server-Sent Events. - *

    By default if this is not set, and Jackson is available, the - * {@link #jackson2JsonEncoder} override is used instead. Use this method - * to customize the SSE encoder. + *

    By default if this is not set, and Jackson is available, + * the {@link #jackson2JsonEncoder} override is used instead. + * Use this method to customize the SSE encoder. */ void serverSentEventEncoder(Encoder encoder); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java index 50ee18643416..e23937943a94 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java @@ -121,8 +121,8 @@ private Flux> encode(Publisher input, ResolvableType el return Flux.from(input).map(element -> { - ServerSentEvent sse = (element instanceof ServerSentEvent ? - (ServerSentEvent) element : ServerSentEvent.builder().data(element).build()); + ServerSentEvent sse = (element instanceof ServerSentEvent serverSentEvent ? + serverSentEvent : ServerSentEvent.builder().data(element).build()); StringBuilder sb = new StringBuilder(); String id = sse.id(); @@ -150,9 +150,9 @@ private Flux> encode(Publisher input, ResolvableType el if (data == null) { result = Flux.just(encodeText(sb + "\n", mediaType, factory)); } - else if (data instanceof String) { - data = StringUtils.replace((String) data, "\n", "\ndata:"); - result = Flux.just(encodeText(sb + (String) data + "\n\n", mediaType, factory)); + else if (data instanceof String text) { + text = StringUtils.replace(text, "\n", "\ndata:"); + result = Flux.just(encodeText(sb + text + "\n\n", mediaType, factory)); } else { result = encodeEvent(sb, data, dataType, mediaType, factory, hints); @@ -203,9 +203,8 @@ public Mono write(Publisher input, ResolvableType actualType, Resolvabl private Map getEncodeHints(ResolvableType actualType, ResolvableType elementType, @Nullable MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) { - if (this.encoder instanceof HttpMessageEncoder) { - HttpMessageEncoder encoder = (HttpMessageEncoder) this.encoder; - return encoder.getEncodeHints(actualType, elementType, mediaType, request, response); + if (this.encoder instanceof HttpMessageEncoder httpMessageEncoder) { + return httpMessageEncoder.getEncodeHints(actualType, elementType, mediaType, request, response); } return Hints.none(); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java index 287dd8ce687d..dc320b50cb84 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java @@ -52,9 +52,7 @@ import org.springframework.util.MimeType; /** - * Abstract base class for Jackson 2.9 decoding, leveraging non-blocking parsing. - * - *

    Compatible with Jackson 2.9.7 and higher. + * Abstract base class for Jackson 2.x decoding, leveraging non-blocking parsing. * * @author Sebastien Deleuze * @author Rossen Stoyanchev @@ -266,12 +264,12 @@ private void logValue(@Nullable Object value, @Nullable Map hint } private CodecException processException(IOException ex) { - if (ex instanceof InvalidDefinitionException) { - JavaType type = ((InvalidDefinitionException) ex).getType(); + if (ex instanceof InvalidDefinitionException ide) { + JavaType type = ide.getType(); return new CodecException("Type definition error: " + type, ex); } - if (ex instanceof JsonProcessingException) { - String originalMessage = ((JsonProcessingException) ex).getOriginalMessage(); + if (ex instanceof JsonProcessingException jpe) { + String originalMessage = jpe.getOriginalMessage(); return new DecodingException("JSON decoding error: " + originalMessage, ex); } return new DecodingException("I/O error while parsing input stream", ex); diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java index 4819610bd380..15b16f521e62 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java @@ -59,7 +59,7 @@ import org.springframework.util.MimeType; /** - * Base class providing support methods for Jackson 2.9 encoding. For non-streaming use + * Base class providing support methods for Jackson 2.x encoding. For non-streaming use * cases, {@link Flux} elements are collected into a {@link List} before serialization for * performance reasons. * diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java index 26e87e371a4e..7292c4ca7fde 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java @@ -48,7 +48,7 @@ import org.springframework.util.ObjectUtils; /** - * Base class providing support methods for Jackson 2.9 encoding and decoding. + * Base class providing support methods for Jackson 2.x encoding and decoding. * * @author Sebastien Deleuze * @author Rossen Stoyanchev @@ -255,7 +255,7 @@ protected Map getHints(ResolvableType resolvableType) { @Nullable protected MethodParameter getParameter(ResolvableType type) { - return (type.getSource() instanceof MethodParameter ? (MethodParameter) type.getSource() : null); + return (type.getSource() instanceof MethodParameter methodParameter ? methodParameter : null); } @Nullable diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java index ffc1df149a0b..612449fe69f0 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -35,7 +35,7 @@ import org.springframework.util.MimeTypeUtils; /** - * Decode a byte stream into JSON and convert to Object's with Jackson 2.9, + * Decode a byte stream into JSON and convert to Object's with Jackson 2.x, * leveraging non-blocking parsing. * * @author Sebastien Deleuze diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java index 5ebab3d6bc50..015449d18f59 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -35,7 +35,7 @@ import org.springframework.util.MimeType; /** - * Encode from an {@code Object} stream to a byte stream of JSON objects using Jackson 2.9. + * Encode from an {@code Object} stream to a byte stream of JSON objects using Jackson 2.x. * For non-streaming use cases, {@link Flux} elements are collected into a {@link List} * before serialization for performance reason. * diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2SmileDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2SmileDecoder.java index 4fc6095f2e04..e58a3b0a509d 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2SmileDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2SmileDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,7 +24,7 @@ import org.springframework.util.MimeType; /** - * Decode a byte stream into Smile and convert to Object's with Jackson 2.9, + * Decode a byte stream into Smile and convert to Object's with Jackson 2.x, * leveraging non-blocking parsing. * * @author Sebastien Deleuze diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2SmileEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2SmileEncoder.java index 580b35c0027d..a5dfacef5757 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2SmileEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2SmileEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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,7 +30,7 @@ import org.springframework.util.MimeType; /** - * Encode from an {@code Object} stream to a byte stream of Smile objects using Jackson 2.9. + * Encode from an {@code Object} stream to a byte stream of Smile objects using Jackson 2.x. * For non-streaming use cases, {@link Flux} elements are collected into a {@link List} * before serialization for performance reason. * diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReader.java index a694e43437a9..e1da5224dc2f 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -154,7 +154,7 @@ public void setFileStorageDirectory(Path fileStorageDirectory) throws IOExceptio * @see Schedulers#newBoundedElastic */ public void setBlockingOperationScheduler(Scheduler blockingOperationScheduler) { - Assert.notNull(blockingOperationScheduler, "FileCreationScheduler must not be null"); + Assert.notNull(blockingOperationScheduler, "'blockingOperationScheduler' must not be null"); this.blockingOperationScheduler = blockingOperationScheduler; } diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageReader.java index 79f181a1f76d..74c61f18c955 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -129,7 +129,7 @@ private LinkedMultiValueMap toMultiValueMap(Map toList(Collection collection) { - return collection instanceof List ? (List) collection : new ArrayList<>(collection); + return (collection instanceof List list ? list : new ArrayList<>(collection)); } } diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriter.java index 4eced82403d3..b4fca60dd935 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriter.java @@ -79,7 +79,7 @@ public class MultipartHttpMessageWriter extends MultipartWriterSupport private static final Map DEFAULT_HINTS = Hints.from(Hints.SUPPRESS_LOGGING_HINT, true); - private final List> partWriters; + private final Supplier>> partWritersSupplier; @Nullable private final HttpMessageWriter> formWriter; @@ -112,8 +112,23 @@ public MultipartHttpMessageWriter(List> partWriters) { public MultipartHttpMessageWriter(List> partWriters, @Nullable HttpMessageWriter> formWriter) { + this(() -> partWriters, formWriter); + } + + /** + * Constructor with a supplier for an explicit list of writers for + * serializing parts and a writer for plain form data to fall back when + * no media type is specified and the actual map consists of String + * values only. + * @param partWritersSupplier the supplier for writers for serializing parts + * @param formWriter the fallback writer for form data, {@code null} by default + * @since 6.0.3 + */ + public MultipartHttpMessageWriter(Supplier>> partWritersSupplier, + @Nullable HttpMessageWriter> formWriter) { + super(initMediaTypes(formWriter)); - this.partWriters = partWriters; + this.partWritersSupplier = partWritersSupplier; this.formWriter = formWriter; } @@ -131,7 +146,7 @@ private static List initMediaTypes(@Nullable HttpMessageWriter for * @since 5.0.7 */ public List> getPartWriters() { - return Collections.unmodifiableList(this.partWriters); + return Collections.unmodifiableList(this.partWritersSupplier.get()); } @@ -145,6 +160,20 @@ public HttpMessageWriter> getFormWriter() { } + @Override + public boolean canWrite(ResolvableType elementType, @Nullable MediaType mediaType) { + if (MultiValueMap.class.isAssignableFrom(elementType.toClass())) { + if (mediaType == null) { + return true; + } + for (MediaType supportedMediaType : getWritableMediaTypes()) { + if (supportedMediaType.isCompatibleWith(mediaType)) { + return true; + } + } + } + return false; + } @Override public Mono write(Publisher> inputStream, @@ -250,8 +279,8 @@ else if (resolvableType.resolve() == Resource.class) { MediaType contentType = headers.getContentType(); - final ResolvableType finalBodyType = resolvableType; - Optional> writer = this.partWriters.stream() + ResolvableType finalBodyType = resolvableType; + Optional> writer = this.partWritersSupplier.get().stream() .filter(partWriter -> partWriter.canWrite(finalBodyType, contentType)) .findFirst(); diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartWriterSupport.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartWriterSupport.java index 0bd3bfa23069..9e78ab9a9e9b 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartWriterSupport.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartWriterSupport.java @@ -24,7 +24,6 @@ import reactor.core.publisher.Mono; -import org.springframework.core.ResolvableType; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.HttpHeaders; @@ -34,7 +33,6 @@ import org.springframework.util.Assert; import org.springframework.util.FastByteArrayOutputStream; import org.springframework.util.MimeTypeUtils; -import org.springframework.util.MultiValueMap; /** * Support class for multipart HTTP message writers. @@ -83,21 +81,6 @@ public List getWritableMediaTypes() { return this.supportedMediaTypes; } - - public boolean canWrite(ResolvableType elementType, @Nullable MediaType mediaType) { - if (MultiValueMap.class.isAssignableFrom(elementType.toClass())) { - if (mediaType == null) { - return true; - } - for (MediaType supportedMediaType : this.supportedMediaTypes) { - if (supportedMediaType.isCompatibleWith(mediaType)) { - return true; - } - } - } - return false; - } - /** * Generate a multipart boundary. *

    By default delegates to {@link MimeTypeUtils#generateMultipartBoundary()}. diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/PartHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/PartHttpMessageWriter.java index 3c64a862bfa6..ddf92c14caa3 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/PartHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/PartHttpMessageWriter.java @@ -48,6 +48,21 @@ public PartHttpMessageWriter() { super(MultipartHttpMessageReader.MIME_TYPES); } + @Override + public boolean canWrite(ResolvableType elementType, @Nullable MediaType mediaType) { + if (Part.class.isAssignableFrom(elementType.toClass())) { + if (mediaType == null) { + return true; + } + for (MediaType supportedMediaType : getWritableMediaTypes()) { + if (supportedMediaType.isCompatibleWith(mediaType)) { + return true; + } + } + } + return false; + } + @Override public Mono write(Publisher parts, @@ -81,7 +96,7 @@ private Flux encodePart(byte[] boundary, Part part, DataBufferFa String name = part.name(); if (!headers.containsKey(HttpHeaders.CONTENT_DISPOSITION)) { headers.setContentDispositionFormData(name, - (part instanceof FilePart ? ((FilePart) part).filename() : null)); + (part instanceof FilePart filePart ? filePart.filename() : null)); } return Flux.concat( diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java index 0dcaad99b5e7..3b7b4ddb2e23 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -55,6 +55,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer { Assert.notNull(defaultCodecs, "'defaultCodecs' is required"); this.defaultCodecs = defaultCodecs; this.customCodecs = new DefaultCustomCodecs(); + this.defaultCodecs.setPartWritersSupplier(this::getWriters); } /** @@ -64,6 +65,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer { protected BaseCodecConfigurer(BaseCodecConfigurer other) { this.defaultCodecs = other.cloneDefaultCodecs(); this.customCodecs = new DefaultCustomCodecs(other.customCodecs); + this.defaultCodecs.setPartWritersSupplier(this::getWriters); } /** @@ -196,20 +198,18 @@ public void withDefaultCodecConfig(Consumer codecsConfigCons private void addCodec(Object codec, boolean applyDefaultConfig) { - if (codec instanceof Decoder) { - codec = new DecoderHttpMessageReader<>((Decoder) codec); + if (codec instanceof Decoder decoder) { + codec = new DecoderHttpMessageReader<>(decoder); } - else if (codec instanceof Encoder) { - codec = new EncoderHttpMessageWriter<>((Encoder) codec); + else if (codec instanceof Encoder encoder) { + codec = new EncoderHttpMessageWriter<>(encoder); } - if (codec instanceof HttpMessageReader) { - HttpMessageReader reader = (HttpMessageReader) codec; + if (codec instanceof HttpMessageReader reader) { boolean canReadToObject = reader.canRead(ResolvableType.forClass(Object.class), null); (canReadToObject ? this.objectReaders : this.typedReaders).put(reader, applyDefaultConfig); } - else if (codec instanceof HttpMessageWriter) { - HttpMessageWriter writer = (HttpMessageWriter) codec; + else if (codec instanceof HttpMessageWriter writer) { boolean canWriteObject = writer.canWrite(ResolvableType.forClass(Object.class), null); (canWriteObject ? this.objectWriters : this.typedWriters).put(writer, applyDefaultConfig); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java index 31a6023d2eb1..05c4c0f4a4d1 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; +import java.util.function.Supplier; import org.springframework.core.codec.AbstractDataBufferDecoder; import org.springframework.core.codec.ByteArrayDecoder; @@ -62,6 +63,8 @@ import org.springframework.http.codec.multipart.MultipartHttpMessageReader; import org.springframework.http.codec.multipart.MultipartHttpMessageWriter; import org.springframework.http.codec.multipart.PartEventHttpMessageReader; +import org.springframework.http.codec.multipart.PartEventHttpMessageWriter; +import org.springframework.http.codec.multipart.PartHttpMessageWriter; import org.springframework.http.codec.protobuf.KotlinSerializationProtobufDecoder; import org.springframework.http.codec.protobuf.KotlinSerializationProtobufEncoder; import org.springframework.http.codec.protobuf.ProtobufDecoder; @@ -160,6 +163,15 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure @Nullable private Encoder kotlinSerializationProtobufEncoder; + @Nullable + private DefaultMultipartCodecs multipartCodecs; + + @Nullable + private Supplier>> partWritersSupplier; + + @Nullable + private HttpMessageReader multipartReader; + @Nullable private Consumer codecConsumer; @@ -224,6 +236,9 @@ protected BaseDefaultCodecs(BaseDefaultCodecs other) { this.kotlinSerializationJsonEncoder = other.kotlinSerializationJsonEncoder; this.kotlinSerializationProtobufDecoder = other.kotlinSerializationProtobufDecoder; this.kotlinSerializationProtobufEncoder = other.kotlinSerializationProtobufEncoder; + this.multipartCodecs = other.multipartCodecs != null ? + new DefaultMultipartCodecs(other.multipartCodecs) : null; + this.multipartReader = other.multipartReader; this.codecConsumer = other.codecConsumer; this.maxInMemorySize = other.maxInMemorySize; this.enableLoggingRequestDetails = other.enableLoggingRequestDetails; @@ -351,6 +366,31 @@ public void enableLoggingRequestDetails(boolean enable) { } } + @Override + public CodecConfigurer.MultipartCodecs multipartCodecs() { + if (this.multipartCodecs == null) { + this.multipartCodecs = new DefaultMultipartCodecs(); + } + return this.multipartCodecs; + } + + @Override + public void multipartReader(HttpMessageReader multipartReader) { + this.multipartReader = multipartReader; + initTypedReaders(); + } + + /** + * Set a supplier for part writers to use when + * {@link #multipartCodecs()} are not explicitly configured. + * That's the same set of writers as for general except for the multipart + * writer itself. + */ + void setPartWritersSupplier(Supplier>> supplier) { + this.partWritersSupplier = supplier; + initTypedWriters(); + } + @Override @Nullable public Boolean isEnableLoggingRequestDetails() { @@ -405,6 +445,15 @@ else if (kotlinSerializationProtobufPresent) { (KotlinSerializationProtobufDecoder) this.kotlinSerializationProtobufDecoder : new KotlinSerializationProtobufDecoder())); } addCodec(this.typedReaders, new FormHttpMessageReader()); + if (this.multipartReader != null) { + addCodec(this.typedReaders, this.multipartReader); + } + else { + DefaultPartHttpMessageReader partReader = new DefaultPartHttpMessageReader(); + addCodec(this.typedReaders, partReader); + addCodec(this.typedReaders, new MultipartHttpMessageReader(partReader)); + } + addCodec(this.typedReaders, new PartEventHttpMessageReader()); // client vs server.. extendTypedReaders(this.typedReaders); @@ -424,13 +473,12 @@ protected void addCodec(List codecs, T codec) { * if configured by the application, to the given codec , including any * codec it contains. */ - @SuppressWarnings("rawtypes") private void initCodec(@Nullable Object codec) { - if (codec instanceof DecoderHttpMessageReader) { - codec = ((DecoderHttpMessageReader) codec).getDecoder(); + if (codec instanceof DecoderHttpMessageReader decoderHttpMessageReader) { + codec = decoderHttpMessageReader.getDecoder(); } - else if (codec instanceof EncoderHttpMessageWriter) { - codec = ((EncoderHttpMessageWriter) codec).getEncoder(); + else if (codec instanceof EncoderHttpMessageWriter encoderHttpMessageWriter) { + codec = encoderHttpMessageWriter.getEncoder(); } if (codec == null) { @@ -439,72 +487,74 @@ else if (codec instanceof EncoderHttpMessageWriter) { Integer size = this.maxInMemorySize; if (size != null) { - if (codec instanceof AbstractDataBufferDecoder) { - ((AbstractDataBufferDecoder) codec).setMaxInMemorySize(size); + if (codec instanceof AbstractDataBufferDecoder abstractDataBufferDecoder) { + abstractDataBufferDecoder.setMaxInMemorySize(size); } + // Pattern variables in the following if-blocks cannot be named the same as instance fields + // due to lacking support in Checkstyle: https://github.com/checkstyle/checkstyle/issues/10969 if (protobufPresent) { - if (codec instanceof ProtobufDecoder) { - ((ProtobufDecoder) codec).setMaxMessageSize(size); + if (codec instanceof ProtobufDecoder protobufDec) { + protobufDec.setMaxMessageSize(size); } } if (kotlinSerializationCborPresent) { - if (codec instanceof KotlinSerializationCborDecoder) { - ((KotlinSerializationCborDecoder) codec).setMaxInMemorySize(size); + if (codec instanceof KotlinSerializationCborDecoder kotlinSerializationCborDec) { + kotlinSerializationCborDec.setMaxInMemorySize(size); } } if (kotlinSerializationJsonPresent) { - if (codec instanceof KotlinSerializationJsonDecoder) { - ((KotlinSerializationJsonDecoder) codec).setMaxInMemorySize(size); + if (codec instanceof KotlinSerializationJsonDecoder kotlinSerializationJsonDec) { + kotlinSerializationJsonDec.setMaxInMemorySize(size); } } if (kotlinSerializationProtobufPresent) { - if (codec instanceof KotlinSerializationProtobufDecoder) { - ((KotlinSerializationProtobufDecoder) codec).setMaxInMemorySize(size); + if (codec instanceof KotlinSerializationProtobufDecoder kotlinSerializationProtobufDec) { + kotlinSerializationProtobufDec.setMaxInMemorySize(size); } } if (jackson2Present) { - if (codec instanceof AbstractJackson2Decoder) { - ((AbstractJackson2Decoder) codec).setMaxInMemorySize(size); + if (codec instanceof AbstractJackson2Decoder abstractJackson2Decoder) { + abstractJackson2Decoder.setMaxInMemorySize(size); } } if (jaxb2Present) { - if (codec instanceof Jaxb2XmlDecoder) { - ((Jaxb2XmlDecoder) codec).setMaxInMemorySize(size); + if (codec instanceof Jaxb2XmlDecoder jaxb2XmlDecoder) { + jaxb2XmlDecoder.setMaxInMemorySize(size); } } - if (codec instanceof FormHttpMessageReader) { - ((FormHttpMessageReader) codec).setMaxInMemorySize(size); + if (codec instanceof FormHttpMessageReader formHttpMessageReader) { + formHttpMessageReader.setMaxInMemorySize(size); } - if (codec instanceof ServerSentEventHttpMessageReader) { - ((ServerSentEventHttpMessageReader) codec).setMaxInMemorySize(size); + if (codec instanceof ServerSentEventHttpMessageReader serverSentEventHttpMessageReader) { + serverSentEventHttpMessageReader.setMaxInMemorySize(size); } - if (codec instanceof DefaultPartHttpMessageReader) { - ((DefaultPartHttpMessageReader) codec).setMaxInMemorySize(size); + if (codec instanceof DefaultPartHttpMessageReader defaultPartHttpMessageReader) { + defaultPartHttpMessageReader.setMaxInMemorySize(size); } - if (codec instanceof PartEventHttpMessageReader) { - ((PartEventHttpMessageReader) codec).setMaxInMemorySize(size); + if (codec instanceof PartEventHttpMessageReader partEventHttpMessageReader) { + partEventHttpMessageReader.setMaxInMemorySize(size); } } Boolean enable = this.enableLoggingRequestDetails; if (enable != null) { - if (codec instanceof FormHttpMessageReader) { - ((FormHttpMessageReader) codec).setEnableLoggingRequestDetails(enable); + if (codec instanceof FormHttpMessageReader formHttpMessageReader) { + formHttpMessageReader.setEnableLoggingRequestDetails(enable); } - if (codec instanceof MultipartHttpMessageReader) { - ((MultipartHttpMessageReader) codec).setEnableLoggingRequestDetails(enable); + if (codec instanceof MultipartHttpMessageReader multipartHttpMessageReader) { + multipartHttpMessageReader.setEnableLoggingRequestDetails(enable); } - if (codec instanceof DefaultPartHttpMessageReader) { - ((DefaultPartHttpMessageReader) codec).setEnableLoggingRequestDetails(enable); + if (codec instanceof DefaultPartHttpMessageReader defaultPartHttpMessageReader) { + defaultPartHttpMessageReader.setEnableLoggingRequestDetails(enable); } - if (codec instanceof PartEventHttpMessageReader) { - ((PartEventHttpMessageReader) codec).setEnableLoggingRequestDetails(enable); + if (codec instanceof PartEventHttpMessageReader partEventHttpMessageReader) { + partEventHttpMessageReader.setEnableLoggingRequestDetails(enable); } - if (codec instanceof FormHttpMessageWriter) { - ((FormHttpMessageWriter) codec).setEnableLoggingRequestDetails(enable); + if (codec instanceof FormHttpMessageWriter formHttpMessageWriter) { + formHttpMessageWriter.setEnableLoggingRequestDetails(enable); } - if (codec instanceof MultipartHttpMessageWriter) { - ((MultipartHttpMessageWriter) codec).setEnableLoggingRequestDetails(enable); + if (codec instanceof MultipartHttpMessageWriter multipartHttpMessageWriter) { + multipartHttpMessageWriter.setEnableLoggingRequestDetails(enable); } } @@ -513,17 +563,17 @@ else if (codec instanceof EncoderHttpMessageWriter) { } // Recurse for nested codecs - if (codec instanceof MultipartHttpMessageReader) { - initCodec(((MultipartHttpMessageReader) codec).getPartReader()); + if (codec instanceof MultipartHttpMessageReader multipartHttpMessageReader) { + initCodec(multipartHttpMessageReader.getPartReader()); } - else if (codec instanceof MultipartHttpMessageWriter) { - initCodec(((MultipartHttpMessageWriter) codec).getFormWriter()); + else if (codec instanceof MultipartHttpMessageWriter multipartHttpMessageWriter) { + initCodec(multipartHttpMessageWriter.getFormWriter()); } - else if (codec instanceof ServerSentEventHttpMessageReader) { - initCodec(((ServerSentEventHttpMessageReader) codec).getDecoder()); + else if (codec instanceof ServerSentEventHttpMessageReader serverSentEventHttpMessageReader) { + initCodec(serverSentEventHttpMessageReader.getDecoder()); } - else if (codec instanceof ServerSentEventHttpMessageWriter) { - initCodec(((ServerSentEventHttpMessageWriter) codec).getEncoder()); + else if (codec instanceof ServerSentEventHttpMessageWriter serverSentEventHttpMessageWriter) { + initCodec(serverSentEventHttpMessageWriter.getEncoder()); } } @@ -640,9 +690,25 @@ final List> getBaseTypedWriters() { addCodec(writers, new ProtobufHttpMessageWriter(this.protobufEncoder != null ? (ProtobufEncoder) this.protobufEncoder : new ProtobufEncoder())); } + addCodec(writers, new MultipartHttpMessageWriter(this::getPartWriters, new FormHttpMessageWriter())); + addCodec(writers, new PartEventHttpMessageWriter()); + addCodec(writers, new PartHttpMessageWriter()); return writers; } + private List> getPartWriters() { + if (this.multipartCodecs != null) { + return this.multipartCodecs.getWriters(); + } + else if (this.partWritersSupplier != null) { + return this.partWritersSupplier.get(); + } + else { + return Collections.emptyList(); + } + } + + /** * Hook for client or server specific typed writers. */ @@ -765,4 +831,41 @@ protected Encoder getKotlinSerializationJsonEncoder() { return this.kotlinSerializationJsonEncoder; } + + /** + * Default implementation of {@link CodecConfigurer.MultipartCodecs}. + */ + protected class DefaultMultipartCodecs implements CodecConfigurer.MultipartCodecs { + + private final List> writers = new ArrayList<>(); + + + DefaultMultipartCodecs() { + } + + DefaultMultipartCodecs(DefaultMultipartCodecs other) { + this.writers.addAll(other.writers); + } + + + @Override + public CodecConfigurer.MultipartCodecs encoder(Encoder encoder) { + writer(new EncoderHttpMessageWriter<>(encoder)); + initTypedWriters(); + return this; + } + + @Override + public CodecConfigurer.MultipartCodecs writer(HttpMessageWriter writer) { + this.writers.add(writer); + initTypedWriters(); + return this; + } + + List> getWriters() { + return this.writers; + } + } + + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java b/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java index c321682174b1..a494a74ccefe 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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,21 +16,12 @@ package org.springframework.http.codec.support; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.function.Supplier; import org.springframework.core.codec.Decoder; -import org.springframework.core.codec.Encoder; import org.springframework.http.codec.ClientCodecConfigurer; -import org.springframework.http.codec.EncoderHttpMessageWriter; -import org.springframework.http.codec.FormHttpMessageWriter; import org.springframework.http.codec.HttpMessageReader; -import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ServerSentEventHttpMessageReader; -import org.springframework.http.codec.multipart.MultipartHttpMessageWriter; -import org.springframework.http.codec.multipart.PartEventHttpMessageWriter; import org.springframework.lang.Nullable; /** @@ -40,47 +31,18 @@ */ class ClientDefaultCodecsImpl extends BaseDefaultCodecs implements ClientCodecConfigurer.ClientDefaultCodecs { - @Nullable - private DefaultMultipartCodecs multipartCodecs; - @Nullable private Decoder sseDecoder; - @Nullable - private Supplier>> partWritersSupplier; - ClientDefaultCodecsImpl() { } ClientDefaultCodecsImpl(ClientDefaultCodecsImpl other) { super(other); - this.multipartCodecs = (other.multipartCodecs != null ? - new DefaultMultipartCodecs(other.multipartCodecs) : null); this.sseDecoder = other.sseDecoder; } - - /** - * Set a supplier for part writers to use when - * {@link #multipartCodecs()} are not explicitly configured. - * That's the same set of writers as for general except for the multipart - * writer itself. - */ - void setPartWritersSupplier(Supplier>> supplier) { - this.partWritersSupplier = supplier; - initTypedWriters(); - } - - - @Override - public ClientCodecConfigurer.MultipartCodecs multipartCodecs() { - if (this.multipartCodecs == null) { - this.multipartCodecs = new DefaultMultipartCodecs(); - } - return this.multipartCodecs; - } - @Override public void serverSentEventDecoder(Decoder decoder) { this.sseDecoder = decoder; @@ -98,58 +60,4 @@ protected void extendObjectReaders(List> objectReaders) { addCodec(objectReaders, new ServerSentEventHttpMessageReader(decoder)); } - @Override - protected void extendTypedWriters(List> typedWriters) { - addCodec(typedWriters, new MultipartHttpMessageWriter(getPartWriters(), new FormHttpMessageWriter())); - addCodec(typedWriters, new PartEventHttpMessageWriter()); - } - - private List> getPartWriters() { - if (this.multipartCodecs != null) { - return this.multipartCodecs.getWriters(); - } - else if (this.partWritersSupplier != null) { - return this.partWritersSupplier.get(); - } - else { - return Collections.emptyList(); - } - } - - - /** - * Default implementation of {@link ClientCodecConfigurer.MultipartCodecs}. - */ - private class DefaultMultipartCodecs implements ClientCodecConfigurer.MultipartCodecs { - - private final List> writers = new ArrayList<>(); - - - DefaultMultipartCodecs() { - } - - DefaultMultipartCodecs(DefaultMultipartCodecs other) { - this.writers.addAll(other.writers); - } - - - @Override - public ClientCodecConfigurer.MultipartCodecs encoder(Encoder encoder) { - writer(new EncoderHttpMessageWriter<>(encoder)); - initTypedWriters(); - return this; - } - - @Override - public ClientCodecConfigurer.MultipartCodecs writer(HttpMessageWriter writer) { - this.writers.add(writer); - initTypedWriters(); - return this; - } - - List> getWriters() { - return this.writers; - } - } - } diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java index 382d11bec8c4..fc03ca5de384 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,11 +16,7 @@ package org.springframework.http.codec.support; -import java.util.ArrayList; -import java.util.List; - import org.springframework.http.codec.ClientCodecConfigurer; -import org.springframework.http.codec.HttpMessageWriter; /** * Default implementation of {@link ClientCodecConfigurer}. @@ -33,12 +29,10 @@ public class DefaultClientCodecConfigurer extends BaseCodecConfigurer implements public DefaultClientCodecConfigurer() { super(new ClientDefaultCodecsImpl()); - ((ClientDefaultCodecsImpl) defaultCodecs()).setPartWritersSupplier(this::getPartWriters); } private DefaultClientCodecConfigurer(DefaultClientCodecConfigurer other) { super(other); - ((ClientDefaultCodecsImpl) defaultCodecs()).setPartWritersSupplier(this::getPartWriters); } @@ -57,14 +51,5 @@ protected BaseDefaultCodecs cloneDefaultCodecs() { return new ClientDefaultCodecsImpl((ClientDefaultCodecsImpl) defaultCodecs()); } - private List> getPartWriters() { - List> result = new ArrayList<>(); - result.addAll(this.customCodecs.getTypedWriters().keySet()); - result.addAll(this.defaultCodecs.getBaseTypedWriters()); - result.addAll(this.customCodecs.getObjectWriters().keySet()); - result.addAll(this.defaultCodecs.getBaseObjectWriters()); - result.addAll(this.defaultCodecs.getCatchAllWriters()); - return result; - } } diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java b/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java index 186949655a4f..52c7bf407982 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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,14 +19,9 @@ import java.util.List; import org.springframework.core.codec.Encoder; -import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.codec.ServerSentEventHttpMessageWriter; -import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader; -import org.springframework.http.codec.multipart.MultipartHttpMessageReader; -import org.springframework.http.codec.multipart.PartEventHttpMessageReader; -import org.springframework.http.codec.multipart.PartHttpMessageWriter; import org.springframework.lang.Nullable; /** @@ -36,9 +31,6 @@ */ class ServerDefaultCodecsImpl extends BaseDefaultCodecs implements ServerCodecConfigurer.ServerDefaultCodecs { - @Nullable - private HttpMessageReader multipartReader; - @Nullable private Encoder sseEncoder; @@ -48,42 +40,16 @@ class ServerDefaultCodecsImpl extends BaseDefaultCodecs implements ServerCodecCo ServerDefaultCodecsImpl(ServerDefaultCodecsImpl other) { super(other); - this.multipartReader = other.multipartReader; this.sseEncoder = other.sseEncoder; } - @Override - public void multipartReader(HttpMessageReader reader) { - this.multipartReader = reader; - initTypedReaders(); - } - @Override public void serverSentEventEncoder(Encoder encoder) { this.sseEncoder = encoder; initObjectWriters(); } - - @Override - protected void extendTypedReaders(List> typedReaders) { - if (this.multipartReader != null) { - addCodec(typedReaders, this.multipartReader); - } - else { - DefaultPartHttpMessageReader partReader = new DefaultPartHttpMessageReader(); - addCodec(typedReaders, partReader); - addCodec(typedReaders, new MultipartHttpMessageReader(partReader)); - } - addCodec(typedReaders, new PartEventHttpMessageReader()); - } - - @Override - protected void extendTypedWriters(List> typedWriters) { - addCodec(typedWriters, new PartHttpMessageWriter()); - } - @Override protected void extendObjectWriters(List> objectWriters) { objectWriters.add(new ServerSentEventHttpMessageWriter(getSseEncoder())); diff --git a/spring-web/src/main/java/org/springframework/http/converter/ByteArrayHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/ByteArrayHttpMessageConverter.java index b5be96421b46..3a7025acbf12 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/ByteArrayHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/ByteArrayHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/converter/cbor/MappingJackson2CborHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/cbor/MappingJackson2CborHttpMessageConverter.java index 12016c184b87..7d49906a9740 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/cbor/MappingJackson2CborHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/cbor/MappingJackson2CborHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -37,8 +37,6 @@ *

    The default constructor uses the default configuration provided by * {@link Jackson2ObjectMapperBuilder}. * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Sebastien Deleuze * @since 5.0 */ diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java index 136f660140a7..4ef9b902defd 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java @@ -69,8 +69,6 @@ * Abstract base class for Jackson based and content type independent * {@link HttpMessageConverter} implementations. * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Arjen Poutsma * @author Keith Donald * @author Rossen Stoyanchev diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java index d1e1e580e782..260559e86399 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java @@ -89,8 +89,6 @@ * support for Kotlin classes and data classes * * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Sebastien Deleuze * @author Juergen Hoeller * @author Tadaya Tsuyukubo @@ -821,20 +819,20 @@ private void addDeserializers(SimpleModule module) { @SuppressWarnings("deprecation") // on Jackson 2.13: configure(MapperFeature, boolean) private void configureFeature(ObjectMapper objectMapper, Object feature, boolean enabled) { - if (feature instanceof JsonParser.Feature) { - objectMapper.configure((JsonParser.Feature) feature, enabled); + if (feature instanceof JsonParser.Feature jsonParserFeature) { + objectMapper.configure(jsonParserFeature, enabled); } - else if (feature instanceof JsonGenerator.Feature) { - objectMapper.configure((JsonGenerator.Feature) feature, enabled); + else if (feature instanceof JsonGenerator.Feature jsonGeneratorFeature) { + objectMapper.configure(jsonGeneratorFeature, enabled); } - else if (feature instanceof SerializationFeature) { - objectMapper.configure((SerializationFeature) feature, enabled); + else if (feature instanceof SerializationFeature serializationFeature) { + objectMapper.configure(serializationFeature, enabled); } - else if (feature instanceof DeserializationFeature) { - objectMapper.configure((DeserializationFeature) feature, enabled); + else if (feature instanceof DeserializationFeature deserializationFeature) { + objectMapper.configure(deserializationFeature, enabled); } - else if (feature instanceof MapperFeature) { - objectMapper.configure((MapperFeature) feature, enabled); + else if (feature instanceof MapperFeature mapperFeature) { + objectMapper.configure(mapperFeature, enabled); } else { throw new FatalBeanException("Unknown feature class: " + feature.getClass().getName()); diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java index adc81d96b164..a9f332645dee 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -129,8 +129,6 @@ * <property name="modulesToInstall" value="myapp.jackson.MySampleModule,myapp.jackson.MyOtherModule"/> * </bean> * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Dmitry Katsubo * @author Rossen Stoyanchev * @author Brian Clozel @@ -410,7 +408,7 @@ public final void setModulesToInstall(Class... modules) { /** * Set whether to let Jackson find available modules via the JDK ServiceLoader, - * based on META-INF metadata in the classpath. Requires Jackson 2.2 or higher. + * based on META-INF metadata in the classpath. *

    If this mode is not set, Spring's Jackson2ObjectMapperFactoryBean itself * will try to find the JSR-310 and Joda-Time support modules on the classpath - * provided that Java 8 and Joda-Time themselves are available, respectively. diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/JsonbHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/JsonbHttpMessageConverter.java index 94a5c2a2b6ef..48b2dbbd8ac7 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/JsonbHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/JsonbHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java index a64e0f2278e8..5280926d3244 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -36,8 +36,6 @@ * *

    The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}. * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Arjen Poutsma * @author Keith Donald * @author Rossen Stoyanchev diff --git a/spring-web/src/main/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverter.java index 5bed2ca92956..da51c2f6f6a6 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -35,8 +35,6 @@ * *

    The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}. * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Sebastien Deleuze * @since 5.0 */ diff --git a/spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java index 975f6feb1fe4..8a7fd943d58c 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -26,7 +26,6 @@ import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter; import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; -import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.util.ClassUtils; /** @@ -75,13 +74,6 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv public AllEncompassingFormHttpMessageConverter() { - try { - addPartConverter(new SourceHttpMessageConverter<>()); - } - catch (Error err) { - // Ignore when no TransformerFactory implementation is available - } - if (jaxb2Present && !jackson2XmlPresent) { addPartConverter(new Jaxb2RootElementHttpMessageConverter()); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java index 05a866066833..a5c3d1cb9622 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -37,8 +37,6 @@ * *

    The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}. * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Sebastien Deleuze * @since 4.1 */ diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverter.java index b06ddc8e3105..0f6a968cfd0b 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -69,8 +69,10 @@ public MarshallingHttpMessageConverter() { public MarshallingHttpMessageConverter(Marshaller marshaller) { Assert.notNull(marshaller, "Marshaller must not be null"); this.marshaller = marshaller; - if (marshaller instanceof Unmarshaller) { - this.unmarshaller = (Unmarshaller) marshaller; + // The following pattern variable cannot be named "unmarshaller" due to lacking + // support in Checkstyle: https://github.com/checkstyle/checkstyle/issues/10969 + if (marshaller instanceof Unmarshaller _unmarshaller) { + this.unmarshaller = _unmarshaller; } } @@ -121,7 +123,7 @@ protected boolean supports(Class clazz) { @Override protected Object readFromSource(Class clazz, HttpHeaders headers, Source source) throws Exception { - Assert.notNull(this.unmarshaller, "Property 'unmarshaller' is required"); + Assert.state(this.unmarshaller != null, "Property 'unmarshaller' is required"); Object result = this.unmarshaller.unmarshal(source); if (!clazz.isInstance(result)) { throw new TypeMismatchException(result, clazz); @@ -131,7 +133,7 @@ protected Object readFromSource(Class clazz, HttpHeaders headers, Source sour @Override protected void writeToResult(Object o, HttpHeaders headers, Result result) throws Exception { - Assert.notNull(this.marshaller, "Property 'marshaller' is required"); + Assert.state(this.marshaller != null, "Property 'marshaller' is required"); this.marshaller.marshal(o, result); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java index a2a6c77afee4..40d4b4de422a 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java @@ -21,7 +21,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.StringReader; -import java.util.HashSet; import java.util.Set; import javax.xml.parsers.DocumentBuilder; @@ -75,15 +74,12 @@ public class SourceHttpMessageConverter extends AbstractHttpMe private static final XMLResolver NO_OP_XML_RESOLVER = (publicID, systemID, base, ns) -> InputStream.nullInputStream(); - private static final Set> SUPPORTED_CLASSES = new HashSet<>(8); - - static { - SUPPORTED_CLASSES.add(DOMSource.class); - SUPPORTED_CLASSES.add(SAXSource.class); - SUPPORTED_CLASSES.add(StAXSource.class); - SUPPORTED_CLASSES.add(StreamSource.class); - SUPPORTED_CLASSES.add(Source.class); - } + private static final Set> SUPPORTED_CLASSES = Set.of( + DOMSource.class, + SAXSource.class, + StAXSource.class, + StreamSource.class, + Source.class); private final TransformerFactory transformerFactory = TransformerFactory.newInstance(); diff --git a/spring-web/src/main/java/org/springframework/http/server/RequestPath.java b/spring-web/src/main/java/org/springframework/http/server/RequestPath.java index 70e2ccae63c0..e98b9b078b52 100644 --- a/spring-web/src/main/java/org/springframework/http/server/RequestPath.java +++ b/spring-web/src/main/java/org/springframework/http/server/RequestPath.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -46,7 +46,7 @@ public interface RequestPath extends PathContainer { /** * The portion of the request path after the context path which is typically - * used for request mapping within the application . + * used for request mapping within the application. */ PathContainer pathWithinApplication(); diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java index 15ae65409331..ca28b8e05808 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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,7 +72,7 @@ public HttpServletResponse getServletResponse() { @Override public void setStatusCode(HttpStatusCode status) { - Assert.notNull(status, "HttpStatus must not be null"); + Assert.notNull(status, "HttpStatusCode must not be null"); this.servletResponse.setStatus(status.value()); } diff --git a/spring-web/src/main/java/org/springframework/http/observation/DefaultServerRequestObservationConvention.java b/spring-web/src/main/java/org/springframework/http/server/observation/DefaultServerRequestObservationConvention.java similarity index 95% rename from spring-web/src/main/java/org/springframework/http/observation/DefaultServerRequestObservationConvention.java rename to spring-web/src/main/java/org/springframework/http/server/observation/DefaultServerRequestObservationConvention.java index 43690b8497ff..72c6e762ca81 100644 --- a/spring-web/src/main/java/org/springframework/http/observation/DefaultServerRequestObservationConvention.java +++ b/spring-web/src/main/java/org/springframework/http/server/observation/DefaultServerRequestObservationConvention.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package org.springframework.http.observation; +package org.springframework.http.server.observation; import io.micrometer.common.KeyValue; import io.micrometer.common.KeyValues; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; -import org.springframework.http.observation.ServerHttpObservationDocumentation.HighCardinalityKeyNames; -import org.springframework.http.observation.ServerHttpObservationDocumentation.LowCardinalityKeyNames; +import org.springframework.http.server.observation.ServerHttpObservationDocumentation.HighCardinalityKeyNames; +import org.springframework.http.server.observation.ServerHttpObservationDocumentation.LowCardinalityKeyNames; import org.springframework.util.StringUtils; /** diff --git a/spring-web/src/main/java/org/springframework/http/observation/ServerHttpObservationDocumentation.java b/spring-web/src/main/java/org/springframework/http/server/observation/ServerHttpObservationDocumentation.java similarity index 95% rename from spring-web/src/main/java/org/springframework/http/observation/ServerHttpObservationDocumentation.java rename to spring-web/src/main/java/org/springframework/http/server/observation/ServerHttpObservationDocumentation.java index 0264c9a6744b..d42d029c9d39 100644 --- a/spring-web/src/main/java/org/springframework/http/observation/ServerHttpObservationDocumentation.java +++ b/spring-web/src/main/java/org/springframework/http/server/observation/ServerHttpObservationDocumentation.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.http.observation; +package org.springframework.http.server.observation; import io.micrometer.common.KeyValue; import io.micrometer.common.docs.KeyName; @@ -33,9 +33,9 @@ public enum ServerHttpObservationDocumentation implements ObservationDocumentation { /** - * HTTP exchanges observations for Servlet-based servers. + * HTTP request observations for Servlet-based servers. */ - HTTP_SERVLET_SERVER_EXCHANGES { + HTTP_SERVLET_SERVER_REQUESTS { @Override public Class> getDefaultConvention() { return DefaultServerRequestObservationConvention.class; diff --git a/spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationContext.java b/spring-web/src/main/java/org/springframework/http/server/observation/ServerRequestObservationContext.java similarity index 83% rename from spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationContext.java rename to spring-web/src/main/java/org/springframework/http/server/observation/ServerRequestObservationContext.java index 43d7949ecf86..bf37e01df18c 100644 --- a/spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationContext.java +++ b/spring-web/src/main/java/org/springframework/http/server/observation/ServerRequestObservationContext.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.http.observation; +package org.springframework.http.server.observation; import io.micrometer.observation.transport.RequestReplyReceiverContext; import jakarta.servlet.http.HttpServletRequest; @@ -23,10 +23,10 @@ import org.springframework.lang.Nullable; /** - * Context that holds information for metadata collection during observations - * for {@link ServerHttpObservationDocumentation#HTTP_SERVLET_SERVER_EXCHANGES Servlet HTTP exchanges}. + * Context that holds information for metadata collection regarding + * {@link ServerHttpObservationDocumentation#HTTP_SERVLET_SERVER_REQUESTS Servlet HTTP requests} observations. *

    This context also extends {@link RequestReplyReceiverContext} for propagating - * tracing information with the HTTP server exchange. + * tracing information during HTTP request processing. * * @author Brian Clozel * @since 6.0 diff --git a/spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationConvention.java b/spring-web/src/main/java/org/springframework/http/server/observation/ServerRequestObservationConvention.java similarity index 88% rename from spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationConvention.java rename to spring-web/src/main/java/org/springframework/http/server/observation/ServerRequestObservationConvention.java index ce1e96ce81b6..39f4b1967da0 100644 --- a/spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationConvention.java +++ b/spring-web/src/main/java/org/springframework/http/server/observation/ServerRequestObservationConvention.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.http.observation; +package org.springframework.http.server.observation; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationConvention; /** - * Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_SERVLET_SERVER_EXCHANGES Servlet HTTP exchanges}. + * Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_SERVLET_SERVER_REQUESTS Servlet HTTP requests}. * * @author Brian Clozel * @since 6.0 diff --git a/spring-web/src/main/java/org/springframework/http/observation/package-info.java b/spring-web/src/main/java/org/springframework/http/server/observation/package-info.java similarity index 67% rename from spring-web/src/main/java/org/springframework/http/observation/package-info.java rename to spring-web/src/main/java/org/springframework/http/server/observation/package-info.java index 73b05998c39f..aebdd17f177d 100644 --- a/spring-web/src/main/java/org/springframework/http/observation/package-info.java +++ b/spring-web/src/main/java/org/springframework/http/server/observation/package-info.java @@ -1,9 +1,9 @@ /** - * Instrumentation for {@link io.micrometer.observation.Observation observing} HTTP applications. + * Instrumentation for {@link io.micrometer.observation.Observation observing} HTTP server applications. */ @NonNullApi @NonNullFields -package org.springframework.http.observation; +package org.springframework.http.server.observation; import org.springframework.lang.NonNullApi; import org.springframework.lang.NonNullFields; diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerReadPublisher.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerReadPublisher.java index bec275652bab..d9a39fbdffa2 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerReadPublisher.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerReadPublisher.java @@ -38,7 +38,7 @@ * *

    Specifically a base class for reading from the HTTP request body with * Servlet non-blocking I/O and Undertow XNIO as well as handling incoming - * WebSocket messages with standard Java WebSocket (JSR-356), Jetty, and + * WebSocket messages with standard Jakarta WebSocket (JSR-356), Jetty, and * Undertow. * * @author Arjen Poutsma diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java index bf4520ac86b3..0223f1f274a0 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -35,7 +35,7 @@ * *

    Specifically a base class for writing to the HTTP response body with * Servlet non-blocking I/O and Undertow XNIO as well for writing WebSocket - * messages through the Java WebSocket API (JSR-356), Jetty, and Undertow. + * messages through the Jakarta WebSocket API (JSR-356), Jetty, and Undertow. * * @author Arjen Poutsma * @author Violeta Georgieva diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java index d9eb907b0ea8..888d58a84bd9 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java @@ -115,11 +115,11 @@ public boolean setRawStatusCode(@Nullable Integer statusCode) { return setStatusCode(statusCode != null ? HttpStatusCode.valueOf(statusCode) : null); } + @Deprecated @Override @Nullable - @Deprecated public Integer getRawStatusCode() { - return this.statusCode != null ? this.statusCode.value() : null; + return (this.statusCode != null ? this.statusCode.value() : null); } @Override @@ -291,10 +291,10 @@ else if (this.state.compareAndSet(State.COMMIT_ACTION_FAILED, State.COMMITTING)) /** * Invoked when the response is getting committed allowing subclasses to * make apply header values to the underlying response. - *

    Note that most subclasses use an {@link HttpHeaders} instance that + *

    Note that some subclasses use an {@link HttpHeaders} instance that * wraps an adapter to the native response headers such that changes are * propagated to the underlying response on the go. That means this callback - * is typically not used other than for specialized updates such as setting + * might not be used other than for specialized updates such as setting * the contentType or characterEncoding fields in a Servlet response. */ protected abstract void applyHeaders(); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ChannelSendOperator.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ChannelSendOperator.java index da46443edc2f..4cba68c3bfb8 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ChannelSendOperator.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ChannelSendOperator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -335,8 +335,8 @@ public void cancel() { private void releaseCachedItem() { synchronized (this) { Object item = this.item; - if (item instanceof DataBuffer) { - DataBufferUtils.release((DataBuffer) item); + if (item instanceof DataBuffer dataBuffer) { + DataBufferUtils.release(dataBuffer); } this.item = null; } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultSslInfo.java b/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultSslInfo.java index 3533ad70c9ce..4ca73c11d369 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultSslInfo.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultSslInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -100,8 +100,8 @@ private static X509Certificate[] initCertificates(SSLSession session) { List result = new ArrayList<>(certificates.length); for (Certificate certificate : certificates) { - if (certificate instanceof X509Certificate) { - result.add((X509Certificate) certificate); + if (certificate instanceof X509Certificate x509Certificate) { + result.add(x509Certificate); } } return (!result.isEmpty() ? result.toArray(new X509Certificate[0]) : null); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHeadersAdapter.java index 3ddf02b993be..e0dfecfd77a9 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHeadersAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHeadersAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -18,7 +18,6 @@ import java.util.AbstractSet; import java.util.Collection; -import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -105,13 +104,13 @@ public boolean isEmpty() { @Override public boolean containsKey(Object key) { - return (key instanceof String && this.headers.contains((String) key)); + return (key instanceof String headerName && this.headers.contains(headerName)); } @Override public boolean containsValue(Object value) { - return (value instanceof String && - this.headers.stream().anyMatch(field -> field.contains((String) value))); + return (value instanceof String searchString && + this.headers.stream().anyMatch(field -> field.contains(searchString))); } @Nullable @@ -134,9 +133,9 @@ public List put(String key, List value) { @Nullable @Override public List remove(Object key) { - if (key instanceof String) { + if (key instanceof String headerName) { List oldValues = get(key); - this.headers.remove((String) key); + this.headers.remove(headerName); return oldValues; } return null; @@ -170,7 +169,6 @@ public Set>> entrySet() { public Iterator>> iterator() { return new EntryIterator(); } - @Override public int size() { return headers.size(); @@ -187,16 +185,16 @@ public String toString() { private class EntryIterator implements Iterator>> { - private final Enumeration names = headers.getFieldNames(); + private final Iterator names = headers.getFieldNamesCollection().iterator(); @Override public boolean hasNext() { - return this.names.hasMoreElements(); + return this.names.hasNext(); } @Override public Entry> next() { - return new HeaderEntry(this.names.nextElement()); + return new HeaderEntry(this.names.next()); } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java index 35964ca6d715..694bf286f816 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java @@ -17,12 +17,11 @@ package org.springframework.http.server.reactive; import java.io.IOException; +import java.io.OutputStream; import java.net.URISyntaxException; import java.nio.ByteBuffer; -import java.nio.charset.Charset; import jakarta.servlet.AsyncContext; -import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletResponse; @@ -35,8 +34,8 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.MultiValueMap; /** @@ -45,11 +44,21 @@ * * @author Violeta Georgieva * @author Brian Clozel + * @author Juergen Hoeller * @since 5.0 * @see org.springframework.web.server.adapter.AbstractReactiveWebInitializer */ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter { + private static final boolean jetty11Present = ClassUtils.isPresent( + "org.eclipse.jetty.server.HttpOutput", JettyHttpHandlerAdapter.class.getClassLoader()); + + /* Jetty 12: see spring-web.gradle + private static final boolean jetty12Present = ClassUtils.isPresent( + "org.eclipse.jetty.ee10.servlet.HttpOutput", JettyHttpHandlerAdapter.class.getClassLoader()); + */ + + public JettyHttpHandlerAdapter(HttpHandler httpHandler) { super(httpHandler); } @@ -59,23 +68,39 @@ public JettyHttpHandlerAdapter(HttpHandler httpHandler) { protected ServletServerHttpRequest createRequest(HttpServletRequest request, AsyncContext context) throws IOException, URISyntaxException { - Assert.notNull(getServletPath(), "Servlet path is not initialized"); - return new JettyServerHttpRequest( - request, context, getServletPath(), getDataBufferFactory(), getBufferSize()); + if (jetty11Present) { + Assert.state(getServletPath() != null, "Servlet path is not initialized"); + return new Jetty11ServerHttpRequest( + request, context, getServletPath(), getDataBufferFactory(), getBufferSize()); + } + else { + return super.createRequest(request, context); + } } @Override protected ServletServerHttpResponse createResponse(HttpServletResponse response, AsyncContext context, ServletServerHttpRequest request) throws IOException { - return new JettyServerHttpResponse( - response, context, getDataBufferFactory(), getBufferSize(), request); + if (jetty11Present) { + return new Jetty11ServerHttpResponse( + response, context, getDataBufferFactory(), getBufferSize(), request); + } + /* Jetty 12: see spring-web.gradle + else if (jetty12Present) { + return new Jetty12ServerHttpResponse( + response, context, getDataBufferFactory(), getBufferSize(), request); + } + */ + else { + return super.createResponse(response, context, request); + } } - private static final class JettyServerHttpRequest extends ServletServerHttpRequest { + private static final class Jetty11ServerHttpRequest extends ServletServerHttpRequest { - JettyServerHttpRequest(HttpServletRequest request, AsyncContext asyncContext, + Jetty11ServerHttpRequest(HttpServletRequest request, AsyncContext asyncContext, String servletPath, DataBufferFactory bufferFactory, int bufferSize) throws IOException, URISyntaxException { @@ -84,8 +109,7 @@ private static final class JettyServerHttpRequest extends ServletServerHttpReque private static MultiValueMap createHeaders(HttpServletRequest servletRequest) { Request request = getRequest(servletRequest); - HttpFields.Mutable fields = HttpFields.build(request.getHttpFields()); - return new JettyHeadersAdapter(fields); + return new JettyHeadersAdapter(HttpFields.build(request.getHttpFields())); } private static Request getRequest(HttpServletRequest request) { @@ -104,9 +128,9 @@ else if (request instanceof HttpServletRequestWrapper wrapper) { } - private static final class JettyServerHttpResponse extends ServletServerHttpResponse { + private static final class Jetty11ServerHttpResponse extends ServletServerHttpResponse { - JettyServerHttpResponse(HttpServletResponse response, AsyncContext asyncContext, + Jetty11ServerHttpResponse(HttpServletResponse response, AsyncContext asyncContext, DataBufferFactory bufferFactory, int bufferSize, ServletServerHttpRequest request) throws IOException { @@ -115,8 +139,7 @@ private static final class JettyServerHttpResponse extends ServletServerHttpResp private static HttpHeaders createHeaders(HttpServletResponse servletResponse) { Response response = getResponse(servletResponse); - HttpFields.Mutable fields = response.getHttpFields(); - return new HttpHeaders(new JettyHeadersAdapter(fields)); + return new HttpHeaders(new JettyHeadersAdapter(response.getHttpFields())); } private static Response getResponse(HttpServletResponse response) { @@ -135,36 +158,45 @@ else if (response instanceof HttpServletResponseWrapper wrapper) { @Override protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException { - ByteBuffer input = dataBuffer.toByteBuffer(); - int len = input.remaining(); - ServletResponse response = getNativeResponse(); - ((HttpOutput) response.getOutputStream()).write(input); - return len; + OutputStream output = getOutputStream(); + if (output instanceof HttpOutput httpOutput) { + ByteBuffer input = dataBuffer.toByteBuffer(); + int len = input.remaining(); + httpOutput.write(input); + return len; + } + return super.writeToOutputStream(dataBuffer); } @Override protected void applyHeaders() { - HttpServletResponse response = getNativeResponse(); - MediaType contentType = null; - try { - contentType = getHeaders().getContentType(); - } - catch (Exception ex) { - String rawContentType = getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); - response.setContentType(rawContentType); - } - if (response.getContentType() == null && contentType != null) { - response.setContentType(contentType.toString()); - } - Charset charset = (contentType != null ? contentType.getCharset() : null); - if (response.getCharacterEncoding() == null && charset != null) { - response.setCharacterEncoding(charset.name()); - } - long contentLength = getHeaders().getContentLength(); - if (contentLength != -1) { - response.setContentLengthLong(contentLength); + adaptHeaders(false); + } + } + + + /* Jetty 12: see spring-web.gradle + private static final class Jetty12ServerHttpResponse extends ServletServerHttpResponse { + + Jetty12ServerHttpResponse(HttpServletResponse response, AsyncContext asyncContext, + DataBufferFactory bufferFactory, int bufferSize, ServletServerHttpRequest request) + throws IOException { + + super(response, asyncContext, bufferFactory, bufferSize, request); + } + + @Override + protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException { + OutputStream output = getOutputStream(); + if (output instanceof org.eclipse.jetty.ee10.servlet.HttpOutput httpOutput) { + ByteBuffer input = dataBuffer.toByteBuffer(); + int len = input.remaining(); + httpOutput.write(input); + return len; } + return super.writeToOutputStream(dataBuffer); } } + */ } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java index 7df363091ebb..43bc99122b95 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java @@ -196,8 +196,8 @@ public T getNativeRequest() { @Override @Nullable protected String initId() { - if (this.request instanceof Connection) { - return ((Connection) this.request).channel().id().asShortText() + + if (this.request instanceof Connection connection) { + return connection.channel().id().asShortText() + "-" + logPrefixIndex.incrementAndGet(); } return null; @@ -212,8 +212,8 @@ protected String initLogPrefix() { if (id != null) { return id; } - if (this.request instanceof Connection) { - return ((Connection) this.request).channel().id().asShortText() + + if (this.request instanceof Connection connection) { + return connection.channel().id().asShortText() + "-" + logPrefixIndex.incrementAndGet(); } return getId(); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java index 08553cd78dad..bc1981af40dc 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java @@ -193,8 +193,8 @@ public T getNativeRequest() { @Override @Nullable protected String initId() { - if (this.request instanceof Connection) { - return ((Connection) this.request).channel().id().asShortText() + + if (this.request instanceof Connection connection) { + return connection.channel().id().asShortText() + "-" + logPrefixIndex.incrementAndGet(); } return null; @@ -209,8 +209,8 @@ protected String initLogPrefix() { if (id != null) { return id; } - if (this.request instanceof Connection) { - return ((Connection) this.request).channel().id().asShortText() + + if (this.request instanceof Connection connection) { + return connection.channel().id().asShortText() + "-" + logPrefixIndex.incrementAndGet(); } return getId(); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java index 84f157b3ccc2..fc6143bfdaf0 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -123,11 +123,11 @@ public Flux getBody() { * @since 5.3.3 */ public static T getNativeRequest(ServerHttpRequest request) { - if (request instanceof AbstractServerHttpRequest) { - return ((AbstractServerHttpRequest) request).getNativeRequest(); + if (request instanceof AbstractServerHttpRequest abstractServerHttpRequest) { + return abstractServerHttpRequest.getNativeRequest(); } - else if (request instanceof ServerHttpRequestDecorator) { - return getNativeRequest(((ServerHttpRequestDecorator) request).getDelegate()); + else if (request instanceof ServerHttpRequestDecorator serverHttpRequestDecorator) { + return getNativeRequest(serverHttpRequestDecorator.getDelegate()); } else { throw new IllegalArgumentException( diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java index d2609626f3d4..7f97d04484a5 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java @@ -67,8 +67,8 @@ default boolean setRawStatusCode(@Nullable Integer value) { * @since 5.2.4 * @deprecated as of 6.0, in favor of {@link #getStatusCode()} */ - @Nullable @Deprecated(since = "6.0") + @Nullable default Integer getRawStatusCode() { HttpStatusCode httpStatus = getStatusCode(); return (httpStatus != null ? httpStatus.value() : null); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponseDecorator.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponseDecorator.java index 87f5a89fbacc..e336284d38fd 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponseDecorator.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponseDecorator.java @@ -131,11 +131,11 @@ public Mono setComplete() { * @since 5.3.3 */ public static T getNativeResponse(ServerHttpResponse response) { - if (response instanceof AbstractServerHttpResponse) { - return ((AbstractServerHttpResponse) response).getNativeResponse(); + if (response instanceof AbstractServerHttpResponse abstractServerHttpResponse) { + return abstractServerHttpResponse.getNativeResponse(); } - else if (response instanceof ServerHttpResponseDecorator) { - return getNativeResponse(((ServerHttpResponseDecorator) response).getDelegate()); + else if (response instanceof ServerHttpResponseDecorator serverHttpResponseDecorator) { + return getNativeResponse(serverHttpResponseDecorator.getDelegate()); } else { throw new IllegalArgumentException( diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletHttpHandlerAdapter.java index 8918aa568f69..456f85e4b256 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletHttpHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletHttpHandlerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -169,7 +169,7 @@ public void service(ServletRequest request, ServletResponse response) throws Ser AsyncListener requestListener; String logPrefix; try { - httpRequest = createRequest(((HttpServletRequest) request), asyncContext); + httpRequest = createRequest((HttpServletRequest) request, asyncContext); requestListener = httpRequest.getAsyncListener(); logPrefix = httpRequest.getLogPrefix(); } @@ -182,8 +182,10 @@ public void service(ServletRequest request, ServletResponse response) throws Ser return; } - ServerHttpResponse httpResponse = createResponse(((HttpServletResponse) response), asyncContext, httpRequest); - AsyncListener responseListener = ((ServletServerHttpResponse) httpResponse).getAsyncListener(); + ServletServerHttpResponse wrappedResponse = + createResponse((HttpServletResponse) response, asyncContext, httpRequest); + ServerHttpResponse httpResponse = wrappedResponse; + AsyncListener responseListener = wrappedResponse.getAsyncListener(); if (httpRequest.getMethod() == HttpMethod.HEAD) { httpResponse = new HttpHeadResponseDecorator(httpResponse); } @@ -200,7 +202,7 @@ public void service(ServletRequest request, ServletResponse response) throws Ser protected ServletServerHttpRequest createRequest(HttpServletRequest request, AsyncContext context) throws IOException, URISyntaxException { - Assert.notNull(this.servletPath, "Servlet path is not initialized"); + Assert.state(this.servletPath != null, "Servlet path is not initialized"); return new ServletServerHttpRequest( request, context, this.servletPath, getDataBufferFactory(), getBufferSize()); } @@ -263,9 +265,7 @@ private static class HttpHandlerAsyncListener implements AsyncListener { private final String logPrefix; - - public HttpHandlerAsyncListener( - AsyncListener requestAsyncListener, AsyncListener responseAsyncListener, + public HttpHandlerAsyncListener(AsyncListener requestAsyncListener, AsyncListener responseAsyncListener, Runnable handlerDisposeTask, AtomicBoolean completionFlag, String logPrefix) { this.requestAsyncListener = requestAsyncListener; @@ -275,7 +275,6 @@ public HttpHandlerAsyncListener( this.logPrefix = logPrefix; } - @Override public void onTimeout(AsyncEvent event) { // Should never happen since we call asyncContext.setTimeout(-1) @@ -361,9 +360,7 @@ private static class HandlerResultSubscriber implements Subscriber, Runnab @Nullable private volatile Subscription subscription; - public HandlerResultSubscriber( - AsyncContext asyncContext, AtomicBoolean completionFlag, String logPrefix) { - + public HandlerResultSubscriber(AsyncContext asyncContext, AtomicBoolean completionFlag, String logPrefix) { this.asyncContext = asyncContext; this.completionFlag = completionFlag; this.logPrefix = logPrefix; diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java index e010c1bdad4b..bf62a8d676d4 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java @@ -56,6 +56,7 @@ * Adapt {@link ServerHttpRequest} to the Servlet {@link HttpServletRequest}. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 5.0 */ class ServletServerHttpRequest extends AbstractServerHttpRequest { @@ -65,6 +66,8 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest { private final HttpServletRequest request; + private final ServletInputStream inputStream; + private final RequestBodyPublisher bodyPublisher; private final Object cookieLock = new Object(); @@ -99,8 +102,8 @@ public ServletServerHttpRequest(MultiValueMap headers, HttpServl this.asyncListener = new RequestAsyncListener(); // Tomcat expects ReadListener registration on initial thread - ServletInputStream inputStream = request.getInputStream(); - this.bodyPublisher = new RequestBodyPublisher(inputStream); + this.inputStream = request.getInputStream(); + this.bodyPublisher = new RequestBodyPublisher(this.inputStream); this.bodyPublisher.registerReadListener(); } @@ -231,6 +234,13 @@ AsyncListener getAsyncListener() { return this.asyncListener; } + /** + * Return the {@link ServletInputStream} for the current response. + */ + protected final ServletInputStream getInputStream() { + return this.inputStream; + } + /** * Read from the request body InputStream and return a DataBuffer. * Invoked only when {@link ServletInputStream#isReady()} returns "true". @@ -239,7 +249,7 @@ AsyncListener getAsyncListener() { * or {@link #EOF_BUFFER} if the input stream returned -1. */ DataBuffer readFromInputStream() throws IOException { - int read = this.request.getInputStream().read(this.buffer); + int read = this.inputStream.read(this.buffer); logBytesRead(read); if (read > 0) { diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java index 4ca820990e7b..0e34c6b7256e 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -44,6 +44,7 @@ * Adapt {@link ServerHttpResponse} to the Servlet {@link HttpServletResponse}. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 5.0 */ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse { @@ -128,6 +129,11 @@ protected void applyHeaders() { this.response.addHeader(headerName, headerValue); } }); + + adaptHeaders(false); + } + + protected void adaptHeaders(boolean removeAdaptedHeaders) { MediaType contentType = null; try { contentType = getHeaders().getContentType(); @@ -139,19 +145,25 @@ protected void applyHeaders() { if (this.response.getContentType() == null && contentType != null) { this.response.setContentType(contentType.toString()); } + Charset charset = (contentType != null ? contentType.getCharset() : null); if (this.response.getCharacterEncoding() == null && charset != null) { this.response.setCharacterEncoding(charset.name()); } + long contentLength = getHeaders().getContentLength(); if (contentLength != -1) { this.response.setContentLengthLong(contentLength); } + + if (removeAdaptedHeaders) { + getHeaders().remove(HttpHeaders.CONTENT_TYPE); + getHeaders().remove(HttpHeaders.CONTENT_LENGTH); + } } @Override protected void applyCookies() { - // Servlet Cookie doesn't support same site: // https://github.com/eclipse-ee4j/servlet-api/issues/175 @@ -185,6 +197,13 @@ protected Processor, Void> createBodyFlu return processor; } + /** + * Return the {@link ServletOutputStream} for the current response. + */ + protected final ServletOutputStream getOutputStream() { + return this.outputStream; + } + /** * Write the DataBuffer to the response body OutputStream. * Invoked only when {@link ServletOutputStream#isReady()} returns "true" diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHeadersAdapter.java index 0063239f9154..d24a3cc7f672 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHeadersAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHeadersAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java index 91f3272a80b0..f302eb5f44eb 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java @@ -20,12 +20,8 @@ import java.lang.reflect.Field; import java.net.URISyntaxException; import java.nio.ByteBuffer; -import java.nio.charset.Charset; import jakarta.servlet.AsyncContext; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletResponse; @@ -40,7 +36,6 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.ReflectionUtils; @@ -52,12 +47,12 @@ * @author Violeta Georgieva * @author Brian Clozel * @author Sam Brannen + * @author Juergen Hoeller * @since 5.0 * @see org.springframework.web.server.adapter.AbstractReactiveWebInitializer */ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter { - public TomcatHttpHandlerAdapter(HttpHandler httpHandler) { super(httpHandler); } @@ -67,7 +62,7 @@ public TomcatHttpHandlerAdapter(HttpHandler httpHandler) { protected ServletServerHttpRequest createRequest(HttpServletRequest request, AsyncContext asyncContext) throws IOException, URISyntaxException { - Assert.notNull(getServletPath(), "Servlet path is not initialized"); + Assert.state(getServletPath() != null, "Servlet path is not initialized"); return new TomcatServerHttpRequest( request, asyncContext, getServletPath(), getDataBufferFactory(), getBufferSize()); } @@ -130,11 +125,11 @@ else if (request instanceof HttpServletRequestWrapper wrapper) { @Override protected DataBuffer readFromInputStream() throws IOException { - ServletInputStream inputStream = ((ServletRequest) getNativeRequest()).getInputStream(); - if (!(inputStream instanceof CoyoteInputStream coyoteInputStream)) { + if (!(getInputStream() instanceof CoyoteInputStream coyoteInputStream)) { // It's possible InputStream can be wrapped, preventing use of CoyoteInputStream return super.readFromInputStream(); } + ByteBuffer byteBuffer = this.factory.isDirect() ? ByteBuffer.allocateDirect(this.bufferSize) : ByteBuffer.allocate(this.bufferSize); @@ -197,36 +192,18 @@ else if (response instanceof HttpServletResponseWrapper wrapper) { @Override protected void applyHeaders() { - HttpServletResponse response = getNativeResponse(); - MediaType contentType = null; - try { - contentType = getHeaders().getContentType(); - } - catch (Exception ex) { - String rawContentType = getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); - response.setContentType(rawContentType); - } - if (response.getContentType() == null && contentType != null) { - response.setContentType(contentType.toString()); - } - getHeaders().remove(HttpHeaders.CONTENT_TYPE); - Charset charset = (contentType != null ? contentType.getCharset() : null); - if (response.getCharacterEncoding() == null && charset != null) { - response.setCharacterEncoding(charset.name()); - } - long contentLength = getHeaders().getContentLength(); - if (contentLength != -1) { - response.setContentLengthLong(contentLength); - } - getHeaders().remove(HttpHeaders.CONTENT_LENGTH); + adaptHeaders(true); } @Override protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException { + if (!(getOutputStream() instanceof CoyoteOutputStream coyoteOutputStream)) { + return super.writeToOutputStream(dataBuffer); + } + ByteBuffer input = dataBuffer.toByteBuffer(); int len = input.remaining(); - ServletResponse response = getNativeResponse(); - ((CoyoteOutputStream) response.getOutputStream()).write(input); + coyoteOutputStream.write(input); return len; } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java index 6a195245c90a..bf6420c43665 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java @@ -64,12 +64,12 @@ class UndertowServerHttpResponse extends AbstractListenerServerHttpResponse impl HttpServerExchange exchange, DataBufferFactory bufferFactory, UndertowServerHttpRequest request) { super(bufferFactory, createHeaders(exchange)); - Assert.notNull(exchange, "HttpServerExchange must not be null"); this.exchange = exchange; this.request = request; } private static HttpHeaders createHeaders(HttpServerExchange exchange) { + Assert.notNull(exchange, "HttpServerExchange must not be null"); UndertowHeadersAdapter headersMap = new UndertowHeadersAdapter(exchange.getResponseHeaders()); return new HttpHeaders(headersMap); } diff --git a/spring-web/src/main/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConvention.java b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/DefaultServerRequestObservationConvention.java similarity index 92% rename from spring-web/src/main/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConvention.java rename to spring-web/src/main/java/org/springframework/http/server/reactive/observation/DefaultServerRequestObservationConvention.java index 51e7efb75da6..825cf99f3d3a 100644 --- a/spring-web/src/main/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConvention.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/DefaultServerRequestObservationConvention.java @@ -14,17 +14,16 @@ * limitations under the License. */ -package org.springframework.http.observation.reactive; +package org.springframework.http.server.reactive.observation; import io.micrometer.common.KeyValue; import io.micrometer.common.KeyValues; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; -import org.springframework.http.observation.reactive.ServerHttpObservationDocumentation.HighCardinalityKeyNames; -import org.springframework.http.observation.reactive.ServerHttpObservationDocumentation.LowCardinalityKeyNames; +import org.springframework.http.server.reactive.observation.ServerHttpObservationDocumentation.HighCardinalityKeyNames; +import org.springframework.http.server.reactive.observation.ServerHttpObservationDocumentation.LowCardinalityKeyNames; import org.springframework.util.StringUtils; -import org.springframework.web.util.pattern.PathPattern; /** * Default {@link ServerRequestObservationConvention}. @@ -117,12 +116,12 @@ protected KeyValue status(ServerRequestObservationContext context) { protected KeyValue uri(ServerRequestObservationContext context) { if (context.getCarrier() != null) { - PathPattern pattern = context.getPathPattern(); + String pattern = context.getPathPattern(); if (pattern != null) { - if (pattern.toString().isEmpty()) { + if (pattern.isEmpty()) { return URI_ROOT; } - return KeyValue.of(LowCardinalityKeyNames.URI, pattern.toString()); + return KeyValue.of(LowCardinalityKeyNames.URI, pattern); } if (context.getResponse() != null && context.getResponse().getStatusCode() != null) { HttpStatus status = HttpStatus.resolve(context.getResponse().getStatusCode().value()); diff --git a/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerHttpObservationDocumentation.java b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerHttpObservationDocumentation.java similarity index 95% rename from spring-web/src/main/java/org/springframework/http/observation/reactive/ServerHttpObservationDocumentation.java rename to spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerHttpObservationDocumentation.java index 10d19a90ca16..03cfa784ab57 100644 --- a/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerHttpObservationDocumentation.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerHttpObservationDocumentation.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.http.observation.reactive; +package org.springframework.http.server.reactive.observation; import io.micrometer.common.KeyValue; import io.micrometer.common.docs.KeyName; @@ -33,9 +33,9 @@ public enum ServerHttpObservationDocumentation implements ObservationDocumentation { /** - * HTTP exchanges observations for reactive servers. + * HTTP request observations for reactive servers. */ - HTTP_REACTIVE_SERVER_EXCHANGES { + HTTP_REACTIVE_SERVER_REQUESTS { @Override public Class> getDefaultConvention() { return DefaultServerRequestObservationConvention.class; diff --git a/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationContext.java b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerRequestObservationContext.java similarity index 72% rename from spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationContext.java rename to spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerRequestObservationContext.java index 5a3e1e72cd05..070d86373455 100644 --- a/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationContext.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerRequestObservationContext.java @@ -14,46 +14,47 @@ * limitations under the License. */ -package org.springframework.http.observation.reactive; +package org.springframework.http.server.reactive.observation; + +import java.util.Collections; +import java.util.Map; import io.micrometer.observation.transport.RequestReplyReceiverContext; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.lang.Nullable; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.pattern.PathPattern; /** - * Context that holds information for metadata collection during observations - * for {@link ServerHttpObservationDocumentation#HTTP_REACTIVE_SERVER_EXCHANGES reactive HTTP exchanges}. + * Context that holds information for metadata collection regarding + * {@link ServerHttpObservationDocumentation#HTTP_REACTIVE_SERVER_REQUESTS reactive HTTP requests} observations. *

    This context also extends {@link RequestReplyReceiverContext} for propagating - * tracing information with the HTTP server exchange. + * tracing information during HTTP request processing. * * @author Brian Clozel * @since 6.0 */ public class ServerRequestObservationContext extends RequestReplyReceiverContext { - private final ServerWebExchange serverWebExchange; + private final Map attributes; @Nullable - private PathPattern pathPattern; + private String pathPattern; private boolean connectionAborted; - public ServerRequestObservationContext(ServerWebExchange exchange) { - super((request, key) -> request.getHeaders().getFirst(key)); - this.serverWebExchange = exchange; - setCarrier(exchange.getRequest()); - setResponse(exchange.getResponse()); + public ServerRequestObservationContext(ServerHttpRequest request, ServerHttpResponse response, Map attributes) { + super((req, key) -> req.getHeaders().getFirst(key)); + setCarrier(request); + setResponse(response); + this.attributes = Collections.unmodifiableMap(attributes); } /** - * Return the current {@link ServerWebExchange HTTP exchange}. + * Return an immutable map of the current request attributes. */ - public ServerWebExchange getServerWebExchange() { - return this.serverWebExchange; + public Map getAttributes() { + return this.attributes; } /** @@ -63,7 +64,7 @@ public ServerWebExchange getServerWebExchange() { * @return the path pattern, or {@code null} if none found */ @Nullable - public PathPattern getPathPattern() { + public String getPathPattern() { return this.pathPattern; } @@ -72,7 +73,7 @@ public PathPattern getPathPattern() { *

    Path patterns must have a low cardinality for the entire application. * @param pathPattern the path pattern, for example {@code "/projects/{name}"}. */ - public void setPathPattern(@Nullable PathPattern pathPattern) { + public void setPathPattern(@Nullable String pathPattern) { this.pathPattern = pathPattern; } diff --git a/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationConvention.java b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerRequestObservationConvention.java similarity index 88% rename from spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationConvention.java rename to spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerRequestObservationConvention.java index 6c7ed8f1330f..d42e025d37fa 100644 --- a/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationConvention.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerRequestObservationConvention.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.http.observation.reactive; +package org.springframework.http.server.reactive.observation; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationConvention; /** - * Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_REACTIVE_SERVER_EXCHANGES reactive HTTP exchanges}. + * Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_REACTIVE_SERVER_REQUESTS reactive HTTP requests}. * * @author Brian Clozel * @since 6.0 diff --git a/spring-web/src/main/java/org/springframework/http/observation/reactive/package-info.java b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/package-info.java similarity index 63% rename from spring-web/src/main/java/org/springframework/http/observation/reactive/package-info.java rename to spring-web/src/main/java/org/springframework/http/server/reactive/observation/package-info.java index 473d6d16164a..d00ac21e8291 100644 --- a/spring-web/src/main/java/org/springframework/http/observation/reactive/package-info.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/package-info.java @@ -1,9 +1,9 @@ /** - * Instrumentation for {@link io.micrometer.observation.Observation observing} reactive HTTP applications. + * Instrumentation for {@link io.micrometer.observation.Observation observing} reactive HTTP server applications. */ @NonNullApi @NonNullFields -package org.springframework.http.observation.reactive; +package org.springframework.http.server.reactive.observation; import org.springframework.lang.NonNullApi; import org.springframework.lang.NonNullFields; diff --git a/spring-web/src/main/java/org/springframework/web/DefaultErrorResponseBuilder.java b/spring-web/src/main/java/org/springframework/web/DefaultErrorResponseBuilder.java new file mode 100644 index 000000000000..58c30a8e7a59 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/DefaultErrorResponseBuilder.java @@ -0,0 +1,209 @@ +/* + * Copyright 2002-2022 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.web; + +import java.net.URI; +import java.util.function.Consumer; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ProblemDetail; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + + +/** + * Default implementation of {@link ErrorResponse.Builder}. + * + * @author Rossen Stoyanchev + * @since 6.0 + */ +final class DefaultErrorResponseBuilder implements ErrorResponse.Builder { + + private final Throwable exception; + + private final HttpStatusCode statusCode; + + @Nullable + private HttpHeaders headers; + + private final ProblemDetail problemDetail; + + private String detailMessageCode; + + @Nullable + private Object[] detailMessageArguments; + + private String titleMessageCode; + + + DefaultErrorResponseBuilder(Throwable ex, HttpStatusCode statusCode, String detail) { + Assert.notNull(ex, "Throwable is required"); + Assert.notNull(statusCode, "HttpStatusCode is required"); + Assert.notNull(detail, "`detail` is required"); + this.exception = ex; + this.statusCode = statusCode; + this.problemDetail = ProblemDetail.forStatusAndDetail(statusCode, detail); + this.detailMessageCode = ErrorResponse.getDefaultDetailMessageCode(ex.getClass(), null); + this.titleMessageCode = ErrorResponse.getDefaultTitleMessageCode(ex.getClass()); + } + + + @Override + public ErrorResponse.Builder header(String headerName, String... headerValues) { + this.headers = (this.headers != null ? this.headers : new HttpHeaders()); + for (String headerValue : headerValues) { + this.headers.add(headerName, headerValue); + } + return this; + } + + @Override + public ErrorResponse.Builder headers(Consumer headersConsumer) { + return this; + } + + @Override + public ErrorResponse.Builder detail(String detail) { + this.problemDetail.setDetail(detail); + return this; + } + + @Override + public ErrorResponse.Builder detailMessageCode(String messageCode) { + Assert.notNull(messageCode, "`detailMessageCode` is required"); + this.detailMessageCode = messageCode; + return this; + } + + @Override + public ErrorResponse.Builder detailMessageArguments(Object... messageArguments) { + this.detailMessageArguments = messageArguments; + return this; + } + + @Override + public ErrorResponse.Builder type(URI type) { + this.problemDetail.setType(type); + return this; + } + + @Override + public ErrorResponse.Builder title(@Nullable String title) { + this.problemDetail.setTitle(title); + return this; + } + + @Override + public ErrorResponse.Builder titleMessageCode(String messageCode) { + Assert.notNull(messageCode, "`titleMessageCode` is required"); + this.titleMessageCode = messageCode; + return this; + } + + @Override + public ErrorResponse.Builder instance(@Nullable URI instance) { + this.problemDetail.setInstance(instance); + return this; + } + + @Override + public ErrorResponse.Builder property(String name, Object value) { + this.problemDetail.setProperty(name, value); + return this; + } + + @Override + public ErrorResponse build() { + return new SimpleErrorResponse( + this.exception, this.statusCode, this.headers, this.problemDetail, + this.detailMessageCode, this.detailMessageArguments, this.titleMessageCode); + } + + + /** + * Simple container for {@code ErrorResponse} values. + */ + private static class SimpleErrorResponse implements ErrorResponse { + + private final Throwable exception; + + private final HttpStatusCode statusCode; + + private final HttpHeaders headers; + + private final ProblemDetail problemDetail; + + private final String detailMessageCode; + + @Nullable + private final Object[] detailMessageArguments; + + private final String titleMessageCode; + + SimpleErrorResponse( + Throwable ex, HttpStatusCode statusCode, @Nullable HttpHeaders headers, ProblemDetail problemDetail, + String detailMessageCode, @Nullable Object[] detailMessageArguments, String titleMessageCode) { + + this.exception = ex; + this.statusCode = statusCode; + this.headers = (headers != null ? headers : HttpHeaders.EMPTY); + this.problemDetail = problemDetail; + this.detailMessageCode = detailMessageCode; + this.detailMessageArguments = detailMessageArguments; + this.titleMessageCode = titleMessageCode; + } + + @Override + public HttpStatusCode getStatusCode() { + return this.statusCode; + } + + @Override + public HttpHeaders getHeaders() { + return this.headers; + } + + @Override + public ProblemDetail getBody() { + return this.problemDetail; + } + + @Override + public String getDetailMessageCode() { + return this.detailMessageCode; + } + + @Override + public Object[] getDetailMessageArguments() { + return this.detailMessageArguments; + } + + @Override + public String getTitleMessageCode() { + return this.titleMessageCode; + } + + @Override + public String toString() { + return "ErrorResponse{status=" + this.statusCode + ", " + + "headers=" + this.headers + ", body=" + this.problemDetail + ", " + + "exception=" + this.exception + "}"; + } + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/ErrorResponse.java b/spring-web/src/main/java/org/springframework/web/ErrorResponse.java index 3f239de6e264..0b3c200292da 100644 --- a/spring-web/src/main/java/org/springframework/web/ErrorResponse.java +++ b/spring-web/src/main/java/org/springframework/web/ErrorResponse.java @@ -16,7 +16,9 @@ package org.springframework.web; +import java.net.URI; import java.util.Locale; +import java.util.function.Consumer; import org.springframework.context.MessageSource; import org.springframework.http.HttpHeaders; @@ -24,7 +26,6 @@ import org.springframework.http.ProblemDetail; import org.springframework.lang.Nullable; - /** * Representation of a complete RFC 7807 error response including status, * headers, and an RFC 7807 formatted {@link ProblemDetail} body. Allows any @@ -87,7 +88,11 @@ default Object[] getDetailMessageArguments() { /** * Variant of {@link #getDetailMessageArguments()} that uses the given * {@link MessageSource} for resolving the message argument values. - * This is useful for example to message codes from validation errors. + *

    This is useful for example to expand message codes from validation errors. + *

    The default implementation delegates to {@link #getDetailMessageArguments()}, + * ignoring the supplied {@code MessageSource} and {@code Locale}. + * @param messageSource the {@code MessageSource} to use for the lookup + * @param locale the {@code Locale} to use for the lookup */ @Nullable default Object[] getDetailMessageArguments(MessageSource messageSource, Locale locale) { @@ -95,10 +100,10 @@ default Object[] getDetailMessageArguments(MessageSource messageSource, Locale l } /** - * Return a code to use to resolve the problem "detail" for this exception + * Return a code to use to resolve the problem "title" for this exception * through a {@link MessageSource}. *

    By default this is initialized via - * {@link #getDefaultDetailMessageCode(Class, String)}. + * {@link #getDefaultTitleMessageCode(Class, String)}. */ default String getTitleMessageCode() { return getDefaultTitleMessageCode(getClass()); @@ -106,8 +111,8 @@ default String getTitleMessageCode() { /** * Resolve the {@link #getDetailMessageCode() detailMessageCode} and the - * {@link #getTitleMessageCode() titleCode} through the given - * {@link MessageSource}, and if found, update the "detail" and "title!" + * {@link #getTitleMessageCode() titleMessageCode} through the given + * {@link MessageSource}, and if found, update the "detail" and "title" * fields respectively. * @param messageSource the {@code MessageSource} to use for the lookup * @param locale the {@code Locale} to use for the lookup @@ -132,8 +137,9 @@ default ProblemDetail updateAndGetBody(@Nullable MessageSource messageSource, Lo * Build a message code for the "detail" field, for the given exception type. * @param exceptionType the exception type associated with the problem * @param suffix an optional suffix, e.g. for exceptions that may have multiple - * error message with different arguments. - * @return {@code "problemDetail."} followed by the full {@link Class#getName() class name} + * error message with different arguments + * @return {@code "problemDetail."} followed by the fully qualified + * {@link Class#getName() class name} and an optional suffix */ static String getDefaultDetailMessageCode(Class exceptionType, @Nullable String suffix) { return "problemDetail." + exceptionType.getName() + (suffix != null ? "." + suffix : ""); @@ -142,41 +148,143 @@ static String getDefaultDetailMessageCode(Class exceptionType, @Nullable Stri /** * Build a message code for the "title" field, for the given exception type. * @param exceptionType the exception type associated with the problem - * @return {@code "problemDetail.title."} followed by the full {@link Class#getName() class name} + * @return {@code "problemDetail.title."} followed by the fully qualified + * {@link Class#getName() class name} */ static String getDefaultTitleMessageCode(Class exceptionType) { return "problemDetail.title." + exceptionType.getName(); } /** - * Map the given Exception to an {@link ErrorResponse}. - * @param ex the Exception, mostly to derive message codes, if not provided - * @param status the response status to use - * @param headers optional headers to add to the response - * @param defaultDetail default value for the "detail" field - * @param detailMessageCode the code to use to look up the "detail" field - * through a {@code MessageSource}, falling back on - * {@link #getDefaultDetailMessageCode(Class, String)} - * @param detailMessageArguments the arguments to go with the detailMessageCode - * @return the created {@code ErrorResponse} instance + * Static factory method to build an instance via + * {@link #builder(Throwable, HttpStatusCode, String)}. + */ + static ErrorResponse create(Throwable ex, HttpStatusCode statusCode, String detail) { + return builder(ex, statusCode, detail).build(); + } + + /** + * Return a builder to create an {@code ErrorResponse} instance. + * @param ex the underlying exception that lead to the error response; + * mainly to derive default values for the + * {@linkplain #getDetailMessageCode() detail message code} and for the + * {@linkplain #getTitleMessageCode() title message code}. + * @param statusCode the status code to set in the response + * @param detail the default value for the + * {@link ProblemDetail#setDetail(String) detail} field, unless overridden + * by a {@link MessageSource} lookup with {@link #getDetailMessageCode()} */ - static ErrorResponse createFor( - Exception ex, HttpStatusCode status, @Nullable HttpHeaders headers, - String defaultDetail, @Nullable String detailMessageCode, @Nullable Object[] detailMessageArguments) { + static Builder builder(Throwable ex, HttpStatusCode statusCode, String detail) { + return new DefaultErrorResponseBuilder(ex, statusCode, detail); + } - if (detailMessageCode == null) { - detailMessageCode = ErrorResponse.getDefaultDetailMessageCode(ex.getClass(), null); - } - ErrorResponseException errorResponse = new ErrorResponseException( - status, ProblemDetail.forStatusAndDetail(status, defaultDetail), null, - detailMessageCode, detailMessageArguments); + /** + * Builder for an {@code ErrorResponse}. + */ + interface Builder { + + /** + * Add the given header value(s) under the given name. + * @param headerName the header name + * @param headerValues the header value(s) + * @return the same builder instance + * @see HttpHeaders#add(String, String) + */ + Builder header(String headerName, String... headerValues); + + /** + * Manipulate this response's headers with the given consumer. This is + * useful to {@linkplain HttpHeaders#set(String, String) overwrite} or + * {@linkplain HttpHeaders#remove(Object) remove} existing values, or + * use any other {@link HttpHeaders} methods. + * @param headersConsumer a function that consumes the {@code HttpHeaders} + * @return the same builder instance + */ + Builder headers(Consumer headersConsumer); + + /** + * Set the underlying {@link ProblemDetail#setDetail(String) detail}. + * @return the same builder instance + */ + Builder detail(String detail); + + /** + * Customize the {@link MessageSource} code for looking up the value for + * the underlying {@link #detail(String) detail}. + *

    By default, this is set to + * {@link ErrorResponse#getDefaultDetailMessageCode(Class, String)} with the + * associated Exception type. + * @param messageCode the message code to use + * @return the same builder instance + * @see ErrorResponse#getDetailMessageCode() + */ + Builder detailMessageCode(String messageCode); + + /** + * Set the arguments to provide to the {@link MessageSource} lookup for + * {@link #detailMessageCode(String)}. + * @param messageArguments the arguments to provide + * @return the same builder instance + * @see ErrorResponse#getDetailMessageArguments() + */ + Builder detailMessageArguments(Object... messageArguments); + + /** + * Set the underlying {@link ProblemDetail#setType(URI) type} field. + * @return the same builder instance + */ + Builder type(URI type); + + /** + * Set the underlying {@link ProblemDetail#setTitle(String) title} field. + * @return the same builder instance + */ + Builder title(@Nullable String title); + + /** + * Customize the {@link MessageSource} code for looking up the value for + * the underlying {@link ProblemDetail#setTitle(String) title}. + *

    By default, set via + * {@link ErrorResponse#getDefaultTitleMessageCode(Class)} with the + * associated Exception type. + * @param messageCode the message code to use + * @return the same builder instance + * @see ErrorResponse#getTitleMessageCode() + */ + Builder titleMessageCode(String messageCode); + + /** + * Set the underlying {@link ProblemDetail#setInstance(URI) instance} field. + * @return the same builder instance + */ + Builder instance(@Nullable URI instance); + + /** + * Set a "dynamic" {@link ProblemDetail#setProperty(String, Object) + * property} on the underlying {@code ProblemDetail}. + * @return the same builder instance + */ + Builder property(String name, Object value); + + /** + * Build the {@code ErrorResponse} instance. + */ + ErrorResponse build(); - if (headers != null) { - errorResponse.getHeaders().putAll(headers); + /** + * Build the {@code ErrorResponse} instance and also resolve the "detail" + * and "title" through the given {@link MessageSource}. Effectively a + * shortcut for calling {@link #build()} and then + * {@link ErrorResponse#updateAndGetBody(MessageSource, Locale)}. + * @since 6.0.3 + */ + default ErrorResponse build(@Nullable MessageSource messageSource, Locale locale) { + ErrorResponse response = build(); + response.updateAndGetBody(messageSource, locale); + return response; } - return errorResponse; } } diff --git a/spring-web/src/main/java/org/springframework/web/ErrorResponseException.java b/spring-web/src/main/java/org/springframework/web/ErrorResponseException.java index 96f35b4b7b8c..6e6a8165ddf6 100644 --- a/spring-web/src/main/java/org/springframework/web/ErrorResponseException.java +++ b/spring-web/src/main/java/org/springframework/web/ErrorResponseException.java @@ -53,14 +53,14 @@ public class ErrorResponseException extends NestedRuntimeException implements Er /** - * Constructor with a {@link HttpStatusCode}. + * Constructor with an {@link HttpStatusCode}. */ public ErrorResponseException(HttpStatusCode status) { this(status, null); } /** - * Constructor with a {@link HttpStatusCode} and an optional cause. + * Constructor with an {@link HttpStatusCode} and an optional cause. */ public ErrorResponseException(HttpStatusCode status, @Nullable Throwable cause) { this(status, ProblemDetail.forStatus(status), cause); diff --git a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java index 5f3b76d7a488..b4eb5c6cd8d4 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -74,8 +74,8 @@ public ContentNegotiationManager(Collection strategi Assert.notEmpty(strategies, "At least one ContentNegotiationStrategy is expected"); this.strategies.addAll(strategies); for (ContentNegotiationStrategy strategy : this.strategies) { - if (strategy instanceof MediaTypeFileExtensionResolver) { - this.resolvers.add((MediaTypeFileExtensionResolver) strategy); + if (strategy instanceof MediaTypeFileExtensionResolver mediaTypeFileExtensionResolver) { + this.resolvers.add(mediaTypeFileExtensionResolver); } } } @@ -180,8 +180,8 @@ private List doResolveExtensions(Function getMediaTypeMappings() { Map result = null; for (MediaTypeFileExtensionResolver resolver : this.resolvers) { - if (resolver instanceof MappingMediaTypeFileExtensionResolver) { - Map map = ((MappingMediaTypeFileExtensionResolver) resolver).getMediaTypes(); + if (resolver instanceof MappingMediaTypeFileExtensionResolver mappingResolver) { + Map map = mappingResolver.getMediaTypes(); if (CollectionUtils.isEmpty(map)) { continue; } 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 66c27ac32a91..cafd60a4e86b 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 @@ -17,8 +17,10 @@ package org.springframework.web.bind; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.function.Function; import org.springframework.context.MessageSource; @@ -26,6 +28,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.ProblemDetail; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; @@ -96,20 +99,41 @@ public String getMessage() { @Override public Object[] getDetailMessageArguments() { - return new Object[] { - errorsToStringList(getBindingResult().getGlobalErrors()), - errorsToStringList(getBindingResult().getFieldErrors()) - }; + return new Object[] {errorsToStringList(getGlobalErrors()), errorsToStringList(getFieldErrors())}; } @Override public Object[] getDetailMessageArguments(MessageSource messageSource, Locale locale) { return new Object[] { - errorsToStringList(getBindingResult().getGlobalErrors(), messageSource, locale), - errorsToStringList(getBindingResult().getFieldErrors(), messageSource, locale) + errorsToStringList(getGlobalErrors(), messageSource, locale), + errorsToStringList(getFieldErrors(), messageSource, locale) }; } + /** + * Resolve global and field errors to messages with the given + * {@link MessageSource} and {@link Locale}. + * @return a Map with errors as key and resolves messages as value + * @since 6.0.3 + */ + public Map resolveErrorMessages(MessageSource messageSource, Locale locale) { + Map map = new LinkedHashMap<>(); + addMessages(map, getGlobalErrors(), messageSource, locale); + addMessages(map, getFieldErrors(), messageSource, locale); + return map; + } + + private static void addMessages( + Map map, List errors, + MessageSource messageSource, Locale locale) { + + List messages = errorsToStringList(errors, messageSource, locale); + for (int i = 0; i < errors.size(); i++) { + map.put(errors.get(i), messages.get(i)); + } + } + + /** * Convert each given {@link ObjectError} to a String in single quotes, taking * either the error's default message, or its error code. @@ -126,12 +150,12 @@ public static List errorsToStringList(List errors * back on the error's default message. * @since 6.0 */ - @SuppressWarnings("ConstantConditions") public static List errorsToStringList( - List errors, MessageSource source, Locale locale) { + List errors, @Nullable MessageSource source, Locale locale) { - return errorsToStringList(errors, error -> source.getMessage( - error.getCode(), error.getArguments(), error.getDefaultMessage(), locale)); + return (source != null ? + errorsToStringList(errors, error -> source.getMessage(error, locale)) : + errorsToStringList(errors)); } private static List errorsToStringList( diff --git a/spring-web/src/main/java/org/springframework/web/bind/UnsatisfiedServletRequestParameterException.java b/spring-web/src/main/java/org/springframework/web/bind/UnsatisfiedServletRequestParameterException.java index 203a9c6c033c..e859c55db37a 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/UnsatisfiedServletRequestParameterException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/UnsatisfiedServletRequestParameterException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessor.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessor.java index 878f3af049ef..fede43701501 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessor.java @@ -33,12 +33,13 @@ /** * {@link ReflectiveProcessor} implementation for {@link Controller} and - * controller-specific annotated methods. On top of registering reflection + * controller-specific annotated methods. In addition to registering reflection * hints for invoking the annotated method, this implementation handles: + * *

      - *
    • Return types annotated with {@link ResponseBody}.
    • - *
    • Parameters annotated with {@link RequestBody}.
    • - *
    • {@link HttpEntity} return type and parameters.
    • + *
    • Return types annotated with {@link ResponseBody}
    • + *
    • Parameters annotated with {@link RequestBody}
    • + *
    • {@link HttpEntity} return types and parameters
    • *
    * * @author Stephane Nicoll @@ -49,6 +50,7 @@ class ControllerMappingReflectiveProcessor implements ReflectiveProcessor { private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + @Override public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) { if (element instanceof Class type) { @@ -98,8 +100,8 @@ else if (HttpEntity.class.isAssignableFrom(returnTypeParameter.getParameterType( @Nullable private Type getHttpEntityType(MethodParameter parameter) { MethodParameter nestedParameter = parameter.nested(); - return (nestedParameter.getNestedParameterType() == nestedParameter.getParameterType() - ? null : nestedParameter.getNestedParameterType()); + return (nestedParameter.getNestedParameterType() == nestedParameter.getParameterType() ? + null : nestedParameter.getNestedParameterType()); } } diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandlerReflectiveProcessor.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandlerReflectiveProcessor.java index 448392ea7592..63be56959128 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandlerReflectiveProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandlerReflectiveProcessor.java @@ -37,4 +37,5 @@ protected void registerReturnTypeHints(ReflectionHints hints, MethodParameter re } super.registerReturnTypeHints(hints, returnTypeParameter); } + } diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/InitBinder.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/InitBinder.java index 370b2f2801e0..c262653d87af 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/InitBinder.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/InitBinder.java @@ -22,6 +22,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; + /** * Annotation that identifies methods that initialize the * {@link org.springframework.web.bind.WebDataBinder} which @@ -47,6 +49,7 @@ * or {@link java.util.Locale}, allowing to register context-specific editors. * * @author Juergen Hoeller + * @author Sebastien Deleuze * @since 2.5 * @see ControllerAdvice * @see org.springframework.web.bind.WebDataBinder @@ -55,6 +58,7 @@ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented +@Reflective public @interface InitBinder { /** diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java index 3316065a0760..fa3893153850 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java @@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; import org.springframework.core.annotation.AliasFor; import org.springframework.ui.Model; @@ -60,12 +61,14 @@ * * @author Juergen Hoeller * @author Rossen Stoyanchev + * @author Sebastien Deleuze * @since 2.5 * @see ControllerAdvice */ @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented +@Reflective public @interface ModelAttribute { /** diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeBindException.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeBindException.java index 444266853515..1c1adcac7257 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeBindException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeBindException.java @@ -17,6 +17,7 @@ package org.springframework.web.bind.support; import java.beans.PropertyEditor; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -292,8 +293,8 @@ public String getMessage() { StringBuilder sb = new StringBuilder("Validation failed for argument at index ") .append(parameter.getParameterIndex()).append(" in method: ") .append(parameter.getExecutable().toGenericString()) - .append(", with ").append(this.bindingResult.getErrorCount()).append(" error(s): "); - for (ObjectError error : this.bindingResult.getAllErrors()) { + .append(", with ").append(getErrorCount()).append(" error(s): "); + for (ObjectError error : getAllErrors()) { sb.append('[').append(error).append("] "); } return sb.toString(); @@ -302,11 +303,35 @@ public String getMessage() { @Override public Object[] getDetailMessageArguments(MessageSource source, Locale locale) { return new Object[] { - MethodArgumentNotValidException.errorsToStringList(this.bindingResult.getGlobalErrors(), source, locale), - MethodArgumentNotValidException.errorsToStringList(this.bindingResult.getFieldErrors(), source, locale) + MethodArgumentNotValidException.errorsToStringList(getGlobalErrors(), source, locale), + MethodArgumentNotValidException.errorsToStringList(getFieldErrors(), source, locale) }; } + /** + * Resolve global and field errors to messages with the given + * {@link MessageSource} and {@link Locale}. + * @return a Map with errors as key and resolves messages as value + * @since 6.0.3 + */ + public Map resolveErrorMessages(MessageSource messageSource, Locale locale) { + Map map = new LinkedHashMap<>(); + addMessages(map, getGlobalErrors(), messageSource, locale); + addMessages(map, getFieldErrors(), messageSource, locale); + return map; + } + + private static void addMessages( + Map map, List errors, + MessageSource messageSource, Locale locale) { + + List messages = MethodArgumentNotValidException.errorsToStringList(errors, messageSource, locale); + for (int i = 0; i < errors.size(); i++) { + map.put(errors.get(i), messages.get(i)); + } + } + + @Override public boolean equals(@Nullable Object other) { return (this == other || this.bindingResult.equals(other)); diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java index fb57980f97cb..75d0894dbb83 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java +++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java @@ -122,7 +122,7 @@ public static Mono> extractValuesToBind(ServerWebExchange ex protected static void addBindValue(Map params, String key, List values) { if (!CollectionUtils.isEmpty(values)) { values = values.stream() - .map(value -> value instanceof FormFieldPart ? ((FormFieldPart) value).value() : value) + .map(value -> value instanceof FormFieldPart formFieldPart ? formFieldPart.value() : value) .toList(); params.put(key, values.size() == 1 ? values.get(0) : values); } diff --git a/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java index 72cd840a3035..4357be4d2722 100644 --- a/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -85,6 +85,7 @@ public ExtractingResponseErrorHandler(List> messageConve /** * Set the message converters to use by this extractor. */ + @Override public void setMessageConverters(List> messageConverters) { this.messageConverters = messageConverters; } diff --git a/spring-web/src/main/java/org/springframework/web/client/HttpMessageConverterExtractor.java b/spring-web/src/main/java/org/springframework/web/client/HttpMessageConverterExtractor.java index cbc47a272872..7b339969f24d 100644 --- a/spring-web/src/main/java/org/springframework/web/client/HttpMessageConverterExtractor.java +++ b/spring-web/src/main/java/org/springframework/web/client/HttpMessageConverterExtractor.java @@ -71,13 +71,13 @@ public HttpMessageConverterExtractor(Type responseType, List> messageConverters, Log logger) { Assert.notNull(responseType, "'responseType' must not be null"); Assert.notEmpty(messageConverters, "'messageConverters' must not be empty"); Assert.noNullElements(messageConverters, "'messageConverters' must not contain null elements"); this.responseType = responseType; - this.responseClass = (responseType instanceof Class ? (Class) responseType : null); + this.responseClass = (responseType instanceof Class clazz ? clazz : null); this.messageConverters = messageConverters; this.logger = logger; } @@ -94,9 +94,7 @@ public T extractData(ClientHttpResponse response) throws IOException { try { for (HttpMessageConverter messageConverter : this.messageConverters) { - if (messageConverter instanceof GenericHttpMessageConverter) { - GenericHttpMessageConverter genericMessageConverter = - (GenericHttpMessageConverter) messageConverter; + if (messageConverter instanceof GenericHttpMessageConverter genericMessageConverter) { if (genericMessageConverter.canRead(this.responseType, null, contentType)) { if (logger.isDebugEnabled()) { ResolvableType resolvableType = ResolvableType.forType(this.responseType); diff --git a/spring-web/src/main/java/org/springframework/web/client/RestOperations.java b/spring-web/src/main/java/org/springframework/web/client/RestOperations.java index 214b19b2a0dc..f25c4f2f8953 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestOperations.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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,7 +30,8 @@ /** * Interface specifying a basic set of RESTful operations. - * Implemented by {@link RestTemplate}. Not often used directly, but a useful + * + *

    Implemented by {@link RestTemplate}. Not often used directly, but a useful * option to enhance testability, as it can easily be mocked or stubbed. * * @author Arjen Poutsma @@ -67,7 +68,7 @@ public interface RestOperations { T getForObject(String url, Class responseType, Map uriVariables) throws RestClientException; /** - * Retrieve a representation by doing a GET on the URL . + * Retrieve a representation by doing a GET on the URL. * The response (if any) is converted and returned. * @param url the URL * @param responseType the type of the return value @@ -78,7 +79,7 @@ public interface RestOperations { /** * Retrieve an entity by doing a GET on the specified URL. - * The response is converted and stored in an {@link ResponseEntity}. + * The response is converted and stored in a {@link ResponseEntity}. *

    URI Template variables are expanded using the given URI variables, if any. * @param url the URL * @param responseType the type of the return value @@ -91,7 +92,7 @@ ResponseEntity getForEntity(String url, Class responseType, Object... /** * Retrieve a representation by doing a GET on the URI template. - * The response is converted and stored in an {@link ResponseEntity}. + * The response is converted and stored in a {@link ResponseEntity}. *

    URI Template variables are expanded using the given map. * @param url the URL * @param responseType the type of the return value @@ -103,8 +104,8 @@ ResponseEntity getForEntity(String url, Class responseType, Map ResponseEntity exchange(URI url, HttpMethod method, @Nullable HttpEntity< *

     	 * MyRequest body = ...
     	 * RequestEntity request = RequestEntity
    -	 *     .post(new URI("https://example.com/foo"))
    +	 *     .post(URI.create("https://example.com/foo"))
     	 *     .accept(MediaType.APPLICATION_JSON)
     	 *     .body(body);
     	 * ResponseEntity<MyResponse> response = template.exchange(request, MyResponse.class);
    @@ -631,7 +632,7 @@  ResponseEntity exchange(RequestEntity requestEntity, Class response
     	 * 
     	 * MyRequest body = ...
     	 * RequestEntity request = RequestEntity
    -	 *     .post(new URI("https://example.com/foo"))
    +	 *     .post(URI.create("https://example.com/foo"))
     	 *     .accept(MediaType.APPLICATION_JSON)
     	 *     .body(body);
     	 * ParameterizedTypeReference<List<MyResponse>> myBean =
    diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java
    index 74f198aba318..b1de2311feff 100644
    --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java
    +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java
    @@ -66,7 +66,6 @@
     import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
     import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
     import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
    -import org.springframework.http.converter.xml.SourceHttpMessageConverter;
     import org.springframework.lang.Nullable;
     import org.springframework.util.Assert;
     import org.springframework.util.ClassUtils;
    @@ -78,11 +77,17 @@
     /**
      * Synchronous client to perform HTTP requests, exposing a simple, template
      * method API over underlying HTTP client libraries such as the JDK
    - * {@code HttpURLConnection}, Apache HttpComponents, and others.
    + * {@code HttpURLConnection}, Apache HttpComponents, and others. RestTemplate
    + * offers templates for common scenarios by HTTP method, in addition to the
    + * generalized {@code exchange} and {@code execute} methods that support
    + * less frequent cases.
      *
    - * 

    The RestTemplate offers templates for common scenarios by HTTP method, in - * addition to the generalized {@code exchange} and {@code execute} methods that - * support of less frequent cases. + *

    RestTemplate is typically used as a shared component. However, its + * configuration does not support concurrent modification, and as such its + * configuration is typically prepared on startup. If necessary, you can create + * multiple, differently configured RestTemplate instances on startup. Such + * instances may use the same underlying {@link ClientHttpRequestFactory} + * if they need to share HTTP client resources. * *

    NOTE: As of 5.0 this class is in maintenance mode, with * only minor requests for changes and bugs to be accepted going forward. Please, @@ -167,13 +172,6 @@ public RestTemplate() { this.messageConverters.add(new StringHttpMessageConverter()); this.messageConverters.add(new ResourceHttpMessageConverter(false)); - try { - this.messageConverters.add(new SourceHttpMessageConverter<>()); - } - catch (Error err) { - // Ignore when no TransformerFactory implementation is available - } - this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { @@ -315,8 +313,8 @@ public ResponseErrorHandler getErrorHandler() { * @since 4.3 */ public void setDefaultUriVariables(Map uriVars) { - if (this.uriTemplateHandler instanceof DefaultUriBuilderFactory) { - ((DefaultUriBuilderFactory) this.uriTemplateHandler).setDefaultUriVariables(uriVars); + if (this.uriTemplateHandler instanceof DefaultUriBuilderFactory factory) { + factory.setDefaultUriVariables(uriVars); } else { throw new IllegalArgumentException( @@ -717,8 +715,7 @@ public ResponseEntity exchange(RequestEntity entity, ParameterizedType } private URI resolveUrl(RequestEntity entity) { - if (entity instanceof RequestEntity.UriTemplateRequestEntity) { - RequestEntity.UriTemplateRequestEntity ext = (RequestEntity.UriTemplateRequestEntity) entity; + if (entity instanceof RequestEntity.UriTemplateRequestEntity ext) { if (ext.getVars() != null) { return this.uriTemplateHandler.expand(ext.getUriTemplate(), ext.getVars()); } @@ -997,19 +994,18 @@ public void doWithRequest(ClientHttpRequest request) throws IOException { } private boolean canReadResponse(Type responseType, HttpMessageConverter converter) { - Class responseClass = (responseType instanceof Class ? (Class) responseType : null); + Class responseClass = (responseType instanceof Class clazz ? clazz : null); if (responseClass != null) { return converter.canRead(responseClass, null); } - else if (converter instanceof GenericHttpMessageConverter) { - GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter; + else if (converter instanceof GenericHttpMessageConverter genericConverter) { return genericConverter.canRead(responseType, null, null); } return false; } private Stream getSupportedMediaTypes(Type type, HttpMessageConverter converter) { - Type rawType = (type instanceof ParameterizedType ? ((ParameterizedType) type).getRawType() : type); + Type rawType = (type instanceof ParameterizedType parameterizedType ? parameterizedType.getRawType() : type); Class clazz = (rawType instanceof Class ? (Class) rawType : null); return (clazz != null ? converter.getSupportedMediaTypes(clazz) : converter.getSupportedMediaTypes()) .stream() @@ -1036,8 +1032,8 @@ public HttpEntityRequestCallback(@Nullable Object requestBody) { public HttpEntityRequestCallback(@Nullable Object requestBody, @Nullable Type responseType) { super(responseType); - if (requestBody instanceof HttpEntity) { - this.requestEntity = (HttpEntity) requestBody; + if (requestBody instanceof HttpEntity httpEntity) { + this.requestEntity = httpEntity; } else if (requestBody != null) { this.requestEntity = new HttpEntity<>(requestBody); @@ -1048,7 +1044,7 @@ else if (requestBody != null) { } @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) public void doWithRequest(ClientHttpRequest httpRequest) throws IOException { super.doWithRequest(httpRequest); Object requestBody = this.requestEntity.getBody(); @@ -1064,15 +1060,15 @@ public void doWithRequest(ClientHttpRequest httpRequest) throws IOException { } else { Class requestBodyClass = requestBody.getClass(); - Type requestBodyType = (this.requestEntity instanceof RequestEntity ? - ((RequestEntity)this.requestEntity).getType() : requestBodyClass); + // The following pattern variable cannot be named "requestEntity" due to lacking + // support in Checkstyle: https://github.com/checkstyle/checkstyle/issues/10969 + Type requestBodyType = (this.requestEntity instanceof RequestEntity _requestEntity ? + _requestEntity.getType() : requestBodyClass); HttpHeaders httpHeaders = httpRequest.getHeaders(); HttpHeaders requestHeaders = this.requestEntity.getHeaders(); MediaType requestContentType = requestHeaders.getContentType(); for (HttpMessageConverter messageConverter : getMessageConverters()) { - if (messageConverter instanceof GenericHttpMessageConverter) { - GenericHttpMessageConverter genericConverter = - (GenericHttpMessageConverter) messageConverter; + if (messageConverter instanceof GenericHttpMessageConverter genericConverter) { if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values))); diff --git a/spring-web/src/main/java/org/springframework/web/client/support/RestGatewaySupport.java b/spring-web/src/main/java/org/springframework/web/client/support/RestGatewaySupport.java index a31c4d911a80..adf6b470befa 100644 --- a/spring-web/src/main/java/org/springframework/web/client/support/RestGatewaySupport.java +++ b/spring-web/src/main/java/org/springframework/web/client/support/RestGatewaySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/web/context/ContextCleanupListener.java b/spring-web/src/main/java/org/springframework/web/context/ContextCleanupListener.java index 02034d0a19f7..e731ebd8775d 100644 --- a/spring-web/src/main/java/org/springframework/web/context/ContextCleanupListener.java +++ b/spring-web/src/main/java/org/springframework/web/context/ContextCleanupListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -65,9 +65,9 @@ static void cleanupAttributes(ServletContext servletContext) { String attrName = attrNames.nextElement(); if (attrName.startsWith("org.springframework.")) { Object attrValue = servletContext.getAttribute(attrName); - if (attrValue instanceof DisposableBean) { + if (attrValue instanceof DisposableBean disposableBean) { try { - ((DisposableBean) attrValue).destroy(); + disposableBean.destroy(); } catch (Throwable ex) { if (logger.isWarnEnabled()) { diff --git a/spring-web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributesScope.java b/spring-web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributesScope.java index eb104e23543f..89dc05ac8c3c 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributesScope.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/AbstractRequestAttributesScope.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/web/context/request/RequestContextListener.java b/spring-web/src/main/java/org/springframework/web/context/request/RequestContextListener.java index d6d1e3c3e801..ecca2b235f6a 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/RequestContextListener.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/RequestContextListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2022 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. @@ -51,11 +51,10 @@ public class RequestContextListener implements ServletRequestListener { @Override public void requestInitialized(ServletRequestEvent requestEvent) { - if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) { + if (!(requestEvent.getServletRequest() instanceof HttpServletRequest request)) { throw new IllegalArgumentException( "Request is not an HttpServletRequest: " + requestEvent.getServletRequest()); } - HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest(); ServletRequestAttributes attributes = new ServletRequestAttributes(request); request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes); LocaleContextHolder.setLocale(request.getLocale()); @@ -66,16 +65,16 @@ public void requestInitialized(ServletRequestEvent requestEvent) { public void requestDestroyed(ServletRequestEvent requestEvent) { ServletRequestAttributes attributes = null; Object reqAttr = requestEvent.getServletRequest().getAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE); - if (reqAttr instanceof ServletRequestAttributes) { - attributes = (ServletRequestAttributes) reqAttr; + if (reqAttr instanceof ServletRequestAttributes servletRequestAttributes) { + attributes = servletRequestAttributes; } RequestAttributes threadAttributes = RequestContextHolder.getRequestAttributes(); if (threadAttributes != null) { // We're assumably within the original request thread... LocaleContextHolder.resetLocaleContext(); RequestContextHolder.resetRequestAttributes(); - if (attributes == null && threadAttributes instanceof ServletRequestAttributes) { - attributes = (ServletRequestAttributes) threadAttributes; + if (attributes == null && threadAttributes instanceof ServletRequestAttributes servletRequestAttributes) { + attributes = servletRequestAttributes; } } if (attributes != null) { diff --git a/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java index 0e070c3c806c..4d5eda127a08 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java @@ -19,12 +19,11 @@ import java.security.Principal; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; -import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -52,7 +51,7 @@ */ public class ServletWebRequest extends ServletRequestAttributes implements NativeWebRequest { - private static final List SAFE_METHODS = Arrays.asList("GET", "HEAD"); + private static final Set SAFE_METHODS = Set.of("GET", "HEAD"); /** * Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match". @@ -232,10 +231,10 @@ else if (validateIfUnmodifiedSince(lastModifiedTimestamp)) { } private boolean validateIfMatch(@Nullable String eTag) { - Enumeration ifMatchHeaders = getRequest().getHeaders(HttpHeaders.IF_MATCH); if (SAFE_METHODS.contains(getRequest().getMethod())) { return false; } + Enumeration ifMatchHeaders = getRequest().getHeaders(HttpHeaders.IF_MATCH); if (!ifMatchHeaders.hasMoreElements()) { return false; } @@ -309,7 +308,7 @@ private boolean eTagWeakMatch(@Nullable String first, @Nullable String second) { return first.equals(second); } - private void updateResponseStateChanging(String eTag, long lastModifiedTimestamp) { + private void updateResponseStateChanging(@Nullable String eTag, long lastModifiedTimestamp) { if (this.notModified && getResponse() != null) { getResponse().setStatus(HttpStatus.PRECONDITION_FAILED.value()); } @@ -330,20 +329,18 @@ private boolean validateIfUnmodifiedSince(long lastModifiedTimestamp) { return true; } - private boolean validateIfModifiedSince(long lastModifiedTimestamp) { + private void validateIfModifiedSince(long lastModifiedTimestamp) { if (lastModifiedTimestamp < 0) { - return false; + return; } long ifModifiedSince = parseDateHeader(HttpHeaders.IF_MODIFIED_SINCE); - if (ifModifiedSince == -1) { - return false; + if (ifModifiedSince != -1) { + // We will perform this validation... + this.notModified = ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000); } - // We will perform this validation... - this.notModified = ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000); - return true; } - private void updateResponseIdempotent(String eTag, long lastModifiedTimestamp) { + private void updateResponseIdempotent(@Nullable String eTag, long lastModifiedTimestamp) { if (getResponse() != null) { boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod()); if (this.notModified) { @@ -354,7 +351,7 @@ private void updateResponseIdempotent(String eTag, long lastModifiedTimestamp) { } } - private void addCachingResponseHeaders(String eTag, long lastModifiedTimestamp) { + private void addCachingResponseHeaders(@Nullable String eTag, long lastModifiedTimestamp) { if (SAFE_METHODS.contains(getRequest().getMethod())) { if (lastModifiedTimestamp > 0 && parseDateValue(getResponse().getHeader(HttpHeaders.LAST_MODIFIED)) == -1) { getResponse().setDateHeader(HttpHeaders.LAST_MODIFIED, lastModifiedTimestamp); diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java index bc7c6ed76275..7833f0018e04 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -128,7 +128,7 @@ public void startAsync() { @Override public void dispatch() { - Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext"); + Assert.state(this.asyncContext != null, "Cannot dispatch without an AsyncContext"); this.asyncContext.dispatch(); } diff --git a/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java index da82e189b30e..a1a9e2737177 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -209,8 +209,8 @@ protected void onRefresh() { @Override protected void initPropertySources() { ConfigurableEnvironment env = getEnvironment(); - if (env instanceof ConfigurableWebEnvironment) { - ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, this.servletConfig); + if (env instanceof ConfigurableWebEnvironment configurableWebEnv) { + configurableWebEnv.initPropertySources(this.servletContext, this.servletConfig); } } diff --git a/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java index 87009a3ca5a3..577fd7d482c4 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java @@ -208,8 +208,8 @@ protected void onRefresh() { @Override protected void initPropertySources() { ConfigurableEnvironment env = getEnvironment(); - if (env instanceof ConfigurableWebEnvironment) { - ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, null); + if (env instanceof ConfigurableWebEnvironment configurableWebEnv) { + configurableWebEnv.initPropertySources(this.servletContext, null); } } diff --git a/spring-web/src/main/java/org/springframework/web/context/support/ServletContextAwareProcessor.java b/spring-web/src/main/java/org/springframework/web/context/support/ServletContextAwareProcessor.java index 239edb45f6ea..b4aa7b098462 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/ServletContextAwareProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/ServletContextAwareProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -104,11 +104,11 @@ protected ServletConfig getServletConfig() { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - if (getServletContext() != null && bean instanceof ServletContextAware) { - ((ServletContextAware) bean).setServletContext(getServletContext()); + if (getServletContext() != null && bean instanceof ServletContextAware servletContextAware) { + servletContextAware.setServletContext(getServletContext()); } - if (getServletConfig() != null && bean instanceof ServletConfigAware) { - ((ServletConfigAware) bean).setServletConfig(getServletConfig()); + if (getServletConfig() != null && bean instanceof ServletConfigAware servletConfigAware) { + servletConfigAware.setServletConfig(getServletConfig()); } return bean; } diff --git a/spring-web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java index d20ebf12748a..7906d8820bc5 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java b/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java index cf43e49a5300..deaee9050f80 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -114,19 +114,19 @@ public static WebApplicationContext getWebApplicationContext(ServletContext sc, if (attr == null) { return null; } - if (attr instanceof RuntimeException) { - throw (RuntimeException) attr; + if (attr instanceof RuntimeException runtimeException) { + throw runtimeException; } - if (attr instanceof Error) { - throw (Error) attr; + if (attr instanceof Error error) { + throw error; } - if (attr instanceof Exception) { - throw new IllegalStateException((Exception) attr); + if (attr instanceof Exception exception) { + throw new IllegalStateException(exception); } - if (!(attr instanceof WebApplicationContext)) { + if (!(attr instanceof WebApplicationContext wac)) { throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr); } - return (WebApplicationContext) attr; + return wac; } /** @@ -152,12 +152,12 @@ public static WebApplicationContext findWebApplicationContext(ServletContext sc) while (attrNames.hasMoreElements()) { String attrName = attrNames.nextElement(); Object attrValue = sc.getAttribute(attrName); - if (attrValue instanceof WebApplicationContext) { + if (attrValue instanceof WebApplicationContext currentWac) { if (wac != null) { throw new IllegalStateException("No unique WebApplicationContext found: more than one " + "DispatcherServlet registered with publishContext=true?"); } - wac = (WebApplicationContext) attrValue; + wac = currentWac; } } } @@ -311,10 +311,10 @@ public static void initServletPropertySources(MutablePropertySources sources, */ private static ServletRequestAttributes currentRequestAttributes() { RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); - if (!(requestAttr instanceof ServletRequestAttributes)) { + if (!(requestAttr instanceof ServletRequestAttributes servletRequestAttributes)) { throw new IllegalStateException("Current request is not a servlet request"); } - return (ServletRequestAttributes) requestAttr; + return servletRequestAttributes; } diff --git a/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationObjectSupport.java b/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationObjectSupport.java index 10d22569ccc9..b7035f711f01 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationObjectSupport.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/WebApplicationObjectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -76,8 +76,8 @@ protected boolean isContextRequired() { @Override protected void initApplicationContext(ApplicationContext context) { super.initApplicationContext(context); - if (this.servletContext == null && context instanceof WebApplicationContext) { - this.servletContext = ((WebApplicationContext) context).getServletContext(); + if (this.servletContext == null && context instanceof WebApplicationContext wac) { + this.servletContext = wac.getServletContext(); if (this.servletContext != null) { initServletContext(this.servletContext); } @@ -108,8 +108,8 @@ protected void initServletContext(ServletContext servletContext) { @Nullable protected final WebApplicationContext getWebApplicationContext() throws IllegalStateException { ApplicationContext ctx = getApplicationContext(); - if (ctx instanceof WebApplicationContext) { - return (WebApplicationContext) getApplicationContext(); + if (ctx instanceof WebApplicationContext wac) { + return wac; } else if (isContextRequired()) { throw new IllegalStateException("WebApplicationObjectSupport instance [" + this + diff --git a/spring-web/src/main/java/org/springframework/web/filter/RelativeRedirectResponseWrapper.java b/spring-web/src/main/java/org/springframework/web/filter/RelativeRedirectResponseWrapper.java index 9fb2b8dbf347..7f999bc95ac8 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/RelativeRedirectResponseWrapper.java +++ b/spring-web/src/main/java/org/springframework/web/filter/RelativeRedirectResponseWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java index 277d8e9872f5..3ca50f609b59 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java @@ -27,10 +27,11 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.observation.DefaultServerRequestObservationConvention; -import org.springframework.http.observation.ServerHttpObservationDocumentation; -import org.springframework.http.observation.ServerRequestObservationContext; -import org.springframework.http.observation.ServerRequestObservationConvention; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.observation.DefaultServerRequestObservationConvention; +import org.springframework.http.server.observation.ServerHttpObservationDocumentation; +import org.springframework.http.server.observation.ServerRequestObservationContext; +import org.springframework.http.server.observation.ServerRequestObservationConvention; import org.springframework.lang.Nullable; @@ -108,7 +109,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse filterChain.doFilter(request, response); } catch (Exception ex) { - observation.error(unwrapServletException(ex)).stop(); + observation.error(unwrapServletException(ex)); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); throw ex; } finally { @@ -127,7 +129,7 @@ private Observation createOrFetchObservation(HttpServletRequest request, HttpSer Observation observation = (Observation) request.getAttribute(CURRENT_OBSERVATION_ATTRIBUTE); if (observation == null) { ServerRequestObservationContext context = new ServerRequestObservationContext(request, response); - observation = ServerHttpObservationDocumentation.HTTP_SERVLET_SERVER_EXCHANGES.observation(this.observationConvention, + observation = ServerHttpObservationDocumentation.HTTP_SERVLET_SERVER_REQUESTS.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> context, this.observationRegistry).start(); request.setAttribute(CURRENT_OBSERVATION_ATTRIBUTE, observation); if (!observation.isNoop()) { diff --git a/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java b/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java index ce9e20abde73..ca75df7e7a15 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java @@ -21,14 +21,15 @@ import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; -import org.springframework.http.observation.reactive.DefaultServerRequestObservationConvention; -import org.springframework.http.observation.reactive.ServerHttpObservationDocumentation; -import org.springframework.http.observation.reactive.ServerRequestObservationContext; -import org.springframework.http.observation.reactive.ServerRequestObservationConvention; import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention; +import org.springframework.http.server.reactive.observation.ServerHttpObservationDocumentation; +import org.springframework.http.server.reactive.observation.ServerRequestObservationContext; +import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; @@ -59,11 +60,6 @@ public class ServerHttpObservationFilter implements WebFilter { private static final Set DISCONNECTED_CLIENT_EXCEPTIONS = Set.of("AbortedException", "ClientAbortException", "EOFException", "EofException"); - /** - * Aligned with ObservationThreadLocalAccessor#KEY from micrometer-core. - */ - private static final String MICROMETER_OBSERVATION_KEY = "micrometer.observation"; - private final ObservationRegistry observationRegistry; private final ServerRequestObservationConvention observationConvention; @@ -100,13 +96,14 @@ public static Optional findObservationContext(S @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange); + ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange.getRequest(), + exchange.getResponse(), exchange.getAttributes()); exchange.getAttributes().put(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext); return chain.filter(exchange).transformDeferred(call -> filter(exchange, observationContext, call)); } private Publisher filter(ServerWebExchange exchange, ServerRequestObservationContext observationContext, Mono call) { - Observation observation = ServerHttpObservationDocumentation.HTTP_REACTIVE_SERVER_EXCHANGES.observation(this.observationConvention, + Observation observation = ServerHttpObservationDocumentation.HTTP_REACTIVE_SERVER_REQUESTS.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry); observation.start(); return call.doOnEach(signal -> { @@ -123,7 +120,7 @@ private Publisher filter(ServerWebExchange exchange, ServerRequestObservat observationContext.setConnectionAborted(true); observation.stop(); }) - .contextWrite(context -> context.put(MICROMETER_OBSERVATION_KEY, observation)); + .contextWrite(context -> context.put(ObservationThreadLocalAccessor.KEY, observation)); } private void onTerminalSignal(Observation observation, ServerWebExchange exchange) { diff --git a/spring-web/src/main/java/org/springframework/web/jsf/DelegatingNavigationHandlerProxy.java b/spring-web/src/main/java/org/springframework/web/jsf/DelegatingNavigationHandlerProxy.java index e1369563e9f8..0e3bac26bdda 100644 --- a/spring-web/src/main/java/org/springframework/web/jsf/DelegatingNavigationHandlerProxy.java +++ b/spring-web/src/main/java/org/springframework/web/jsf/DelegatingNavigationHandlerProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -110,8 +110,8 @@ public DelegatingNavigationHandlerProxy(NavigationHandler originalNavigationHand @Override public void handleNavigation(FacesContext facesContext, String fromAction, String outcome) { NavigationHandler handler = getDelegate(facesContext); - if (handler instanceof DecoratingNavigationHandler) { - ((DecoratingNavigationHandler) handler).handleNavigation( + if (handler instanceof DecoratingNavigationHandler decoratingNavigationHandler) { + decoratingNavigationHandler.handleNavigation( facesContext, fromAction, outcome, this.originalNavigationHandler); } else { diff --git a/spring-web/src/main/java/org/springframework/web/jsf/FacesContextUtils.java b/spring-web/src/main/java/org/springframework/web/jsf/FacesContextUtils.java index 05c2addf4df2..69a7a7a59cf7 100644 --- a/spring-web/src/main/java/org/springframework/web/jsf/FacesContextUtils.java +++ b/spring-web/src/main/java/org/springframework/web/jsf/FacesContextUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2022 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. @@ -55,16 +55,16 @@ public static WebApplicationContext getWebApplicationContext(FacesContext fc) { if (attr == null) { return null; } - if (attr instanceof RuntimeException) { - throw (RuntimeException) attr; + if (attr instanceof RuntimeException runtimeException) { + throw runtimeException; } - if (attr instanceof Error) { - throw (Error) attr; + if (attr instanceof Error error) { + throw error; } - if (!(attr instanceof WebApplicationContext)) { + if (!(attr instanceof WebApplicationContext wac)) { throw new IllegalStateException("Root context attribute is not of type WebApplicationContext: " + attr); } - return (WebApplicationContext) attr; + return wac; } /** diff --git a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java index f9b353ac0c2a..4402dd37bf3c 100644 --- a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java +++ b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -160,8 +160,8 @@ public int getOrder() { if (this.order == null) { String beanName = null; Object resolvedBean = null; - if (this.beanFactory != null && this.beanOrName instanceof String) { - beanName = (String) this.beanOrName; + if (this.beanFactory != null && this.beanOrName instanceof String stringBeanName) { + beanName = stringBeanName; String targetBeanName = ScopedProxyUtils.getTargetBeanName(beanName); boolean isScopedProxy = this.beanFactory.containsBean(targetBeanName); // Avoid eager @ControllerAdvice bean resolution for scoped proxies, diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerTypePredicate.java b/spring-web/src/main/java/org/springframework/web/method/HandlerTypePredicate.java index d91704195198..c0366067f4df 100644 --- a/spring-web/src/main/java/org/springframework/web/method/HandlerTypePredicate.java +++ b/spring-web/src/main/java/org/springframework/web/method/HandlerTypePredicate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/MapMethodProcessor.java b/spring-web/src/main/java/org/springframework/web/method/annotation/MapMethodProcessor.java index 165cf32e4873..ba47fe3d4258 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/MapMethodProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/MapMethodProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -65,8 +65,8 @@ public boolean supportsReturnType(MethodParameter returnType) { public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { - if (returnValue instanceof Map){ - mavContainer.addAllAttributes((Map) returnValue); + if (returnValue instanceof Map map) { + mavContainer.addAllAttributes(map); } else if (returnValue != null) { // should not happen diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java index 78ad6ede2a75..2e3d0efd8b75 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -406,9 +406,9 @@ protected void validateValueIfApplicable(WebDataBinder binder, MethodParameter p Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann); if (validationHints != null) { for (Validator validator : binder.getValidators()) { - if (validator instanceof SmartValidator) { + if (validator instanceof SmartValidator smartValidator) { try { - ((SmartValidator) validator).validateValue(targetType, fieldName, value, + smartValidator.validateValue(targetType, fieldName, value, binder.getBindingResult(), validationHints); } catch (IllegalArgumentException ex) { diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelMethodProcessor.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelMethodProcessor.java index 3915259ae7e7..2aef369c69a6 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelMethodProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelMethodProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -65,8 +65,8 @@ public void handleReturnValue(@Nullable Object returnValue, MethodParameter retu if (returnValue == null) { return; } - else if (returnValue instanceof Model) { - mavContainer.addAllAttributes(((Model) returnValue).asMap()); + else if (returnValue instanceof Model model) { + mavContainer.addAllAttributes(model.asMap()); } else { // should not happen diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java index 265d8cae47aa..eb5f2b31151d 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -234,8 +234,8 @@ public void contributeMethodArgument(MethodParameter parameter, @Nullable Object Assert.state(name != null, "Unresolvable parameter name"); parameter = parameter.nestedIfOptional(); - if (value instanceof Optional) { - value = ((Optional) value).orElse(null); + if (value instanceof Optional optional) { + value = optional.orElse(null); } if (value == null) { @@ -245,8 +245,8 @@ public void contributeMethodArgument(MethodParameter parameter, @Nullable Object } builder.queryParam(name); } - else if (value instanceof Collection) { - for (Object element : (Collection) value) { + else if (value instanceof Collection elements) { + for (Object element : elements) { element = formatUriValue(conversionService, TypeDescriptor.nested(parameter, 1), element); builder.queryParam(name, element); } @@ -263,8 +263,8 @@ protected String formatUriValue( if (value == null) { return null; } - else if (value instanceof String) { - return (String) value; + else if (value instanceof String string) { + return string; } else if (cs != null) { return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR); diff --git a/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandler.java b/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandler.java index 6317abf679ac..2133effcb315 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandler.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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,7 +22,7 @@ /** * Strategy interface to handle the value returned from the invocation of a - * handler method . + * handler method. * * @author Arjen Poutsma * @since 3.1 diff --git a/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java b/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java index 301f060916aa..c00b7923e014 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -94,8 +94,8 @@ private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, Me private boolean isAsyncReturnValue(@Nullable Object value, MethodParameter returnType) { for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) { - if (handler instanceof AsyncHandlerMethodReturnValueHandler && - ((AsyncHandlerMethodReturnValueHandler) handler).isAsyncReturnValue(value, returnType)) { + if (handler instanceof AsyncHandlerMethodReturnValueHandler asyncHandler && + asyncHandler.isAsyncReturnValue(value, returnType)) { return true; } } diff --git a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java index b662d31301d7..99e7ea079cef 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -208,21 +208,21 @@ protected Object doInvoke(Object... args) throws Exception { } catch (IllegalArgumentException ex) { assertTargetBean(method, getBean(), args); - String text = (ex.getMessage() == null || ex.getCause() instanceof NullPointerException) - ? "Illegal argument": ex.getMessage(); + String text = (ex.getMessage() == null || ex.getCause() instanceof NullPointerException) ? + "Illegal argument" : ex.getMessage(); throw new IllegalStateException(formatInvokeError(text, args), ex); } catch (InvocationTargetException ex) { // Unwrap for HandlerExceptionResolvers ... - Throwable targetException = ex.getTargetException(); - if (targetException instanceof RuntimeException) { - throw (RuntimeException) targetException; + Throwable targetException = ex.getCause(); + if (targetException instanceof RuntimeException runtimeException) { + throw runtimeException; } - else if (targetException instanceof Error) { - throw (Error) targetException; + else if (targetException instanceof Error error) { + throw error; } - else if (targetException instanceof Exception) { - throw (Exception) targetException; + else if (targetException instanceof Exception exception) { + throw exception; } else { throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException); diff --git a/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java b/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java index 344a2c267c6a..bf5ae9f91ef8 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java @@ -40,7 +40,7 @@ *

    A default {@link Model} is automatically created at instantiation. * An alternate model instance may be provided via {@link #setRedirectModel} * for use in a redirect scenario. When {@link #setRedirectModelScenario} is set - * to {@code true} signalling a redirect scenario, the {@link #getModel()} + * to {@code true} signaling a redirect scenario, the {@link #getModel()} * returns the redirect model instead of the default model. * * @author Rossen Stoyanchev @@ -106,7 +106,7 @@ public void setViewName(@Nullable String viewName) { */ @Nullable public String getViewName() { - return (this.view instanceof String ? (String) this.view : null); + return (this.view instanceof String viewName ? viewName : null); } /** diff --git a/spring-web/src/main/java/org/springframework/web/multipart/MultipartFileResource.java b/spring-web/src/main/java/org/springframework/web/multipart/MultipartFileResource.java index 1e98350ddc42..4095c494e7f3 100644 --- a/spring-web/src/main/java/org/springframework/web/multipart/MultipartFileResource.java +++ b/spring-web/src/main/java/org/springframework/web/multipart/MultipartFileResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -88,9 +88,9 @@ public String getDescription() { @Override - public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof MultipartFileResource && - ((MultipartFileResource) other).multipartFile.equals(this.multipartFile))); + public boolean equals(@Nullable Object obj) { + return (this == obj || (obj instanceof MultipartFileResource other && + this.multipartFile.equals(other.multipartFile))); } @Override diff --git a/spring-web/src/main/java/org/springframework/web/multipart/support/StandardServletMultipartResolver.java b/spring-web/src/main/java/org/springframework/web/multipart/support/StandardServletMultipartResolver.java index 7888b8bde0fd..3e35fb96228a 100644 --- a/spring-web/src/main/java/org/springframework/web/multipart/support/StandardServletMultipartResolver.java +++ b/spring-web/src/main/java/org/springframework/web/multipart/support/StandardServletMultipartResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -114,8 +114,8 @@ public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) @Override public void cleanupMultipart(MultipartHttpServletRequest request) { - if (!(request instanceof AbstractMultipartHttpServletRequest) || - ((AbstractMultipartHttpServletRequest) request).isResolved()) { + if (!(request instanceof AbstractMultipartHttpServletRequest abstractMultipartHttpServletRequest) || + abstractMultipartHttpServletRequest.isResolved()) { // To be on the safe side: explicitly delete the parts, // but only actual file parts (for Resin compatibility) try { 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 244471bcab7b..ab1d0aae0c81 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 @@ -56,8 +56,9 @@ public MethodNotAllowedException(String method, @Nullable Collection } this.method = method; this.httpMethods = Collections.unmodifiableSet(new LinkedHashSet<>(supportedMethods)); - getBody().setDetail(this.httpMethods.isEmpty() ? - getReason() : "Supported methods: " + this.httpMethods); + if (!this.httpMethods.isEmpty()) { + setDetail("Supported methods: " + this.httpMethods); + } } diff --git a/spring-web/src/main/java/org/springframework/web/server/MissingRequestValueException.java b/spring-web/src/main/java/org/springframework/web/server/MissingRequestValueException.java index f987fb63abdd..281676507a71 100644 --- a/spring-web/src/main/java/org/springframework/web/server/MissingRequestValueException.java +++ b/spring-web/src/main/java/org/springframework/web/server/MissingRequestValueException.java @@ -42,7 +42,6 @@ public MissingRequestValueException(String name, Class type, String label, Me this.name = name; this.type = type; this.label = label; - getBody().setDetail(getReason()); } 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 51014e1d3b6e..aeb728667f7e 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 @@ -47,7 +47,7 @@ public class NotAcceptableStatusException extends ResponseStatusException { public NotAcceptableStatusException(String reason) { super(HttpStatus.NOT_ACCEPTABLE, reason, null, PARSE_ERROR_DETAIL_CODE, null); this.supportedMediaTypes = Collections.emptyList(); - getBody().setDetail("Could not parse Accept header."); + setDetail("Could not parse Accept header."); } /** @@ -58,7 +58,7 @@ public NotAcceptableStatusException(List mediaTypes) { "Could not find acceptable representation", null, null, new Object[] {mediaTypes}); this.supportedMediaTypes = Collections.unmodifiableList(mediaTypes); - getBody().setDetail("Acceptable representations: " + mediaTypes + "."); + setDetail("Acceptable representations: " + mediaTypes + "."); } 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 d72992d22ceb..c779133e5e69 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 @@ -23,8 +23,9 @@ import org.springframework.web.ErrorResponseException; /** - * Subclass of {@link ErrorResponseException} that accepts a "reason" and maps - * it to the "detail" property of {@link org.springframework.http.ProblemDetail}. + * Subclass of {@link ErrorResponseException} that accepts a "reason", and by + * default maps that to the {@link ErrorResponseException#setDetail(String) "detail"} + * of the {@code ProblemDetail}. * * @author Rossen Stoyanchev * @author Juergen Hoeller @@ -75,8 +76,7 @@ public ResponseStatusException(int rawStatusCode, @Nullable String reason, @Null * @param cause a nested exception (optional) */ public ResponseStatusException(HttpStatusCode status, @Nullable String reason, @Nullable Throwable cause) { - super(status, cause); - this.reason = reason; + this(status, reason, cause, null, null); } /** @@ -93,6 +93,7 @@ protected ResponseStatusException( super(status, ProblemDetail.forStatus(status), cause, messageDetailCode, messageDetailArguments); this.reason = reason; + setDetail(reason); } diff --git a/spring-web/src/main/java/org/springframework/web/server/ServerErrorException.java b/spring-web/src/main/java/org/springframework/web/server/ServerErrorException.java index a11a6135c3c3..e079e0f2b9af 100644 --- a/spring-web/src/main/java/org/springframework/web/server/ServerErrorException.java +++ b/spring-web/src/main/java/org/springframework/web/server/ServerErrorException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/web/server/UnsatisfiedRequestParameterException.java b/spring-web/src/main/java/org/springframework/web/server/UnsatisfiedRequestParameterException.java index 778c251216e5..21c043afdc20 100644 --- a/spring-web/src/main/java/org/springframework/web/server/UnsatisfiedRequestParameterException.java +++ b/spring-web/src/main/java/org/springframework/web/server/UnsatisfiedRequestParameterException.java @@ -40,7 +40,7 @@ public UnsatisfiedRequestParameterException(List conditions, MultiValueM super(initReason(conditions, params), null, null, null, new Object[] {conditions}); this.conditions = conditions; this.requestParams = params; - getBody().setDetail("Invalid request parameters."); + setDetail("Invalid request parameters."); } private static String initReason(List conditions, MultiValueMap queryParams) { diff --git a/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java b/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java index dadf69d39fce..2d194236e638 100644 --- a/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java +++ b/spring-web/src/main/java/org/springframework/web/server/UnsupportedMediaTypeStatusException.java @@ -62,7 +62,7 @@ public UnsupportedMediaTypeStatusException(@Nullable String reason) { this.supportedMediaTypes = Collections.emptyList(); this.bodyType = null; this.method = null; - getBody().setDetail("Could not parse Content-Type."); + setDetail("Could not parse Content-Type."); } /** diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/AbstractReactiveWebInitializer.java b/spring-web/src/main/java/org/springframework/web/server/adapter/AbstractReactiveWebInitializer.java index 6e18bec9bbeb..7f3251b0a25e 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/AbstractReactiveWebInitializer.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/AbstractReactiveWebInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -28,6 +28,8 @@ import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; import org.springframework.web.WebApplicationInitializer; /** @@ -55,10 +57,10 @@ public abstract class AbstractReactiveWebInitializer implements WebApplicationIn @Override public void onStartup(ServletContext servletContext) throws ServletException { String servletName = getServletName(); - Assert.hasLength(servletName, "getServletName() must not return null or empty"); + Assert.state(StringUtils.hasLength(servletName), "getServletName() must not return null or empty"); ApplicationContext applicationContext = createApplicationContext(); - Assert.notNull(applicationContext, "createApplicationContext() must not return null"); + Assert.state(applicationContext != null, "createApplicationContext() must not return null"); refreshApplicationContext(applicationContext); registerCloseListener(servletContext, applicationContext); @@ -92,7 +94,7 @@ protected String getServletName() { protected ApplicationContext createApplicationContext() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); Class[] configClasses = getConfigClasses(); - Assert.notEmpty(configClasses, "No Spring configuration provided through getConfigClasses()"); + Assert.state(!ObjectUtils.isEmpty(configClasses), "No Spring configuration provided through getConfigClasses()"); context.register(configClasses); return context; } diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java index 06b949f955e8..dc3537a0f8d1 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java @@ -19,9 +19,9 @@ import java.security.Principal; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @@ -62,7 +62,7 @@ */ public class DefaultServerWebExchange implements ServerWebExchange { - private static final List SAFE_METHODS = Arrays.asList(HttpMethod.GET, HttpMethod.HEAD); + private static final Set SAFE_METHODS = Set.of(HttpMethod.GET, HttpMethod.HEAD); private static final ResolvableType FORM_DATA_TYPE = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class); @@ -281,7 +281,7 @@ private boolean validateIfMatch(@Nullable String eTag) { if (SAFE_METHODS.contains(getRequest().getMethod())) { return false; } - if (CollectionUtils.isEmpty(getRequest().getHeaders().get(HttpHeaders.IF_MATCH))) { + if (CollectionUtils.isEmpty(getRequestHeaders().get(HttpHeaders.IF_MATCH))) { return false; } this.notModified = matchRequestedETags(getRequestHeaders().getIfMatch(), eTag, false); @@ -346,7 +346,7 @@ private boolean eTagWeakMatch(@Nullable String first, @Nullable String second) { return first.equals(second); } - private void updateResponseStateChanging(String eTag, Instant lastModified) { + private void updateResponseStateChanging(@Nullable String eTag, Instant lastModified) { if (this.notModified) { getResponse().setStatusCode(HttpStatus.PRECONDITION_FAILED); } @@ -357,7 +357,7 @@ private void updateResponseStateChanging(String eTag, Instant lastModified) { private boolean validateIfNoneMatch(@Nullable String eTag) { try { - if (CollectionUtils.isEmpty(getRequest().getHeaders().get(HttpHeaders.IF_NONE_MATCH))) { + if (CollectionUtils.isEmpty(getRequestHeaders().get(HttpHeaders.IF_NONE_MATCH))) { return false; } this.notModified = !matchRequestedETags(getRequestHeaders().getIfNoneMatch(), eTag, true); @@ -401,17 +401,15 @@ private boolean validateIfUnmodifiedSince(Instant lastModified) { return true; } - private boolean validateIfModifiedSince(Instant lastModified) { + private void validateIfModifiedSince(Instant lastModified) { if (lastModified.isBefore(Instant.EPOCH)) { - return false; + return; } long ifModifiedSince = getRequestHeaders().getIfModifiedSince(); - if (ifModifiedSince == -1) { - return false; + if (ifModifiedSince != -1) { + // We will perform this validation... + this.notModified = ChronoUnit.SECONDS.between(lastModified, Instant.ofEpochMilli(ifModifiedSince)) >= 0; } - // We will perform this validation... - this.notModified = ChronoUnit.SECONDS.between(lastModified, Instant.ofEpochMilli(ifModifiedSince)) >= 0; - return true; } @Override diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java b/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java index 7dddb1f26ca1..6c8405a56513 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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,8 +16,6 @@ package org.springframework.web.server.adapter; -import java.util.Arrays; -import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -73,9 +71,9 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa private static final String DISCONNECTED_CLIENT_LOG_CATEGORY = "org.springframework.web.server.DisconnectedClient"; - // Similar declaration exists in AbstractSockJsSession.. - private static final Set DISCONNECTED_CLIENT_EXCEPTIONS = new HashSet<>( - Arrays.asList("AbortedException", "ClientAbortException", "EOFException", "EofException")); + // Similar declaration exists in AbstractSockJsSession. + private static final Set DISCONNECTED_CLIENT_EXCEPTIONS = + Set.of("AbortedException", "ClientAbortException", "EOFException", "EofException"); private static final Log logger = LogFactory.getLog(HttpWebHandlerAdapter.class); diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java b/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java index ab1b418f6774..0a6a87bada2d 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -247,8 +247,9 @@ private void updateFilters() { List filtersToUse = this.filters.stream() .peek(filter -> { - if (filter instanceof ForwardedHeaderTransformer && this.forwardedHeaderTransformer == null) { - this.forwardedHeaderTransformer = (ForwardedHeaderTransformer) filter; + if (filter instanceof ForwardedHeaderTransformer forwardedHeaderTransformerFilter + && this.forwardedHeaderTransformer == null) { + this.forwardedHeaderTransformer = forwardedHeaderTransformerFilter; } }) .filter(filter -> !(filter instanceof ForwardedHeaderTransformer)) 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 cf1337781d58..e5d17180413a 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 @@ -95,8 +95,8 @@ private boolean updateResponse(ServerHttpResponse response, Throwable ex) { int code = (statusCode != null ? statusCode.value() : determineRawStatusCode(ex)); if (code != -1) { if (response.setStatusCode(statusCode)) { - if (ex instanceof ResponseStatusException) { - ((ResponseStatusException) ex).getHeaders().forEach((name, values) -> + if (ex instanceof ResponseStatusException responseStatusException) { + responseStatusException.getHeaders().forEach((name, values) -> values.forEach(value -> response.getHeaders().add(name, value))); } result = true; diff --git a/spring-web/src/main/java/org/springframework/web/server/session/WebSessionStore.java b/spring-web/src/main/java/org/springframework/web/server/session/WebSessionStore.java index ca8984f0c053..15eeb1284255 100644 --- a/spring-web/src/main/java/org/springframework/web/server/session/WebSessionStore.java +++ b/spring-web/src/main/java/org/springframework/web/server/session/WebSessionStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -45,7 +45,7 @@ public interface WebSessionStore { * and if it has expired remove the session and return empty. This method * should also update the lastAccessTime of retrieved sessions. * @param sessionId the session to load - * @return the session, or an empty {@code Mono} . + * @return the session, or an empty {@code Mono} */ Mono retrieveSession(String sessionId); diff --git a/spring-web/src/main/java/org/springframework/web/service/annotation/HttpExchangeReflectiveProcessor.java b/spring-web/src/main/java/org/springframework/web/service/annotation/HttpExchangeReflectiveProcessor.java index 16e24ab6b89b..128d821121cc 100644 --- a/spring-web/src/main/java/org/springframework/web/service/annotation/HttpExchangeReflectiveProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/service/annotation/HttpExchangeReflectiveProcessor.java @@ -29,7 +29,7 @@ /** * {@link ReflectiveProcessor} implementation for {@link HttpExchange @HttpExchange} - * and annotated methods. On top of registering reflection hints for invoking + * annotated methods. In addition to registering reflection hints for invoking * the annotated method, this implementation handles reflection-based * binding for return types and parameters annotated with {@link RequestBody}. * @@ -40,6 +40,7 @@ class HttpExchangeReflectiveProcessor implements ReflectiveProcessor { private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); + @Override public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) { if (element instanceof Method method) { diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java index 96ef73a9d6b0..090ec54859fb 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java @@ -17,14 +17,11 @@ package org.springframework.web.service.invoker; import java.net.URI; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.function.Function; import org.reactivestreams.Publisher; @@ -34,7 +31,6 @@ import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.client.MultipartBodyBuilder; -import org.springframework.http.codec.FormHttpMessageWriter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -115,9 +111,11 @@ public HttpMethod getHttpMethod() { } /** - * Return the full URL to use, if set. - *

    This is mutually exclusive with {@link #getUriTemplate() uriTemplate}. - * One of the two has a value but not both. + * Return the URL to use. + *

    Typically, this comes from a {@link URI} method argument, which provides + * the caller with the option to override the {@link #getUriTemplate() + * uriTemplate} from class and method {@code HttpExchange} annotations. + * annotation. */ @Nullable public URI getUri() { @@ -125,9 +123,8 @@ public URI getUri() { } /** - * Return the URL template for the request, if set. - *

    This is mutually exclusive with a {@linkplain #getUri() full URL}. - * One of the two has a value but not both. + * Return the URL template for the request. This comes from the values in + * class and method {@code HttpExchange} annotations. */ @Nullable public String getUriTemplate() { @@ -201,8 +198,6 @@ public static Builder builder() { */ public final static class Builder { - private static final Function, byte[]> FORM_DATA_SERIALIZER = new FormDataSerializer(); - @Nullable private HttpMethod httpMethod; @@ -248,38 +243,29 @@ public Builder setHttpMethod(HttpMethod httpMethod) { } /** - * Set the request URL as a full URL. - *

    This is mutually exclusive with, and resets any previously set - * {@linkplain #setUriTemplate(String) URI template} or - * {@linkplain #setUriVariable(String, String) URI variables}. + * Set the URL to use. When set, this overrides the + * {@linkplain #setUriTemplate(String) URI template} from the + * {@code HttpExchange} annotation. */ public Builder setUri(URI uri) { this.uri = uri; - this.uriTemplate = null; - this.uriVars = null; return this; } /** * Set the request URL as a String template. - *

    This is mutually exclusive with, and resets any previously set - * {@linkplain #setUri(URI) full URI}. */ public Builder setUriTemplate(String uriTemplate) { this.uriTemplate = uriTemplate; - this.uri = null; return this; } /** * Add a URI variable name-value pair. - *

    This is mutually exclusive with, and resets any previously set - * {@linkplain #setUri(URI) full URI}. */ public Builder setUriVariable(String name, String value) { this.uriVars = (this.uriVars != null ? this.uriVars : new LinkedHashMap<>()); this.uriVars.put(name, value); - this.uri = null; return this; } @@ -399,7 +385,7 @@ public > void setBody(P body, ParameterizedTypeReferen public HttpRequestValues build() { URI uri = this.uri; - String uriTemplate = (this.uriTemplate != null || uri != null ? this.uriTemplate : ""); + String uriTemplate = (this.uriTemplate != null ? this.uriTemplate : ""); Map uriVars = (this.uriVars != null ? new HashMap<>(this.uriVars) : Collections.emptyMap()); Object bodyValue = this.bodyValue; @@ -411,7 +397,7 @@ public HttpRequestValues build() { if (isFormData) { Assert.isTrue(bodyValue == null && this.body == null, "Expected body or request params, not both"); - bodyValue = FORM_DATA_SERIALIZER.apply(this.requestParams); + bodyValue = new LinkedMultiValueMap<>(this.requestParams); } else if (uri != null) { uri = UriComponentsBuilder.fromUri(uri) @@ -466,16 +452,4 @@ private String appendQueryParams( } - - private static class FormDataSerializer - extends FormHttpMessageWriter implements Function, byte[]> { - - @Override - public byte[] apply(MultiValueMap requestParams) { - Charset charset = StandardCharsets.UTF_8; - return serializeForm(requestParams, charset).getBytes(charset); - } - - } - } diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java index 61e98f63ddff..af509524ba3d 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java @@ -30,8 +30,6 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.framework.ReflectiveMethodInvocation; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.core.MethodIntrospector; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.annotation.AnnotatedElementUtils; @@ -46,163 +44,80 @@ * Factory to create a client proxy from an HTTP service interface with * {@link HttpExchange @HttpExchange} methods. * - *

    To create an instance, use static methods to obtain a {@link Builder Builder}. + *

    To create an instance, use static methods to obtain a + * {@link Builder Builder}. * * @author Rossen Stoyanchev * @since 6.0 * @see org.springframework.web.reactive.function.client.support.WebClientAdapter */ -public final class HttpServiceProxyFactory implements InitializingBean, EmbeddedValueResolverAware { +public final class HttpServiceProxyFactory { - @Nullable - private final BuilderInitializedFactory builderInitializedFactory; + private final HttpClientAdapter clientAdapter; + + private final List argumentResolvers; @Nullable - private final BeanStyleFactory beanStyleFactory; + private final StringValueResolver embeddedValueResolver; + private final ReactiveAdapterRegistry reactiveAdapterRegistry; + + private final Duration blockTimeout; - /** - * Create an instance with the underlying HTTP client to use. - * @param clientAdapter an adapter for the client - * @deprecated in favor of using the Builder to initialize the - * HttpServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - public HttpServiceProxyFactory(HttpClientAdapter clientAdapter) { - this.beanStyleFactory = new BeanStyleFactory(clientAdapter); - this.builderInitializedFactory = null; - } private HttpServiceProxyFactory( HttpClientAdapter clientAdapter, List argumentResolvers, @Nullable StringValueResolver embeddedValueResolver, ReactiveAdapterRegistry reactiveAdapterRegistry, Duration blockTimeout) { - this.beanStyleFactory = null; - this.builderInitializedFactory = new BuilderInitializedFactory( - clientAdapter, argumentResolvers, embeddedValueResolver, reactiveAdapterRegistry, blockTimeout); - } - - - /** - * Register a custom argument resolver, invoked ahead of default resolvers. - * @param resolver the resolver to add - * @deprecated in favor of using the Builder to initialize the - * HttpServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - public void addCustomArgumentResolver(HttpServiceArgumentResolver resolver) { - Assert.state(this.beanStyleFactory != null, "HttpServiceProxyFactory was created through the builder"); - this.beanStyleFactory.addCustomArgumentResolver(resolver); + this.clientAdapter = clientAdapter; + this.argumentResolvers = argumentResolvers; + this.embeddedValueResolver = embeddedValueResolver; + this.reactiveAdapterRegistry = reactiveAdapterRegistry; + this.blockTimeout = blockTimeout; } - /** - * Set the custom argument resolvers to use, ahead of default resolvers. - * @param resolvers the resolvers to use - * @deprecated in favor of using the Builder to initialize the - * HttpServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - public void setCustomArgumentResolvers(List resolvers) { - Assert.state(this.beanStyleFactory != null, "HttpServiceProxyFactory was created through the builder"); - this.beanStyleFactory.setCustomArgumentResolvers(resolvers); - } - - /** - * Set the {@link ConversionService} to use where input values need to - * be formatted as Strings. - *

    By default this is {@link DefaultFormattingConversionService}. - * @deprecated in favor of using the Builder to initialize the - * HttpServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - public void setConversionService(ConversionService conversionService) { - Assert.state(this.beanStyleFactory != null, "HttpServiceProxyFactory was created through the builder"); - this.beanStyleFactory.setConversionService(conversionService); - } /** - * Set the StringValueResolver to use for resolving placeholders and - * expressions in {@link HttpExchange#url()}. - * @param resolver the resolver to use - * @deprecated in favor of using the Builder to initialize the - * HttpServiceProxyFactory instance. + * Return a proxy that implements the given HTTP service interface to perform + * HTTP requests and retrieve responses through an HTTP client. + * @param serviceType the HTTP service to create a proxy for + * @param the HTTP service type + * @return the created proxy */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - @Override - public void setEmbeddedValueResolver(StringValueResolver resolver) { - if (this.beanStyleFactory != null) { - this.beanStyleFactory.setEmbeddedValueResolver(resolver); - } - } + public S createClient(Class serviceType) { - /** - * Set the {@link ReactiveAdapterRegistry} to use to support different - * asynchronous types for HTTP service method return values. - *

    By default this is {@link ReactiveAdapterRegistry#getSharedInstance()}. - * @deprecated in favor of using the Builder to initialize the - * HttpServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) { - Assert.state(this.beanStyleFactory != null, "HttpServiceProxyFactory was created through the builder"); - this.beanStyleFactory.setReactiveAdapterRegistry(registry); - } + List httpServiceMethods = + MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod).stream() + .map(method -> createHttpServiceMethod(serviceType, method)) + .toList(); - /** - * Configure how long to wait for a response for an HTTP service method - * with a synchronous (blocking) method signature. - *

    By default this is 5 seconds. - * @param blockTimeout the timeout value - * @deprecated in favor of using the Builder to initialize the - * HttpServiceProxyFactory instance. - */ - @Deprecated(since = "6.0.0-RC2", forRemoval = true) - public void setBlockTimeout(Duration blockTimeout) { - Assert.state(this.beanStyleFactory != null, "HttpServiceProxyFactory was created through the builder"); - this.beanStyleFactory.setBlockTimeout(blockTimeout); + return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(httpServiceMethods)); } - - @Override - @Deprecated - public void afterPropertiesSet() throws Exception { - if (this.beanStyleFactory != null) { - this.beanStyleFactory.afterPropertiesSet(); - } + private boolean isExchangeMethod(Method method) { + return AnnotatedElementUtils.hasAnnotation(method, HttpExchange.class); } + private HttpServiceMethod createHttpServiceMethod(Class serviceType, Method method) { + Assert.notNull(this.argumentResolvers, + "No argument resolvers: afterPropertiesSet was not called"); - /** - * Return a proxy that implements the given HTTP service interface to perform - * HTTP requests and retrieve responses through an HTTP client. - * @param serviceType the HTTP service to create a proxy for - * @param the HTTP service type - * @return the created proxy - */ - public S createClient(Class serviceType) { - if (this.builderInitializedFactory != null) { - return this.builderInitializedFactory.createClient(serviceType); - } - else if (this.beanStyleFactory != null) { - return this.beanStyleFactory.createClient(serviceType); - } - else { - throw new IllegalStateException("Expected Builder initialized or Bean-style delegate"); - } + return new HttpServiceMethod( + method, serviceType, this.argumentResolvers, this.clientAdapter, + this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout); } /** - * Return an {@link HttpServiceProxyFactory} builder, initialized with the - * given client. + * Return a builder that's initialized with the given client. */ public static Builder builder(HttpClientAdapter clientAdapter) { return new Builder().clientAdapter(clientAdapter); } /** - * Return an {@link HttpServiceProxyFactory} builder. + * Return an empty builder, with the client to be provided to builder. */ public static Builder builder() { return new Builder(); @@ -365,173 +280,4 @@ public Object invoke(MethodInvocation invocation) throws Throwable { } } - - /** - * Temporary class until bean-style initialization is removed. - */ - private static final class BuilderInitializedFactory { - - private final HttpClientAdapter clientAdapter; - - private final List argumentResolvers; - - @Nullable - private final StringValueResolver embeddedValueResolver; - - private final ReactiveAdapterRegistry reactiveAdapterRegistry; - - private final Duration blockTimeout; - - private BuilderInitializedFactory( - HttpClientAdapter clientAdapter, List argumentResolvers, - @Nullable StringValueResolver embeddedValueResolver, - ReactiveAdapterRegistry reactiveAdapterRegistry, Duration blockTimeout) { - - this.clientAdapter = clientAdapter; - this.argumentResolvers = argumentResolvers; - this.embeddedValueResolver = embeddedValueResolver; - this.reactiveAdapterRegistry = reactiveAdapterRegistry; - this.blockTimeout = blockTimeout; - } - - public S createClient(Class serviceType) { - - List httpServiceMethods = - MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod).stream() - .map(method -> createHttpServiceMethod(serviceType, method)) - .toList(); - - return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(httpServiceMethods)); - } - - private boolean isExchangeMethod(Method method) { - return AnnotatedElementUtils.hasAnnotation(method, HttpExchange.class); - } - - private HttpServiceMethod createHttpServiceMethod(Class serviceType, Method method) { - Assert.notNull(this.argumentResolvers, - "No argument resolvers: afterPropertiesSet was not called"); - - return new HttpServiceMethod( - method, serviceType, this.argumentResolvers, this.clientAdapter, - this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout); - } - } - - - /** - * Temporary class to support bean-style initialization during deprecation period. - */ - private static final class BeanStyleFactory implements InitializingBean, EmbeddedValueResolverAware { - - private final HttpClientAdapter clientAdapter; - - @Nullable - private List customArgumentResolvers; - - @Nullable - private List argumentResolvers; - - @Nullable - private ConversionService conversionService; - - @Nullable - private StringValueResolver embeddedValueResolver; - - private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); - - private Duration blockTimeout = Duration.ofSeconds(5); - - BeanStyleFactory(HttpClientAdapter clientAdapter) { - Assert.notNull(clientAdapter, "HttpClientAdapter is required"); - this.clientAdapter = clientAdapter; - } - - public void addCustomArgumentResolver(HttpServiceArgumentResolver resolver) { - if (this.customArgumentResolvers == null) { - this.customArgumentResolvers = new ArrayList<>(); - } - this.customArgumentResolvers.add(resolver); - } - - public void setCustomArgumentResolvers(List resolvers) { - this.customArgumentResolvers = new ArrayList<>(resolvers); - } - - public void setConversionService(ConversionService conversionService) { - this.conversionService = conversionService; - } - - @Override - public void setEmbeddedValueResolver(StringValueResolver resolver) { - this.embeddedValueResolver = resolver; - } - - public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) { - this.reactiveAdapterRegistry = registry; - } - - public void setBlockTimeout(Duration blockTimeout) { - this.blockTimeout = blockTimeout; - } - - - @Override - public void afterPropertiesSet() throws Exception { - - this.conversionService = (this.conversionService != null ? - this.conversionService : new DefaultFormattingConversionService()); - - this.argumentResolvers = initArgumentResolvers(this.conversionService); - } - - private List initArgumentResolvers(ConversionService conversionService) { - List resolvers = new ArrayList<>(); - - // Custom - if (this.customArgumentResolvers != null) { - resolvers.addAll(this.customArgumentResolvers); - } - - // Annotation-based - resolvers.add(new RequestHeaderArgumentResolver(conversionService)); - resolvers.add(new RequestBodyArgumentResolver(this.reactiveAdapterRegistry)); - resolvers.add(new PathVariableArgumentResolver(conversionService)); - resolvers.add(new RequestParamArgumentResolver(conversionService)); - resolvers.add(new RequestPartArgumentResolver(this.reactiveAdapterRegistry)); - resolvers.add(new CookieValueArgumentResolver(conversionService)); - resolvers.add(new RequestAttributeArgumentResolver()); - - // Specific type - resolvers.add(new UrlArgumentResolver()); - resolvers.add(new HttpMethodArgumentResolver()); - - return resolvers; - } - - - public S createClient(Class serviceType) { - - List httpServiceMethods = - MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod).stream() - .map(method -> createHttpServiceMethod(serviceType, method)) - .toList(); - - return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(httpServiceMethods)); - } - - private boolean isExchangeMethod(Method method) { - return AnnotatedElementUtils.hasAnnotation(method, HttpExchange.class); - } - - private HttpServiceMethod createHttpServiceMethod(Class serviceType, Method method) { - Assert.notNull(this.argumentResolvers, - "No argument resolvers: afterPropertiesSet was not called"); - - return new HttpServiceMethod( - method, serviceType, this.argumentResolvers, this.clientAdapter, - this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout); - } - } - } diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/UrlArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/service/invoker/UrlArgumentResolver.java index bdeee06724aa..3ca43c85051e 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/UrlArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/UrlArgumentResolver.java @@ -40,7 +40,6 @@ public boolean resolve( if (argument != null) { requestValues.setUri((URI) argument); - return true; } return true; diff --git a/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java b/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java index 5d85c5dfa57b..8f5964662433 100644 --- a/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java b/spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java index c57bd4b5e4d5..3cf02804eaef 100644 --- a/spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java @@ -159,8 +159,8 @@ public static Object getCachedPath(ServletRequest request) { */ public static String getCachedPathValue(ServletRequest request) { Object path = getCachedPath(request); - if (path instanceof PathContainer) { - String value = ((PathContainer) path).value(); + if (path instanceof PathContainer pathContainer) { + String value = pathContainer.value(); path = UrlPathHelper.defaultInstance.removeSemicolonContent(value); } return (String) path; diff --git a/spring-web/src/main/java/org/springframework/web/util/UriBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriBuilder.java index 7a014faae786..70b5040d1f71 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -70,7 +70,7 @@ public interface UriBuilder { UriBuilder port(int port); /** - * Set the URI port . Use this method only when the port needs to be + * Set the URI port. Use this method only when the port needs to be * parameterized with a URI variable. Otherwise use {@link #port(int)}. * Passing {@code null} will clear the port of this builder. * @param port the URI port diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index 38762811619b..33cdc834166a 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -726,8 +726,8 @@ public UriComponentsBuilder queryParam(String name, Object... values) { @Nullable private String getQueryParamValue(@Nullable Object value) { if (value != null) { - return (value instanceof Optional ? - ((Optional) value).map(Object::toString).orElse(null) : + return (value instanceof Optional optional ? + optional.map(Object::toString).orElse(null) : value.toString()); } return null; @@ -740,12 +740,12 @@ public UriComponentsBuilder queryParam(String name, @Nullable Collection valu @Override public UriComponentsBuilder queryParamIfPresent(String name, Optional value) { - value.ifPresent(o -> { - if (o instanceof Collection) { - queryParam(name, (Collection) o); + value.ifPresent(v -> { + if (v instanceof Collection values) { + queryParam(name, values); } else { - queryParam(name, o); + queryParam(name, v); } }); return this; diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureTheRestPathElement.java b/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureTheRestPathElement.java index 3ee4ff2ebac0..2176aad24c7c 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureTheRestPathElement.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/CaptureTheRestPathElement.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -66,8 +66,8 @@ public boolean matches(int pathIndex, MatchingContext matchingContext) { MultiValueMap parametersCollector = null; for (int i = pathIndex; i < matchingContext.pathLength; i++) { Element element = matchingContext.pathElements.get(i); - if (element instanceof PathSegment) { - MultiValueMap parameters = ((PathSegment) element).parameters(); + if (element instanceof PathSegment pathSegment) { + MultiValueMap parameters = pathSegment.parameters(); if (!parameters.isEmpty()) { if (parametersCollector == null) { parametersCollector = new LinkedMultiValueMap<>(); @@ -86,8 +86,8 @@ private String pathToString(int fromSegment, List pathElements) { StringBuilder sb = new StringBuilder(); for (int i = fromSegment, max = pathElements.size(); i < max; i++) { Element element = pathElements.get(i); - if (element instanceof PathSegment) { - sb.append(((PathSegment)element).valueToMatch()); + if (element instanceof PathSegment pathSegment) { + sb.append(pathSegment.valueToMatch()); } else { sb.append(element.value()); diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/LiteralPathElement.java b/spring-web/src/main/java/org/springframework/web/util/pattern/LiteralPathElement.java index 6c6249df1302..ae3c2ea305d3 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/LiteralPathElement.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/LiteralPathElement.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -118,6 +118,10 @@ public char[] getChars() { return this.text; } + @Override + public boolean isLiteral() { + return true; + } @Override public String toString() { diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/PathElement.java b/spring-web/src/main/java/org/springframework/web/util/pattern/PathElement.java index df4330172645..b9660bf3381a 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/PathElement.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/PathElement.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,6 +25,7 @@ * Common supertype for the Ast nodes created to represent a path pattern. * * @author Andy Clement + * @author Brian Clozel * @since 5.0 */ abstract class PathElement { @@ -99,6 +100,14 @@ public int getScore() { return 0; } + /** + * Return whether this PathElement can be strictly {@link String#compareTo(String) compared} + * against another element for matching. + */ + public boolean isLiteral() { + return false; + } + /** * Return if the there are no more PathElements in the pattern. * @return {@code true} if the there are no more elements diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java b/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java index a153563a9923..ed3339c1dfff 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java @@ -304,7 +304,7 @@ public PathContainer extractPathWithinPattern(PathContainer path) { // Find first path element that is not a separator or a literal (i.e. the first pattern based element) PathElement elem = this.head; while (elem != null) { - if (elem.getWildcardCount() != 0 || elem.getCaptureCount() != 0) { + if (!elem.isLiteral()) { break; } elem = elem.next; diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/SeparatorPathElement.java b/spring-web/src/main/java/org/springframework/web/util/pattern/SeparatorPathElement.java index b140e88b44e3..3e9855b7c11d 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/SeparatorPathElement.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/SeparatorPathElement.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -67,6 +67,10 @@ public char[] getChars() { return new char[] {this.separator}; } + @Override + public boolean isLiteral() { + return true; + } @Override public String toString() { diff --git a/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java b/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java index 2164f8dbdec5..c5434444d68b 100644 --- a/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java +++ b/spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java @@ -25,20 +25,22 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.springframework.http.ContentDisposition.parse; /** - * Unit tests for {@link ContentDisposition} + * Unit tests for {@link ContentDisposition}. + * * @author Sebastien Deleuze * @author Rossen Stoyanchev */ class ContentDispositionTests { - private static DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME; + private static final DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME; - @Test @SuppressWarnings("deprecation") - void parse() { + @Test + void parseFilenameQuoted() { assertThat(parse("form-data; name=\"foo\"; filename=\"foo.txt\"; size=123")) .isEqualTo(ContentDisposition.formData() .name("foo") @@ -72,7 +74,7 @@ void parseEncodedFilename() { .build()); } - @Test // gh-24112 + @Test // gh-24112 void parseEncodedFilenameWithPaddedCharset() { assertThat(parse("attachment; filename*= UTF-8''some-file.zip")) .isEqualTo(ContentDisposition.attachment() @@ -80,7 +82,7 @@ void parseEncodedFilenameWithPaddedCharset() { .build()); } - @Test // gh-26463 + @Test // gh-26463 void parseBase64EncodedFilename() { String input = "attachment; filename=\"=?UTF-8?B?5pel5pys6KqeLmNzdg==?=\""; assertThat(parse(input).getFilename()).isEqualTo("日本語.csv"); @@ -95,7 +97,7 @@ void parseBase64EncodedFilenameMultipleSegments() { assertThat(parse(input).getFilename()).isEqualTo("Spring框架为基于Java的现代企业应用程序提供了全面的编程和配置模型.txt"); } - @Test // gh-26463 + @Test // gh-26463 void parseBase64EncodedShiftJISFilename() { String input = "attachment; filename=\"=?SHIFT_JIS?B?k/qWe4zqLmNzdg==?=\""; assertThat(parse(input).getFilename()).isEqualTo("日本語.csv"); @@ -168,8 +170,8 @@ void parseBackslashInLastPosition() { } - @Test @SuppressWarnings("deprecation") + @Test void parseWithExtraSemicolons() { assertThat(parse("form-data; name=\"foo\";; ; filename=\"foo.txt\"; size=123")) .isEqualTo(ContentDisposition.formData() @@ -179,8 +181,8 @@ void parseWithExtraSemicolons() { .build()); } - @Test @SuppressWarnings("deprecation") + @Test void parseDates() { ZonedDateTime creationTime = ZonedDateTime.parse("Mon, 12 Feb 2007 10:15:30 -0500", formatter); ZonedDateTime modificationTime = ZonedDateTime.parse("Tue, 13 Feb 2007 10:15:30 -0500", formatter); @@ -198,8 +200,8 @@ void parseDates() { .build()); } - @Test @SuppressWarnings("deprecation") + @Test void parseIgnoresInvalidDates() { ZonedDateTime readTime = ZonedDateTime.parse("Wed, 14 Feb 2007 10:15:30 -0500", formatter); @@ -228,13 +230,8 @@ void parseInvalidParameter() { assertThatIllegalArgumentException().isThrownBy(() -> parse("foo;bar")); } - private static ContentDisposition parse(String input) { - return ContentDisposition.parse(input); - } - - - @Test @SuppressWarnings("deprecation") + @Test void format() { assertThat( ContentDisposition.formData() @@ -266,14 +263,11 @@ void formatWithEncodedFilenameUsingUsAscii() { .isEqualTo("form-data; name=\"name\"; filename=\"test.txt\""); } - @Test // gh-24220 + @Test // gh-24220 void formatWithFilenameWithQuotes() { - BiConsumer tester = (input, output) -> { - assertThat(ContentDisposition.formData().filename(input).build().toString()) .isEqualTo("form-data; filename=\"" + output + "\""); - assertThat(ContentDisposition.formData().filename(input, StandardCharsets.US_ASCII).build().toString()) .isEqualTo("form-data; filename=\"" + output + "\""); }; diff --git a/spring-web/src/test/java/org/springframework/http/HttpEntityTests.java b/spring-web/src/test/java/org/springframework/http/HttpEntityTests.java index 6ebf469d088e..40b3a7d799ec 100644 --- a/spring-web/src/test/java/org/springframework/http/HttpEntityTests.java +++ b/spring-web/src/test/java/org/springframework/http/HttpEntityTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -109,8 +109,8 @@ void requestEntity() throws Exception { headers.setContentType(MediaType.TEXT_PLAIN); String body = "foo"; HttpEntity httpEntity = new HttpEntity<>(body, headers); - RequestEntity requestEntity = new RequestEntity<>(body, headers, HttpMethod.GET, new URI("/")); - RequestEntity requestEntity2 = new RequestEntity<>(body, headers, HttpMethod.GET, new URI("/")); + RequestEntity requestEntity = new RequestEntity<>(body, headers, HttpMethod.GET, URI.create("/")); + RequestEntity requestEntity2 = new RequestEntity<>(body, headers, HttpMethod.GET, URI.create("/")); assertThat(requestEntity.getBody()).isEqualTo(body); assertThat(requestEntity.getHeaders().getContentType()).isEqualTo(MediaType.TEXT_PLAIN); diff --git a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java index f802ac500bb6..68f77065c1dc 100644 --- a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java +++ b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -153,7 +153,7 @@ void contentType() { @Test void location() throws URISyntaxException { - URI location = new URI("https://www.example.com/hotels"); + URI location = URI.create("https://www.example.com/hotels"); headers.setLocation(location); assertThat(headers.getLocation()).as("Invalid Location header").isEqualTo(location); assertThat(headers.getFirst("Location")).as("Invalid Location header").isEqualTo("https://www.example.com/hotels"); @@ -588,7 +588,7 @@ void keySetOperations() { // isEmpty() and size() assertThat(keySet.isEmpty()).isFalse(); - assertThat(keySet.size()).isEqualTo(2); + assertThat(keySet).hasSize(2); // contains() assertThat(keySet.contains("Alpha")).as("Alpha should be present").isTrue(); @@ -610,18 +610,18 @@ void keySetOperations() { // remove() assertThat(keySet.remove("Alpha")).isTrue(); - assertThat(keySet.size()).isEqualTo(1); - assertThat(headers.size()).isEqualTo(1); + assertThat(keySet).hasSize(1); + assertThat(headers).hasSize(1); assertThat(keySet.remove("Alpha")).isFalse(); - assertThat(keySet.size()).isEqualTo(1); - assertThat(headers.size()).isEqualTo(1); + assertThat(keySet).hasSize(1); + assertThat(headers).hasSize(1); // clear() keySet.clear(); assertThat(keySet.isEmpty()).isTrue(); - assertThat(keySet.size()).isEqualTo(0); + assertThat(keySet).isEmpty(); assertThat(headers.isEmpty()).isTrue(); - assertThat(headers.size()).isEqualTo(0); + assertThat(headers).isEmpty(); // Unsupported operations assertThatExceptionOfType(UnsupportedOperationException.class) @@ -653,7 +653,7 @@ void keySetRemovalChecks() { assertThat(removed).isTrue(); assertThat(headers.keySet().remove("Alpha")).isFalse(); - assertThat(headers.size()).isEqualTo(1); + assertThat(headers).hasSize(1); assertThat(headers.containsKey("Alpha")).as("Alpha should have been removed").isFalse(); assertThat(headers.containsKey("Bravo")).as("Bravo should be present").isTrue(); assertThat(headers.keySet()).containsOnly("Bravo"); diff --git a/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java b/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java index 550b85fd36d6..191019f98ab4 100644 --- a/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java +++ b/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -96,7 +96,7 @@ public void suffixRangeShorterThanRepresentation() { @Test public void parseRanges() { List ranges = HttpRange.parseRanges("bytes=0-0,500-,-1"); - assertThat(ranges.size()).isEqualTo(3); + assertThat(ranges).hasSize(3); assertThat(ranges.get(0).getRangeStart(1000)).isEqualTo(0); assertThat(ranges.get(0).getRangeEnd(1000)).isEqualTo(0); assertThat(ranges.get(1).getRangeStart(1000)).isEqualTo(500); @@ -114,7 +114,7 @@ public void parseRangesValidations() { atLimit.append(',').append(i).append('-').append(i + 1); } List ranges = HttpRange.parseRanges(atLimit.toString()); - assertThat(ranges.size()).isEqualTo(100); + assertThat(ranges).hasSize(100); // 2. Above limit.. StringBuilder aboveLimit = new StringBuilder("bytes=0-0"); @@ -185,7 +185,7 @@ public void toResourceRegionsValidations() { // 1. Below length List belowLengthRanges = HttpRange.parseRanges("bytes=0-1,2-3"); List regions = HttpRange.toResourceRegions(belowLengthRanges, resource); - assertThat(regions.size()).isEqualTo(2); + assertThat(regions).hasSize(2); // 2. At length List atLengthRanges = HttpRange.parseRanges("bytes=0-1,2-4"); diff --git a/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java b/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java index 190bdf82e890..a3661ff719c5 100644 --- a/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java +++ b/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java @@ -291,7 +291,7 @@ public void specificityComparator() throws Exception { MediaType allXml = new MediaType("application", "*+xml"); MediaType all = MediaType.ALL; - @SuppressWarnings("deprecation") + @SuppressWarnings("removal") Comparator comp = MediaType.SPECIFICITY_COMPARATOR; // equal @@ -335,7 +335,7 @@ public void specificityComparator() throws Exception { } @Test - @SuppressWarnings("deprecation") + @SuppressWarnings("removal") public void sortBySpecificityRelated() { MediaType audioBasic = new MediaType("audio", "basic"); MediaType audio = new MediaType("audio"); @@ -366,7 +366,7 @@ public void sortBySpecificityRelated() { } @Test - @SuppressWarnings("deprecation") + @SuppressWarnings("removal") public void sortBySpecificityUnrelated() { MediaType audioBasic = new MediaType("audio", "basic"); MediaType audioWave = new MediaType("audio", "wave"); @@ -398,7 +398,7 @@ public void qualityComparator() throws Exception { MediaType allXml = new MediaType("application", "*+xml"); MediaType all = MediaType.ALL; - @SuppressWarnings("deprecation") + @SuppressWarnings("removal") Comparator comp = MediaType.QUALITY_VALUE_COMPARATOR; // equal @@ -442,7 +442,7 @@ public void qualityComparator() throws Exception { } @Test - @SuppressWarnings("deprecation") + @SuppressWarnings("removal") public void sortByQualityRelated() { MediaType audioBasic = new MediaType("audio", "basic"); MediaType audio = new MediaType("audio"); @@ -473,7 +473,7 @@ public void sortByQualityRelated() { } @Test - @SuppressWarnings("deprecation") + @SuppressWarnings("removal") public void sortByQualityUnrelated() { MediaType audioBasic = new MediaType("audio", "basic"); MediaType audioWave = new MediaType("audio", "wave"); diff --git a/spring-web/src/test/java/org/springframework/http/ProblemDetailTests.java b/spring-web/src/test/java/org/springframework/http/ProblemDetailTests.java new file mode 100644 index 000000000000..4b94d9786d6a --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/ProblemDetailTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2022 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.http; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link ProblemDetail}. + * + * @author Juergen Hoeller + */ +class ProblemDetailTests { + + @Test + void equalsAndHashCode() { + ProblemDetail pd1 = ProblemDetail.forStatus(500); + ProblemDetail pd2 = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR); + ProblemDetail pd3 = ProblemDetail.forStatus(HttpStatus.NOT_FOUND); + ProblemDetail pd4 = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, "some detail"); + + assertThat(pd1).isEqualTo(pd2); + assertThat(pd2).isEqualTo(pd1); + assertThat(pd1.hashCode()).isEqualTo(pd2.hashCode()); + + assertThat(pd3).isNotEqualTo(pd4); + assertThat(pd4).isNotEqualTo(pd3); + assertThat(pd3.hashCode()).isNotEqualTo(pd4.hashCode()); + + assertThat(pd1).isNotEqualTo(pd3); + assertThat(pd1).isNotEqualTo(pd4); + assertThat(pd2).isNotEqualTo(pd3); + assertThat(pd2).isNotEqualTo(pd4); + assertThat(pd1.hashCode()).isNotEqualTo(pd3.hashCode()); + assertThat(pd1.hashCode()).isNotEqualTo(pd4.hashCode()); + } + +} 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 1c979728f9b4..45799b74c0f7 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-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -17,7 +17,6 @@ package org.springframework.http; import java.net.URI; -import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; @@ -41,10 +40,10 @@ class RequestEntityTests { @Test - void normal() throws URISyntaxException { + void normal() { String headerName = "My-Custom-Header"; String headerValue = "HeaderValue"; - URI url = new URI("https://example.com"); + URI url = URI.create("https://example.com"); Integer entity = 42; RequestEntity requestEntity = @@ -59,14 +58,14 @@ void normal() throws URISyntaxException { } @Test - void uriVariablesExpansion() throws URISyntaxException { + void uriVariablesExpansion() { URI uri = UriComponentsBuilder.fromUriString("https://example.com/{foo}").buildAndExpand("bar").toUri(); RequestEntity.get(uri).accept(MediaType.TEXT_PLAIN).build(); String url = "https://www.{host}.com/{path}"; String host = "example"; String path = "foo/bar"; - URI expected = new URI("https://www.example.com/foo/bar"); + URI expected = URI.create("https://www.example.com/foo/bar"); uri = UriComponentsBuilder.fromUriString(url).buildAndExpand(host, path).toUri(); RequestEntity entity = RequestEntity.get(uri).build(); @@ -107,14 +106,14 @@ void get() { } @Test - void headers() throws URISyntaxException { + void headers() { MediaType accept = MediaType.TEXT_PLAIN; long ifModifiedSince = 12345L; String ifNoneMatch = "\"foo\""; long contentLength = 67890; MediaType contentType = MediaType.TEXT_PLAIN; - RequestEntity responseEntity = RequestEntity.post(new URI("https://example.com")). + RequestEntity responseEntity = RequestEntity.post(URI.create("https://example.com")). accept(accept). acceptCharset(StandardCharsets.UTF_8). ifModifiedSince(ifModifiedSince). @@ -126,7 +125,7 @@ void headers() throws URISyntaxException { assertThat(responseEntity).isNotNull(); assertThat(responseEntity.getMethod()).isEqualTo(HttpMethod.POST); - assertThat(responseEntity.getUrl()).isEqualTo(new URI("https://example.com")); + assertThat(responseEntity.getUrl()).isEqualTo(URI.create("https://example.com")); HttpHeaders responseHeaders = responseEntity.getHeaders(); assertThat(responseHeaders.getFirst(HttpHeaders.ACCEPT)).isEqualTo(MediaType.TEXT_PLAIN_VALUE); @@ -140,8 +139,8 @@ void headers() throws URISyntaxException { } @Test - void methods() throws URISyntaxException { - URI url = new URI("https://example.com"); + void methods() { + URI url = URI.create("https://example.com"); RequestEntity entity = RequestEntity.get(url).build(); assertThat(entity.getMethod()).isEqualTo(HttpMethod.GET); @@ -167,8 +166,8 @@ void methods() throws URISyntaxException { } @Test // SPR-13154 - void types() throws URISyntaxException { - URI url = new URI("https://example.com"); + void types() { + URI url = URI.create("https://example.com"); List body = Arrays.asList("foo", "bar"); ParameterizedTypeReference typeReference = new ParameterizedTypeReference>() {}; diff --git a/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java b/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java index 07b5544db57a..dacfad7b8fd6 100644 --- a/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java +++ b/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java @@ -17,7 +17,6 @@ package org.springframework.http; import java.net.URI; -import java.net.URISyntaxException; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -47,7 +46,7 @@ void normal() { assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(responseEntity.getHeaders().containsKey(headerName)).isTrue(); List list = responseEntity.getHeaders().get(headerName); - assertThat(list.size()).isEqualTo(2); + assertThat(list).hasSize(2); assertThat(list.get(0)).isEqualTo(headerValue1); assertThat(list.get(1)).isEqualTo(headerValue2); assertThat((int) responseEntity.getBody()).isEqualTo((int) entity); @@ -92,8 +91,8 @@ void ofEmptyOptional() { } @Test - void createdLocation() throws URISyntaxException { - URI location = new URI("location"); + void createdLocation() { + URI location = URI.create("location"); ResponseEntity responseEntity = ResponseEntity.created(location).build(); assertThat(responseEntity).isNotNull(); @@ -106,7 +105,7 @@ void createdLocation() throws URISyntaxException { } @Test - void acceptedNoBody() throws URISyntaxException { + void acceptedNoBody() { ResponseEntity responseEntity = ResponseEntity.accepted().build(); assertThat(responseEntity).isNotNull(); @@ -115,7 +114,7 @@ void acceptedNoBody() throws URISyntaxException { } @Test // SPR-14939 - void acceptedNoBodyWithAlternativeBodyType() throws URISyntaxException { + void acceptedNoBodyWithAlternativeBodyType() { ResponseEntity responseEntity = ResponseEntity.accepted().build(); assertThat(responseEntity).isNotNull(); @@ -124,7 +123,7 @@ void acceptedNoBodyWithAlternativeBodyType() throws URISyntaxException { } @Test - void noContent() throws URISyntaxException { + void noContent() { ResponseEntity responseEntity = ResponseEntity.noContent().build(); assertThat(responseEntity).isNotNull(); @@ -133,7 +132,7 @@ void noContent() throws URISyntaxException { } @Test - void badRequest() throws URISyntaxException { + void badRequest() { ResponseEntity responseEntity = ResponseEntity.badRequest().build(); assertThat(responseEntity).isNotNull(); @@ -142,7 +141,7 @@ void badRequest() throws URISyntaxException { } @Test - void notFound() throws URISyntaxException { + void notFound() { ResponseEntity responseEntity = ResponseEntity.notFound().build(); assertThat(responseEntity).isNotNull(); @@ -151,7 +150,7 @@ void notFound() throws URISyntaxException { } @Test - void unprocessableEntity() throws URISyntaxException { + void unprocessableEntity() { ResponseEntity responseEntity = ResponseEntity.unprocessableEntity().body("error"); assertThat(responseEntity).isNotNull(); @@ -160,7 +159,7 @@ void unprocessableEntity() throws URISyntaxException { } @Test - void internalServerError() throws URISyntaxException { + void internalServerError() { ResponseEntity responseEntity = ResponseEntity.internalServerError().body("error"); assertThat(responseEntity).isNotNull(); @@ -169,8 +168,8 @@ void internalServerError() throws URISyntaxException { } @Test - void headers() throws URISyntaxException { - URI location = new URI("location"); + void headers() { + URI location = URI.create("location"); long contentLength = 67890; MediaType contentType = MediaType.TEXT_PLAIN; @@ -197,7 +196,7 @@ void headers() throws URISyntaxException { } @Test - void Etagheader() throws URISyntaxException { + void Etagheader() { ResponseEntity responseEntity = ResponseEntity.ok().eTag("\"foo\"").build(); assertThat(responseEntity.getHeaders().getETag()).isEqualTo("\"foo\""); @@ -221,8 +220,8 @@ void headersCopy() { HttpHeaders responseHeaders = responseEntity.getHeaders(); assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseHeaders.size()).isEqualTo(1); - assertThat(responseHeaders.get("X-CustomHeader").size()).isEqualTo(1); + assertThat(responseHeaders).hasSize(1); + assertThat(responseHeaders.get("X-CustomHeader")).hasSize(1); assertThat(responseHeaders.getFirst("X-CustomHeader")).isEqualTo("vale"); } diff --git a/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTests.java index 427f21f61900..d3024af1a43e 100644 --- a/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -48,15 +48,15 @@ abstract class AbstractHttpRequestFactoryTests extends AbstractMockWebServerTest @BeforeEach final void createFactory() throws Exception { factory = createRequestFactory(); - if (factory instanceof InitializingBean) { - ((InitializingBean) factory).afterPropertiesSet(); + if (factory instanceof InitializingBean initializingBean) { + initializingBean.afterPropertiesSet(); } } @AfterEach final void destroyFactory() throws Exception { - if (factory instanceof DisposableBean) { - ((DisposableBean) factory).destroy(); + if (factory instanceof DisposableBean disposableBean) { + disposableBean.destroy(); } } @@ -66,7 +66,7 @@ final void destroyFactory() throws Exception { @Test void status() throws Exception { - URI uri = new URI(baseUrl + "/status/notfound"); + URI uri = URI.create(baseUrl + "/status/notfound"); ClientHttpRequest request = factory.createRequest(uri, HttpMethod.GET); assertThat(request.getMethod()).as("Invalid HTTP method").isEqualTo(HttpMethod.GET); assertThat(request.getURI()).as("Invalid HTTP URI").isEqualTo(uri); @@ -78,7 +78,7 @@ void status() throws Exception { @Test void echo() throws Exception { - ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/echo"), HttpMethod.PUT); + ClientHttpRequest request = factory.createRequest(URI.create(baseUrl + "/echo"), HttpMethod.PUT); assertThat(request.getMethod()).as("Invalid HTTP method").isEqualTo(HttpMethod.PUT); String headerName = "MyHeader"; @@ -107,7 +107,7 @@ void echo() throws Exception { @Test void multipleWrites() throws Exception { - ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/echo"), HttpMethod.POST); + ClientHttpRequest request = factory.createRequest(URI.create(baseUrl + "/echo"), HttpMethod.POST); final byte[] body = "Hello World".getBytes(StandardCharsets.UTF_8); if (request instanceof StreamingHttpOutputMessage streamingRequest) { @@ -128,7 +128,7 @@ void multipleWrites() throws Exception { @Test void headersAfterExecute() throws Exception { - ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/status/ok"), HttpMethod.POST); + ClientHttpRequest request = factory.createRequest(URI.create(baseUrl + "/status/ok"), HttpMethod.POST); request.getHeaders().add("MyHeader", "value"); byte[] body = "Hello World".getBytes(StandardCharsets.UTF_8); @@ -152,7 +152,7 @@ void httpMethods() throws Exception { } protected void assertHttpMethod(String path, HttpMethod method) throws Exception { - ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/methods/" + path), method); + ClientHttpRequest request = factory.createRequest(URI.create(baseUrl + "/methods/" + path), method); if (method == HttpMethod.POST || method == HttpMethod.PUT || method == HttpMethod.PATCH) { // requires a body try { @@ -171,7 +171,7 @@ protected void assertHttpMethod(String path, HttpMethod method) throws Exception @Test void queryParameters() throws Exception { - URI uri = new URI(baseUrl + "/params?param1=value¶m2=value1¶m2=value2"); + URI uri = URI.create(baseUrl + "/params?param1=value¶m2=value1¶m2=value2"); ClientHttpRequest request = factory.createRequest(uri, HttpMethod.GET); try (ClientHttpResponse response = request.execute()) { diff --git a/spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryTests.java index 131a923b55a0..3f0a595ddf8d 100644 --- a/spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -36,7 +36,7 @@ protected ClientHttpRequestFactory createRequestFactory() { @Test void repeatableRead() throws Exception { - ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/echo"), HttpMethod.PUT); + ClientHttpRequest request = factory.createRequest(URI.create(baseUrl + "/echo"), HttpMethod.PUT); assertThat(request.getMethod()).as("Invalid HTTP method").isEqualTo(HttpMethod.PUT); String headerName = "MyHeader"; String headerValue1 = "value1"; diff --git a/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java index 96a725d156e9..d1d355ccfc28 100644 --- a/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java @@ -38,7 +38,7 @@ /** * @author Stephane Nicoll */ -public class HttpComponentsClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests { +class HttpComponentsClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests { @Override protected ClientHttpRequestFactory createRequestFactory() { @@ -47,19 +47,20 @@ protected ClientHttpRequestFactory createRequestFactory() { @Override @Test - public void httpMethods() throws Exception { + void httpMethods() throws Exception { super.httpMethods(); assertHttpMethod("patch", HttpMethod.PATCH); } @Test - public void assertCustomConfig() throws Exception { + @SuppressWarnings("deprecation") + void assertCustomConfig() throws Exception { HttpClient httpClient = HttpClientBuilder.create().build(); HttpComponentsClientHttpRequestFactory hrf = new HttpComponentsClientHttpRequestFactory(httpClient); hrf.setConnectTimeout(1234); hrf.setConnectionRequestTimeout(4321); - URI uri = new URI(baseUrl + "/status/ok"); + URI uri = URI.create(baseUrl + "/status/ok"); HttpComponentsClientHttpRequest request = (HttpComponentsClientHttpRequest) hrf.createRequest(uri, HttpMethod.GET); Object config = request.getHttpContext().getAttribute(HttpClientContext.REQUEST_CONFIG); @@ -71,7 +72,9 @@ public void assertCustomConfig() throws Exception { } @Test - public void defaultSettingsOfHttpClientMergedOnExecutorCustomization() throws Exception { + @SuppressWarnings("deprecation") + void defaultSettingsOfHttpClientMergedOnExecutorCustomization() throws Exception { + @SuppressWarnings("deprecation") RequestConfig defaultConfig = RequestConfig.custom().setConnectTimeout(1234, MILLISECONDS).build(); CloseableHttpClient client = mock(CloseableHttpClient.class, withSettings().extraInterfaces(Configurable.class)); @@ -90,7 +93,8 @@ public void defaultSettingsOfHttpClientMergedOnExecutorCustomization() throws Ex } @Test - public void localSettingsOverrideClientDefaultSettings() throws Exception { + @SuppressWarnings("deprecation") + void localSettingsOverrideClientDefaultSettings() throws Exception { RequestConfig defaultConfig = RequestConfig.custom() .setConnectTimeout(1234, MILLISECONDS) .setConnectionRequestTimeout(6789, MILLISECONDS) @@ -109,11 +113,12 @@ public void localSettingsOverrideClientDefaultSettings() throws Exception { } @Test - public void mergeBasedOnCurrentHttpClient() throws Exception { + @SuppressWarnings("deprecation") + void mergeBasedOnCurrentHttpClient() throws Exception { RequestConfig defaultConfig = RequestConfig.custom() .setConnectionRequestTimeout(1234, MILLISECONDS) .build(); - final CloseableHttpClient client = mock(CloseableHttpClient.class, + CloseableHttpClient client = mock(CloseableHttpClient.class, withSettings().extraInterfaces(Configurable.class)); Configurable configurable = (Configurable) client; given(configurable.getConfig()).willReturn(defaultConfig); @@ -141,7 +146,7 @@ public HttpClient getHttpClient() { } private RequestConfig retrieveRequestConfig(HttpComponentsClientHttpRequestFactory factory) throws Exception { - URI uri = new URI(baseUrl + "/status/ok"); + URI uri = URI.create(baseUrl + "/status/ok"); HttpComponentsClientHttpRequest request = (HttpComponentsClientHttpRequest) factory.createRequest(uri, HttpMethod.GET); return (RequestConfig) request.getHttpContext().getAttribute(HttpClientContext.REQUEST_CONFIG); diff --git a/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java index a41cdcea5168..71cbc9eae855 100644 --- a/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java @@ -61,7 +61,7 @@ void basic() throws Exception { interceptors.add(new NoOpInterceptor()); requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, interceptors); - ClientHttpRequest request = requestFactory.createRequest(new URI("https://example.com"), HttpMethod.GET); + ClientHttpRequest request = requestFactory.createRequest(URI.create("https://example.com"), HttpMethod.GET); ClientHttpResponse response = request.execute(); assertThat(((NoOpInterceptor) interceptors.get(0)).invoked).isTrue(); @@ -79,7 +79,7 @@ void noExecution() throws Exception { interceptors.add(new NoOpInterceptor()); requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, interceptors); - ClientHttpRequest request = requestFactory.createRequest(new URI("https://example.com"), HttpMethod.GET); + ClientHttpRequest request = requestFactory.createRequest(URI.create("https://example.com"), HttpMethod.GET); ClientHttpResponse response = request.execute(); assertThat(((NoOpInterceptor) interceptors.get(1)).invoked).isFalse(); @@ -103,7 +103,7 @@ void changeHeaders() throws Exception { @Override protected ClientHttpResponse executeInternal() throws IOException { List headerValues = getHeaders().get(headerName); - assertThat(headerValues.size()).isEqualTo(2); + assertThat(headerValues).hasSize(2); assertThat(headerValues.get(0)).isEqualTo(headerValue); assertThat(headerValues.get(1)).isEqualTo(otherValue); return responseMock; @@ -113,13 +113,13 @@ protected ClientHttpResponse executeInternal() throws IOException { requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); - ClientHttpRequest request = requestFactory.createRequest(new URI("https://example.com"), HttpMethod.GET); + ClientHttpRequest request = requestFactory.createRequest(URI.create("https://example.com"), HttpMethod.GET); request.execute(); } @Test void changeURI() throws Exception { - final URI changedUri = new URI("https://example.com/2"); + final URI changedUri = URI.create("https://example.com/2"); ClientHttpRequestInterceptor interceptor = (request, body, execution) -> execution.execute(new HttpRequestWrapper(request) { @Override @@ -139,7 +139,7 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); - ClientHttpRequest request = requestFactory.createRequest(new URI("https://example.com"), HttpMethod.GET); + ClientHttpRequest request = requestFactory.createRequest(URI.create("https://example.com"), HttpMethod.GET); request.execute(); } @@ -165,7 +165,7 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); - ClientHttpRequest request = requestFactory.createRequest(new URI("https://example.com"), HttpMethod.GET); + ClientHttpRequest request = requestFactory.createRequest(URI.create("https://example.com"), HttpMethod.GET); request.execute(); } @@ -177,7 +177,7 @@ void changeBody() throws Exception { requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); - ClientHttpRequest request = requestFactory.createRequest(new URI("https://example.com"), HttpMethod.GET); + ClientHttpRequest request = requestFactory.createRequest(URI.create("https://example.com"), HttpMethod.GET); request.execute(); assertThat(Arrays.equals(changedBody, requestMock.getBodyAsBytes())).isTrue(); } diff --git a/spring-web/src/test/java/org/springframework/http/client/MultipartBodyBuilderTests.java b/spring-web/src/test/java/org/springframework/http/client/MultipartBodyBuilderTests.java index cadff1575965..a87801e7406c 100644 --- a/spring-web/src/test/java/org/springframework/http/client/MultipartBodyBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/MultipartBodyBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -60,7 +60,7 @@ public void builder() { MultiValueMap> result = builder.build(); - assertThat(result.size()).isEqualTo(5); + assertThat(result).hasSize(5); HttpEntity resultEntity = result.getFirst("key"); assertThat(resultEntity).isNotNull(); assertThat(resultEntity.getBody()).isEqualTo(multipartData); diff --git a/spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpResponseTests.java b/spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpResponseTests.java index 2c28ad3a5508..8370ea6bd9ea 100644 --- a/spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpResponseTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/SimpleClientHttpResponseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/test/java/org/springframework/http/client/StreamingSimpleClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/StreamingSimpleClientHttpRequestFactoryTests.java index 9bc295a2537a..00350e33735f 100644 --- a/spring-web/src/test/java/org/springframework/http/client/StreamingSimpleClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/StreamingSimpleClientHttpRequestFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -55,7 +55,7 @@ public void interceptor() throws Exception { ClientHttpResponse response = null; try { - ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/echo"), HttpMethod.GET); + ClientHttpRequest request = factory.createRequest(URI.create(baseUrl + "/echo"), HttpMethod.GET); response = request.execute(); assertThat(response.getStatusCode()).as("Invalid response status").isEqualTo(HttpStatus.OK); HttpHeaders responseHeaders = response.getHeaders(); @@ -74,7 +74,7 @@ public void largeFileUpload() throws Exception { Random rnd = new Random(); ClientHttpResponse response = null; try { - ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/methods/post"), HttpMethod.POST); + ClientHttpRequest request = factory.createRequest(URI.create(baseUrl + "/methods/post"), HttpMethod.POST); final int BUF_SIZE = 4096; final int ITERATIONS = Integer.MAX_VALUE / BUF_SIZE; // final int contentLength = ITERATIONS * BUF_SIZE; diff --git a/spring-web/src/test/java/org/springframework/http/client/support/ProxyFactoryBeanTests.java b/spring-web/src/test/java/org/springframework/http/client/support/ProxyFactoryBeanTests.java index 0f2f8aa45748..33bda55cad76 100644 --- a/spring-web/src/test/java/org/springframework/http/client/support/ProxyFactoryBeanTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/support/ProxyFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 +19,6 @@ import java.net.InetSocketAddress; import java.net.Proxy; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -28,38 +27,31 @@ /** * @author Arjen Poutsma */ -public class ProxyFactoryBeanTests { +class ProxyFactoryBeanTests { - ProxyFactoryBean factoryBean; + private final ProxyFactoryBean factoryBean = new ProxyFactoryBean(); - @BeforeEach - public void setUp() { - factoryBean = new ProxyFactoryBean(); - } @Test - public void noType() { + void noType() { factoryBean.setType(null); - assertThatIllegalArgumentException().isThrownBy( - factoryBean::afterPropertiesSet); + assertThatIllegalArgumentException().isThrownBy(factoryBean::afterPropertiesSet); } @Test - public void noHostname() { + void noHostname() { factoryBean.setHostname(""); - assertThatIllegalArgumentException().isThrownBy( - factoryBean::afterPropertiesSet); + assertThatIllegalArgumentException().isThrownBy(factoryBean::afterPropertiesSet); } @Test - public void noPort() { + void noPort() { factoryBean.setHostname("example.com"); - assertThatIllegalArgumentException().isThrownBy( - factoryBean::afterPropertiesSet); + assertThatIllegalArgumentException().isThrownBy(factoryBean::afterPropertiesSet); } @Test - public void normal() { + void normal() { Proxy.Type type = Proxy.Type.HTTP; factoryBean.setType(type); String hostname = "example.com"; diff --git a/spring-web/src/test/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriterTests.java b/spring-web/src/test/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriterTests.java index 1db06c829e4f..0d7e336077c0 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriterTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriterTests.java @@ -127,7 +127,7 @@ public String getFilename() { .block(Duration.ofSeconds(5)); MultiValueMap requestParts = parse(this.response, hints); - assertThat(requestParts.size()).isEqualTo(7); + assertThat(requestParts).hasSize(7); Part part = requestParts.getFirst("name 1"); assertThat(part instanceof FormFieldPart).isTrue(); @@ -135,7 +135,7 @@ public String getFilename() { assertThat(((FormFieldPart) part).value()).isEqualTo("value 1"); List parts2 = requestParts.get("name 2"); - assertThat(parts2.size()).isEqualTo(2); + assertThat(parts2).hasSize(2); part = parts2.get(0); assertThat(part instanceof FormFieldPart).isTrue(); assertThat(part.name()).isEqualTo("name 2"); @@ -202,7 +202,7 @@ public void writeMultipartRelated() { assertThat(contentType.getParameter("charset")).isNull(); MultiValueMap requestParts = parse(this.response, hints); - assertThat(requestParts.size()).isEqualTo(2); + assertThat(requestParts).hasSize(2); assertThat(requestParts.getFirst("name 1").name()).isEqualTo("name 1"); assertThat(requestParts.getFirst("name 2").name()).isEqualTo("name 2"); } @@ -229,7 +229,7 @@ public void singleSubscriberWithResource() throws IOException { this.writer.write(result, null, MediaType.MULTIPART_FORM_DATA, this.response, hints).block(); MultiValueMap requestParts = parse(this.response, hints); - assertThat(requestParts.size()).isEqualTo(1); + assertThat(requestParts).hasSize(1); Part part = requestParts.getFirst("logo"); assertThat(part.name()).isEqualTo("logo"); @@ -281,7 +281,7 @@ public void customContentDisposition() throws IOException { this.response, hints).block(); MultiValueMap requestParts = parse(this.response, hints); - assertThat(requestParts.size()).isEqualTo(2); + assertThat(requestParts).hasSize(2); Part part = requestParts.getFirst("resource"); assertThat(part instanceof FilePart).isTrue(); diff --git a/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageWriterTests.java b/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageWriterTests.java index 03666d4d1cc0..825abaf05fa9 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageWriterTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -79,7 +79,7 @@ void write() { .block(Duration.ofSeconds(5)); MultiValueMap requestParts = parse(this.response, hints); - assertThat(requestParts.size()).isEqualTo(2); + assertThat(requestParts).hasSize(2); Part part = requestParts.getFirst("text part"); assertThat(part.name()).isEqualTo("text part"); diff --git a/spring-web/src/test/java/org/springframework/http/codec/multipart/PartHttpMessageWriterTests.java b/spring-web/src/test/java/org/springframework/http/codec/multipart/PartHttpMessageWriterTests.java index 6fe88f458b1c..b9f452a27b78 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/multipart/PartHttpMessageWriterTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/multipart/PartHttpMessageWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -52,22 +52,10 @@ public class PartHttpMessageWriterTests extends AbstractLeakCheckingTests { @Test public void canWrite() { - assertThat(this.writer.canWrite( - ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Object.class), - MediaType.MULTIPART_FORM_DATA)).isTrue(); - assertThat(this.writer.canWrite( - ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class), - MediaType.MULTIPART_FORM_DATA)).isTrue(); - assertThat(this.writer.canWrite( - ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Object.class), - MediaType.MULTIPART_MIXED)).isTrue(); - assertThat(this.writer.canWrite( - ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Object.class), - MediaType.MULTIPART_RELATED)).isTrue(); - - assertThat(this.writer.canWrite( - ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class), - MediaType.MULTIPART_FORM_DATA)).isFalse(); + assertThat(this.writer.canWrite(ResolvableType.forClass(Part.class), MediaType.MULTIPART_FORM_DATA)).isTrue(); + assertThat(this.writer.canWrite(ResolvableType.forClass(Part.class), MediaType.MULTIPART_MIXED)).isTrue(); + assertThat(this.writer.canWrite(ResolvableType.forClass(Part.class), MediaType.MULTIPART_RELATED)).isTrue(); + assertThat(this.writer.canWrite(ResolvableType.forClass(MultiValueMap.class), MediaType.MULTIPART_FORM_DATA)).isFalse(); } @Test @@ -96,7 +84,7 @@ void write() { .block(Duration.ofSeconds(5)); MultiValueMap requestParts = parse(this.response, hints); - assertThat(requestParts.size()).isEqualTo(2); + assertThat(requestParts).hasSize(2); Part part = requestParts.getFirst("text part"); assertThat(part.name()).isEqualTo("text part"); diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java index 9297a77c3204..aadb220fd0f2 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java @@ -64,8 +64,12 @@ import org.springframework.http.codec.json.Jackson2SmileEncoder; import org.springframework.http.codec.json.KotlinSerializationJsonDecoder; import org.springframework.http.codec.json.KotlinSerializationJsonEncoder; +import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader; +import org.springframework.http.codec.multipart.MultipartHttpMessageReader; import org.springframework.http.codec.multipart.MultipartHttpMessageWriter; +import org.springframework.http.codec.multipart.PartEventHttpMessageReader; import org.springframework.http.codec.multipart.PartEventHttpMessageWriter; +import org.springframework.http.codec.multipart.PartHttpMessageWriter; import org.springframework.http.codec.protobuf.KotlinSerializationProtobufDecoder; import org.springframework.http.codec.protobuf.KotlinSerializationProtobufEncoder; import org.springframework.http.codec.protobuf.ProtobufDecoder; @@ -92,7 +96,7 @@ public class ClientCodecConfigurerTests { @Test public void defaultReaders() { List> readers = this.configurer.getReaders(); - assertThat(readers.size()).isEqualTo(17); + assertThat(readers).hasSize(20); assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteBufferDecoder.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(DataBufferDecoder.class); @@ -103,6 +107,9 @@ public void defaultReaders() { assertThat(getNextDecoder(readers).getClass()).isEqualTo(ProtobufDecoder.class); // SPR-16804 assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(FormHttpMessageReader.class); + assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(DefaultPartHttpMessageReader.class); + assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(MultipartHttpMessageReader.class); + assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(PartEventHttpMessageReader.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationCborDecoder.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationJsonDecoder.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationProtobufDecoder.class); @@ -116,7 +123,7 @@ public void defaultReaders() { @Test public void defaultWriters() { List> writers = this.configurer.getWriters(); - assertThat(writers.size()).isEqualTo(17); + assertThat(writers).hasSize(18); assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteBufferEncoder.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(DataBufferEncoder.class); @@ -127,6 +134,7 @@ public void defaultWriters() { assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class); assertThat(writers.get(this.index.getAndIncrement()).getClass()).isEqualTo(MultipartHttpMessageWriter.class); assertThat(writers.get(this.index.getAndIncrement()).getClass()).isEqualTo(PartEventHttpMessageWriter.class); + assertThat(writers.get(this.index.getAndIncrement()).getClass()).isEqualTo(PartHttpMessageWriter.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationCborEncoder.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationJsonEncoder.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationProtobufEncoder.class); @@ -184,7 +192,7 @@ public void maxInMemorySize() { int size = 99; this.configurer.defaultCodecs().maxInMemorySize(size); List> readers = this.configurer.getReaders(); - assertThat(readers.size()).isEqualTo(17); + assertThat(readers).hasSize(20); assertThat(((ByteArrayDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size); assertThat(((ByteBufferDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size); assertThat(((DataBufferDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size); @@ -194,7 +202,9 @@ public void maxInMemorySize() { assertThat(((StringDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size); assertThat(((ProtobufDecoder) getNextDecoder(readers)).getMaxMessageSize()).isEqualTo(size); assertThat(((FormHttpMessageReader) nextReader(readers)).getMaxInMemorySize()).isEqualTo(size); - + assertThat(((DefaultPartHttpMessageReader) nextReader(readers)).getMaxInMemorySize()).isEqualTo(size); + nextReader(readers); + assertThat(((PartEventHttpMessageReader) nextReader(readers)).getMaxInMemorySize()).isEqualTo(size); assertThat(((KotlinSerializationCborDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size); assertThat(((KotlinSerializationJsonDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size); assertThat(((KotlinSerializationProtobufDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size); @@ -245,7 +255,7 @@ public void clonedConfigurer() { writers = findCodec(this.configurer.getWriters(), MultipartHttpMessageWriter.class).getPartWriters(); assertThat(sseDecoder).isNotSameAs(jackson2Decoder); - assertThat(writers).hasSize(15); + assertThat(writers).hasSize(18); } @Test // gh-24194 @@ -255,7 +265,7 @@ public void cloneShouldNotDropMultipartCodecs() { List> writers = findCodec(clone.getWriters(), MultipartHttpMessageWriter.class).getPartWriters(); - assertThat(writers).hasSize(15); + assertThat(writers).hasSize(18); } @Test @@ -269,7 +279,7 @@ public void cloneShouldNotBeImpactedByChangesToOriginal() { List> writers = findCodec(clone.getWriters(), MultipartHttpMessageWriter.class).getPartWriters(); - assertThat(writers).hasSize(15); + assertThat(writers).hasSize(18); } private Decoder getNextDecoder(List> readers) { diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java index 432fe15928b4..40b554afd1cd 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java @@ -58,6 +58,12 @@ import org.springframework.http.codec.json.Jackson2SmileEncoder; import org.springframework.http.codec.json.KotlinSerializationJsonDecoder; import org.springframework.http.codec.json.KotlinSerializationJsonEncoder; +import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader; +import org.springframework.http.codec.multipart.MultipartHttpMessageReader; +import org.springframework.http.codec.multipart.MultipartHttpMessageWriter; +import org.springframework.http.codec.multipart.PartEventHttpMessageReader; +import org.springframework.http.codec.multipart.PartEventHttpMessageWriter; +import org.springframework.http.codec.multipart.PartHttpMessageWriter; import org.springframework.http.codec.protobuf.KotlinSerializationProtobufDecoder; import org.springframework.http.codec.protobuf.KotlinSerializationProtobufEncoder; import org.springframework.http.codec.protobuf.ProtobufDecoder; @@ -87,7 +93,7 @@ class CodecConfigurerTests { @Test void defaultReaders() { List> readers = this.configurer.getReaders(); - assertThat(readers.size()).isEqualTo(16); + assertThat(readers).hasSize(19); assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteBufferDecoder.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(DataBufferDecoder.class); @@ -97,6 +103,9 @@ void defaultReaders() { assertStringDecoder(getNextDecoder(readers), true); assertThat(getNextDecoder(readers).getClass()).isEqualTo(ProtobufDecoder.class); assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(FormHttpMessageReader.class); + assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(DefaultPartHttpMessageReader.class); + assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(MultipartHttpMessageReader.class); + assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(PartEventHttpMessageReader.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationCborDecoder.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationJsonDecoder.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationProtobufDecoder.class); @@ -109,7 +118,7 @@ void defaultReaders() { @Test void defaultWriters() { List> writers = this.configurer.getWriters(); - assertThat(writers.size()).isEqualTo(15); + assertThat(writers).hasSize(18); assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteBufferEncoder.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(DataBufferEncoder.class); @@ -118,6 +127,9 @@ void defaultWriters() { assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageWriter.class); assertStringEncoder(getNextEncoder(writers), true); assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class); + assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(MultipartHttpMessageWriter.class); + assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(PartEventHttpMessageWriter.class); + assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(PartHttpMessageWriter.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationCborEncoder.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationJsonEncoder.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationProtobufEncoder.class); @@ -149,7 +161,7 @@ void defaultAndCustomReaders() { List> readers = this.configurer.getReaders(); - assertThat(readers.size()).isEqualTo(20); + assertThat(readers).hasSize(23); assertThat(getNextDecoder(readers)).isSameAs(customDecoder1); assertThat(readers.get(this.index.getAndIncrement())).isSameAs(customReader1); assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class); @@ -161,6 +173,9 @@ void defaultAndCustomReaders() { assertThat(getNextDecoder(readers).getClass()).isEqualTo(StringDecoder.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(ProtobufDecoder.class); assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(FormHttpMessageReader.class); + assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(DefaultPartHttpMessageReader.class); + assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(MultipartHttpMessageReader.class); + assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(PartEventHttpMessageReader.class); assertThat(getNextDecoder(readers)).isSameAs(customDecoder2); assertThat(readers.get(this.index.getAndIncrement())).isSameAs(customReader2); assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationCborDecoder.class); @@ -194,7 +209,7 @@ void defaultAndCustomWriters() { List> writers = this.configurer.getWriters(); - assertThat(writers.size()).isEqualTo(19); + assertThat(writers).hasSize(22); assertThat(getNextEncoder(writers)).isSameAs(customEncoder1); assertThat(writers.get(this.index.getAndIncrement())).isSameAs(customWriter1); assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class); @@ -205,6 +220,9 @@ void defaultAndCustomWriters() { assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageWriter.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(CharSequenceEncoder.class); assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class); + assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(MultipartHttpMessageWriter.class); + assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(PartEventHttpMessageWriter.class); + assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(PartHttpMessageWriter.class); assertThat(getNextEncoder(writers)).isSameAs(customEncoder2); assertThat(writers.get(this.index.getAndIncrement())).isSameAs(customWriter2); assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationCborEncoder.class); @@ -240,7 +258,7 @@ void defaultsOffCustomReaders() { List> readers = this.configurer.getReaders(); - assertThat(readers.size()).isEqualTo(4); + assertThat(readers).hasSize(4); assertThat(getNextDecoder(readers)).isSameAs(customDecoder1); assertThat(readers.get(this.index.getAndIncrement())).isSameAs(customReader1); assertThat(getNextDecoder(readers)).isSameAs(customDecoder2); @@ -271,7 +289,7 @@ void defaultsOffWithCustomWriters() { List> writers = this.configurer.getWriters(); - assertThat(writers.size()).isEqualTo(4); + assertThat(writers).hasSize(4); assertThat(getNextEncoder(writers)).isSameAs(customEncoder1); assertThat(writers.get(this.index.getAndIncrement())).isSameAs(customWriter1); assertThat(getNextEncoder(writers)).isSameAs(customEncoder2); diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java index fe648a221615..571001beb6ef 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/ServerCodecConfigurerTests.java @@ -64,7 +64,9 @@ import org.springframework.http.codec.json.KotlinSerializationJsonEncoder; import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader; import org.springframework.http.codec.multipart.MultipartHttpMessageReader; +import org.springframework.http.codec.multipart.MultipartHttpMessageWriter; import org.springframework.http.codec.multipart.PartEventHttpMessageReader; +import org.springframework.http.codec.multipart.PartEventHttpMessageWriter; import org.springframework.http.codec.multipart.PartHttpMessageWriter; import org.springframework.http.codec.protobuf.KotlinSerializationProtobufDecoder; import org.springframework.http.codec.protobuf.KotlinSerializationProtobufEncoder; @@ -92,7 +94,7 @@ public class ServerCodecConfigurerTests { @Test public void defaultReaders() { List> readers = this.configurer.getReaders(); - assertThat(readers.size()).isEqualTo(19); + assertThat(readers).hasSize(19); assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteBufferDecoder.class); assertThat(getNextDecoder(readers).getClass()).isEqualTo(DataBufferDecoder.class); @@ -117,7 +119,7 @@ public void defaultReaders() { @Test public void defaultWriters() { List> writers = this.configurer.getWriters(); - assertThat(writers.size()).isEqualTo(17); + assertThat(writers).hasSize(19); assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteBufferEncoder.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(DataBufferEncoder.class); @@ -126,6 +128,8 @@ public void defaultWriters() { assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageWriter.class); assertStringEncoder(getNextEncoder(writers), true); assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class); + assertThat(writers.get(this.index.getAndIncrement()).getClass()).isEqualTo(MultipartHttpMessageWriter.class); + assertThat(writers.get(this.index.getAndIncrement()).getClass()).isEqualTo(PartEventHttpMessageWriter.class); assertThat(writers.get(this.index.getAndIncrement()).getClass()).isEqualTo(PartHttpMessageWriter.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationCborEncoder.class); assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationJsonEncoder.class); diff --git a/spring-web/src/test/java/org/springframework/http/codec/xml/Jaxb2XmlDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/xml/Jaxb2XmlDecoderTests.java index 548e2d9c847a..0b6cf72288e6 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/xml/Jaxb2XmlDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/xml/Jaxb2XmlDecoderTests.java @@ -99,7 +99,7 @@ public void splitOneBranches() { StepVerifier.create(result) .consumeNextWith(events -> { - assertThat(events.size()).isEqualTo(8); + assertThat(events).hasSize(8); assertStartElement(events.get(0), "pojo"); assertStartElement(events.get(1), "foo"); assertCharacters(events.get(2), "foofoo"); @@ -121,7 +121,7 @@ public void splitMultipleBranches() { StepVerifier.create(result) .consumeNextWith(events -> { - assertThat(events.size()).isEqualTo(8); + assertThat(events).hasSize(8); assertStartElement(events.get(0), "pojo"); assertStartElement(events.get(1), "foo"); assertCharacters(events.get(2), "foo"); @@ -132,7 +132,7 @@ public void splitMultipleBranches() { assertEndElement(events.get(7), "pojo"); }) .consumeNextWith(events -> { - assertThat(events.size()).isEqualTo(8); + assertThat(events).hasSize(8); assertStartElement(events.get(0), "pojo"); assertStartElement(events.get(1), "foo"); assertCharacters(events.get(2), "foofoo"); diff --git a/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java index 79f45c1e4185..f5ed0c539b72 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java @@ -22,50 +22,44 @@ import javax.imageio.ImageIO; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; -import org.springframework.util.FileCopyUtils; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; /** * Unit tests for BufferedImageHttpMessageConverter. + * * @author Arjen Poutsma * @author Rossen Stoyanchev */ -public class BufferedImageHttpMessageConverterTests { +class BufferedImageHttpMessageConverterTests { - private BufferedImageHttpMessageConverter converter; + private final BufferedImageHttpMessageConverter converter = new BufferedImageHttpMessageConverter(); - @BeforeEach - public void setUp() { - converter = new BufferedImageHttpMessageConverter(); - } @Test - public void canRead() { + void canRead() { assertThat(converter.canRead(BufferedImage.class, null)).as("Image not supported").isTrue(); assertThat(converter.canRead(BufferedImage.class, new MediaType("image", "png"))).as("Image not supported").isTrue(); } @Test - public void canWrite() { + void canWrite() { assertThat(converter.canWrite(BufferedImage.class, null)).as("Image not supported").isTrue(); assertThat(converter.canWrite(BufferedImage.class, new MediaType("image", "png"))).as("Image not supported").isTrue(); assertThat(converter.canWrite(BufferedImage.class, new MediaType("*", "*"))).as("Image not supported").isTrue(); } @Test - public void read() throws IOException { + void read() throws IOException { Resource logo = new ClassPathResource("logo.jpg", BufferedImageHttpMessageConverterTests.class); - byte[] body = FileCopyUtils.copyToByteArray(logo.getInputStream()); - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(logo.getInputStream()); inputMessage.getHeaders().setContentType(new MediaType("image", "jpeg")); BufferedImage result = converter.read(BufferedImage.class, inputMessage); assertThat(result.getHeight()).as("Invalid height").isEqualTo(500); @@ -73,13 +67,13 @@ public void read() throws IOException { } @Test - public void write() throws IOException { + void write() throws IOException { Resource logo = new ClassPathResource("logo.jpg", BufferedImageHttpMessageConverterTests.class); BufferedImage body = ImageIO.read(logo.getFile()); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); MediaType contentType = new MediaType("image", "png"); converter.write(body, contentType, outputMessage); - assertThat(outputMessage.getWrittenHeaders().getContentType()).as("Invalid content type").isEqualTo(contentType); + assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content type").isEqualTo(contentType); assertThat(outputMessage.getBodyAsBytes().length > 0).as("Invalid size").isTrue(); BufferedImage result = ImageIO.read(new ByteArrayInputStream(outputMessage.getBodyAsBytes())); assertThat(result.getHeight()).as("Invalid height").isEqualTo(500); @@ -87,14 +81,14 @@ public void write() throws IOException { } @Test - public void writeDefaultContentType() throws IOException { + void writeDefaultContentType() throws IOException { Resource logo = new ClassPathResource("logo.jpg", BufferedImageHttpMessageConverterTests.class); MediaType contentType = new MediaType("image", "png"); converter.setDefaultContentType(contentType); BufferedImage body = ImageIO.read(logo.getFile()); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); converter.write(body, new MediaType("*", "*"), outputMessage); - assertThat(outputMessage.getWrittenHeaders().getContentType()).as("Invalid content type").isEqualTo(contentType); + assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content type").isEqualTo(contentType); assertThat(outputMessage.getBodyAsBytes().length > 0).as("Invalid size").isTrue(); BufferedImage result = ImageIO.read(new ByteArrayInputStream(outputMessage.getBodyAsBytes())); assertThat(result.getHeight()).as("Invalid height").isEqualTo(500); diff --git a/spring-web/src/test/java/org/springframework/http/converter/ByteArrayHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/ByteArrayHttpMessageConverterTests.java index 63c9b5aa3221..612ec0ce4cde 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/ByteArrayHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/ByteArrayHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,8 +22,8 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java index 57da8a22e1f1..8f65b32e05da 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java @@ -41,14 +41,16 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; +import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; +import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; import static org.springframework.http.MediaType.MULTIPART_MIXED; import static org.springframework.http.MediaType.TEXT_XML; @@ -60,6 +62,7 @@ * @author Arjen Poutsma * @author Rossen Stoyanchev * @author Sam Brannen + * @author Sebastien Deleuze */ public class FormHttpMessageConverterTests { @@ -155,6 +158,90 @@ public void writeForm() throws IOException { @Test public void writeMultipart() throws Exception { + + MultiValueMap parts = new LinkedMultiValueMap<>(); + parts.add("name 1", "value 1"); + parts.add("name 2", "value 2+1"); + parts.add("name 2", "value 2+2"); + parts.add("name 3", null); + + Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg"); + parts.add("logo", logo); + + // SPR-12108 + Resource utf8 = new ClassPathResource("/org/springframework/http/converter/logo.jpg") { + @Override + public String getFilename() { + return "Hall\u00F6le.jpg"; + } + }; + parts.add("utf8", utf8); + + MyBean myBean = new MyBean(); + myBean.setString("foo"); + HttpHeaders entityHeaders = new HttpHeaders(); + entityHeaders.setContentType(APPLICATION_JSON); + HttpEntity entity = new HttpEntity<>(myBean, entityHeaders); + parts.add("json", entity); + + Map parameters = new LinkedHashMap<>(2); + parameters.put("charset", StandardCharsets.UTF_8.name()); + parameters.put("foo", "bar"); + + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + this.converter.write(parts, new MediaType("multipart", "form-data", parameters), outputMessage); + + final MediaType contentType = outputMessage.getHeaders().getContentType(); + assertThat(contentType.getParameters()).containsKeys("charset", "boundary", "foo"); // gh-21568, gh-25839 + + // see if Commons FileUpload can read what we wrote + FileUpload fileUpload = new FileUpload(); + fileUpload.setFileItemFactory(new DiskFileItemFactory()); + RequestContext requestContext = new MockHttpOutputMessageRequestContext(outputMessage); + List items = fileUpload.parseRequest(requestContext); + assertThat(items).hasSize(6); + FileItem item = items.get(0); + assertThat(item.isFormField()).isTrue(); + assertThat(item.getFieldName()).isEqualTo("name 1"); + assertThat(item.getString()).isEqualTo("value 1"); + + item = items.get(1); + assertThat(item.isFormField()).isTrue(); + assertThat(item.getFieldName()).isEqualTo("name 2"); + assertThat(item.getString()).isEqualTo("value 2+1"); + + item = items.get(2); + assertThat(item.isFormField()).isTrue(); + assertThat(item.getFieldName()).isEqualTo("name 2"); + assertThat(item.getString()).isEqualTo("value 2+2"); + + item = items.get(3); + assertThat(item.isFormField()).isFalse(); + assertThat(item.getFieldName()).isEqualTo("logo"); + assertThat(item.getName()).isEqualTo("logo.jpg"); + assertThat(item.getContentType()).isEqualTo("image/jpeg"); + assertThat(item.getSize()).isEqualTo(logo.getFile().length()); + + item = items.get(4); + assertThat(item.isFormField()).isFalse(); + assertThat(item.getFieldName()).isEqualTo("utf8"); + assertThat(item.getName()).isEqualTo("Hall\u00F6le.jpg"); + assertThat(item.getContentType()).isEqualTo("image/jpeg"); + assertThat(item.getSize()).isEqualTo(logo.getFile().length()); + + item = items.get(5); + assertThat(item.getFieldName()).isEqualTo("json"); + assertThat(item.getContentType()).isEqualTo("application/json"); + } + + @Test + public void writeMultipartWithSourceHttpMessageConverter() throws Exception { + + converter.setPartConverters(List.of( + new StringHttpMessageConverter(), + new ResourceHttpMessageConverter(), + new SourceHttpMessageConverter<>())); + MultiValueMap parts = new LinkedMultiValueMap<>(); parts.add("name 1", "value 1"); parts.add("name 2", "value 2+1"); @@ -194,7 +281,7 @@ public String getFilename() { fileUpload.setFileItemFactory(new DiskFileItemFactory()); RequestContext requestContext = new MockHttpOutputMessageRequestContext(outputMessage); List items = fileUpload.parseRequest(requestContext); - assertThat(items.size()).isEqualTo(6); + assertThat(items).hasSize(6); FileItem item = items.get(0); assertThat(item.isFormField()).isTrue(); assertThat(item.getFieldName()).isEqualTo("name 1"); @@ -254,7 +341,7 @@ public void writeMultipartOrder() throws Exception { fileUpload.setFileItemFactory(new DiskFileItemFactory()); RequestContext requestContext = new MockHttpOutputMessageRequestContext(outputMessage); List items = fileUpload.parseRequest(requestContext); - assertThat(items.size()).isEqualTo(2); + assertThat(items).hasSize(2); FileItem item = items.get(0); assertThat(item.isFormField()).isTrue(); diff --git a/spring-web/src/test/java/org/springframework/http/converter/ObjectToStringHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/ObjectToStringHttpMessageConverterTests.java index b212dc3668b0..51ac8b6fcd57 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/ObjectToStringHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/ObjectToStringHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -92,7 +92,7 @@ public void canWrite() { @Test public void defaultCharset() throws IOException { - this.converter.write(Integer.valueOf(5), null, response); + this.converter.write(5, null, response); assertThat(servletResponse.getCharacterEncoding()).isEqualTo("ISO-8859-1"); } @@ -124,20 +124,20 @@ public void writeAcceptCharsetTurnedOff() throws IOException { @Test public void read() throws IOException { - Short shortValue = Short.valueOf((short) 781); + Short shortValue = (short) 781; MockHttpServletRequest request = new MockHttpServletRequest(); request.setContentType(MediaType.TEXT_PLAIN_VALUE); request.setContent(shortValue.toString().getBytes(StringHttpMessageConverter.DEFAULT_CHARSET)); assertThat(this.converter.read(Short.class, new ServletServerHttpRequest(request))).isEqualTo(shortValue); - Float floatValue = Float.valueOf(123); + Float floatValue = 123F; request = new MockHttpServletRequest(); request.setContentType(MediaType.TEXT_PLAIN_VALUE); request.setCharacterEncoding("UTF-16"); request.setContent(floatValue.toString().getBytes("UTF-16")); assertThat(this.converter.read(Float.class, new ServletServerHttpRequest(request))).isEqualTo(floatValue); - Long longValue = Long.valueOf(55819182821331L); + Long longValue = 55819182821331L; request = new MockHttpServletRequest(); request.setContentType(MediaType.TEXT_PLAIN_VALUE); request.setCharacterEncoding("UTF-8"); @@ -158,7 +158,7 @@ public void write() throws IOException { @Test public void writeUtf16() throws IOException { MediaType contentType = new MediaType("text", "plain", StandardCharsets.UTF_16); - this.converter.write(Integer.valueOf(958), contentType, this.response); + this.converter.write(958, contentType, this.response); assertThat(this.servletResponse.getCharacterEncoding()).isEqualTo("UTF-16"); assertThat(this.servletResponse.getContentType().startsWith(MediaType.TEXT_PLAIN_VALUE)).isTrue(); diff --git a/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java index 3bf1b9d3e3eb..2c471c825c0d 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -29,9 +29,9 @@ import org.springframework.core.io.Resource; import org.springframework.http.ContentDisposition; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.util.FileCopyUtils; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java index 12ba56e15457..3ec8d8694c91 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/ResourceRegionHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -34,8 +34,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRange; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.util.StringUtils; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-web/src/test/java/org/springframework/http/converter/StringHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/StringHttpMessageConverterTests.java index 4be55e2b821a..039da7a36cd1 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/StringHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/StringHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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,8 +24,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java index 05e09b3700b3..6df1114dd242 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java @@ -33,8 +33,8 @@ import org.springframework.core.testfixture.xml.XmlContent; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; @@ -78,7 +78,7 @@ public void read() throws IOException { assertThat(result.getTitle()).isEqualTo("title"); assertThat(result.getSubtitle().getValue()).isEqualTo("subtitle"); List entries = result.getEntries(); - assertThat(entries.size()).isEqualTo(2); + assertThat(entries).hasSize(2); Entry entry1 = (Entry) entries.get(0); assertThat(entry1.getId()).isEqualTo("id1"); diff --git a/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java index 72dda284efd2..feb24f197a06 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java @@ -29,8 +29,8 @@ import org.springframework.core.testfixture.xml.XmlContent; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; @@ -65,7 +65,7 @@ public void read() throws IOException { assertThat(result.getDescription()).isEqualTo("description"); List items = result.getItems(); - assertThat(items.size()).isEqualTo(2); + assertThat(items).hasSize(2); Item item1 = (Item) items.get(0); assertThat(item1.getTitle()).isEqualTo("title1"); diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/GsonHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/GsonHttpMessageConverterTests.java index 0bbabb216dba..6ece484d1678 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/GsonHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/GsonHttpMessageConverterTests.java @@ -31,9 +31,9 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -189,7 +189,7 @@ public void readAndWriteGenerics() throws Exception { Type genericType = beansList.getGenericType(); List results = (List) converter.read(genericType, MyBeanListHolder.class, inputMessage); - assertThat(results.size()).isEqualTo(1); + assertThat(results).hasSize(1); MyBean result = results.get(0); assertThat(result.getString()).isEqualTo("Foo"); assertThat(result.getNumber()).isEqualTo(42); @@ -216,7 +216,7 @@ public void readAndWriteParameterizedType() throws Exception { inputMessage.getHeaders().setContentType(new MediaType("application", "json")); List results = (List) converter.read(beansList.getType(), null, inputMessage); - assertThat(results.size()).isEqualTo(1); + assertThat(results).hasSize(1); MyBean result = results.get(0); assertThat(result.getString()).isEqualTo("Foo"); assertThat(result.getNumber()).isEqualTo(42); @@ -243,7 +243,7 @@ public void writeParameterizedBaseType() throws Exception { inputMessage.getHeaders().setContentType(new MediaType("application", "json")); List results = (List) converter.read(beansList.getType(), null, inputMessage); - assertThat(results.size()).isEqualTo(1); + assertThat(results).hasSize(1); MyBean result = results.get(0); assertThat(result.getString()).isEqualTo("Foo"); assertThat(result.getNumber()).isEqualTo(42); diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/JsonbHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/JsonbHttpMessageConverterTests.java index c0825f88d2f3..a6727fa39146 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/JsonbHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/JsonbHttpMessageConverterTests.java @@ -31,9 +31,9 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -189,7 +189,7 @@ public void readAndWriteGenerics() throws Exception { Type genericType = beansList.getGenericType(); List results = (List) converter.read(genericType, MyBeanListHolder.class, inputMessage); - assertThat(results.size()).isEqualTo(1); + assertThat(results).hasSize(1); MyBean result = results.get(0); assertThat(result.getString()).isEqualTo("Foo"); assertThat(result.getNumber()).isEqualTo(42); @@ -215,7 +215,7 @@ public void readAndWriteParameterizedType() throws Exception { inputMessage.getHeaders().setContentType(new MediaType("application", "json")); List results = (List) converter.read(beansList.getType(), null, inputMessage); - assertThat(results.size()).isEqualTo(1); + assertThat(results).hasSize(1); MyBean result = results.get(0); assertThat(result.getString()).isEqualTo("Foo"); assertThat(result.getNumber()).isEqualTo(42); @@ -242,7 +242,7 @@ public void writeParameterizedBaseType() throws Exception { inputMessage.getHeaders().setContentType(new MediaType("application", "json")); List results = (List) converter.read(beansList.getType(), null, inputMessage); - assertThat(results.size()).isEqualTo(1); + assertThat(results).hasSize(1); MyBean result = results.get(0); assertThat(result.getString()).isEqualTo("Foo"); assertThat(result.getNumber()).isEqualTo(42); diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java index 415f910be1a2..b5a24d345437 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java @@ -41,11 +41,11 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.lang.Nullable; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -268,7 +268,7 @@ protected JavaType getJavaType(Type type, @Nullable Class contextClass) { inputMessage.getHeaders().setContentType(new MediaType("application", "json")); List results = (List) converter.read(List.class, inputMessage); - assertThat(results.size()).isEqualTo(1); + assertThat(results).hasSize(1); MyBean result = results.get(0); assertThat(result.getString()).isEqualTo("Foo"); assertThat(result.getNumber()).isEqualTo(42); @@ -299,7 +299,7 @@ public void readAndWriteParameterizedType() throws Exception { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); List results = (List) converter.read(beansList.getType(), null, inputMessage); - assertThat(results.size()).isEqualTo(1); + assertThat(results).hasSize(1); MyBean result = results.get(0); assertThat(result.getString()).isEqualTo("Foo"); assertThat(result.getNumber()).isEqualTo(42); @@ -331,7 +331,7 @@ public void writeParameterizedBaseType() throws Exception { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); List results = (List) converter.read(beansList.getType(), null, inputMessage); - assertThat(results.size()).isEqualTo(1); + assertThat(results).hasSize(1); MyBean result = results.get(0); assertThat(result.getString()).isEqualTo("Foo"); assertThat(result.getNumber()).isEqualTo(42); diff --git a/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java index 7e03d05da84e..d0128842681b 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverterTests.java @@ -26,10 +26,10 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.protobuf.Msg; import org.springframework.protobuf.SecondMsg; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; diff --git a/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverterTests.java index bafe9aa29023..ad3971548c20 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverterTests.java @@ -23,10 +23,10 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.protobuf.Msg; import org.springframework.protobuf.SecondMsg; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java index f11d4b015e43..ad57f7d69071 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java @@ -23,8 +23,8 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.within; diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverterTests.java index b4b06af76db8..270037767e8b 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2CollectionHttpMessageConverterTests.java @@ -34,8 +34,8 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import org.springframework.http.MockHttpInputMessage; import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.testfixture.http.MockHttpInputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -145,7 +145,7 @@ protected XMLInputFactory createXmlInputFactory() { try { Collection result = converter.read(rootElementListType, null, inputMessage); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); assertThat(result.iterator().next().external).isEqualTo(""); } catch (HttpMessageNotReadableException ex) { @@ -173,7 +173,7 @@ protected XMLInputFactory createXmlInputFactory() { }; Collection result = c.read(rootElementListType, null, inputMessage); - assertThat(result.size()).isEqualTo(1); + assertThat(result).hasSize(1); assertThat(result.iterator().next().external).isEqualTo("Foo Bar"); } diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java index 614711c1ca6c..b7c3fbfaf77c 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java @@ -37,9 +37,9 @@ import org.springframework.core.io.Resource; import org.springframework.core.testfixture.xml.XmlContent; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java index 7ce1722b97e1..f78becdb6c8d 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java @@ -26,10 +26,10 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.json.MappingJacksonValue; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java index 888ad9b44ba7..620c3f0d3a9f 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/MarshallingHttpMessageConverterTests.java @@ -25,14 +25,14 @@ import org.springframework.beans.TypeMismatchException; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.oxm.Marshaller; import org.springframework.oxm.MarshallingFailureException; import org.springframework.oxm.Unmarshaller; import org.springframework.oxm.UnmarshallingFailureException; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java index 6de216b504ad..dccf6e1be8a5 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java @@ -43,10 +43,10 @@ import org.springframework.core.io.Resource; import org.springframework.core.testfixture.xml.XmlContent; import org.springframework.http.MediaType; -import org.springframework.http.MockHttpInputMessage; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.util.FileCopyUtils; +import org.springframework.web.testfixture.http.MockHttpInputMessage; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java index 6b24f916134d..d2ccc761e7b0 100644 --- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java @@ -59,7 +59,7 @@ void getMethod() { @Test void getUriForSimplePath() throws URISyntaxException { - URI uri = new URI("https://example.com/path"); + URI uri = URI.create("https://example.com/path"); mockRequest.setScheme(uri.getScheme()); mockRequest.setServerName(uri.getHost()); mockRequest.setServerPort(uri.getPort()); @@ -70,7 +70,7 @@ void getUriForSimplePath() throws URISyntaxException { @Test void getUriWithQueryString() throws URISyntaxException { - URI uri = new URI("https://example.com/path?query"); + URI uri = URI.create("https://example.com/path?query"); mockRequest.setScheme(uri.getScheme()); mockRequest.setServerName(uri.getHost()); mockRequest.setServerPort(uri.getPort()); @@ -86,7 +86,7 @@ void getUriWithQueryParam() throws URISyntaxException { mockRequest.setServerName("example.com"); mockRequest.setRequestURI("/path"); mockRequest.setQueryString("query=foo"); - assertThat(request.getURI()).isEqualTo(new URI("https://example.com/path?query=foo")); + assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/path?query=foo")); } @Test // SPR-16414 @@ -96,12 +96,12 @@ void getUriWithMalformedQueryParam() throws URISyntaxException { mockRequest.setServerName("example.com"); mockRequest.setRequestURI("/path"); mockRequest.setQueryString("query=foo%%x"); - assertThat(request.getURI()).isEqualTo(new URI("https://example.com/path")); + assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/path")); } @Test // SPR-13876 void getUriWithEncoding() throws URISyntaxException { - URI uri = new URI("https://example.com/%E4%B8%AD%E6%96%87" + + URI uri = URI.create("https://example.com/%E4%B8%AD%E6%96%87" + "?redirect=https%3A%2F%2Fgithub.com%2Fspring-projects%2Fspring-framework"); mockRequest.setScheme(uri.getScheme()); mockRequest.setServerName(uri.getHost()); @@ -155,7 +155,7 @@ void getHeadersWithEmptyContentTypeAndEncoding() { void getHeadersWithWildcardContentType() { mockRequest.setContentType("*/*"); mockRequest.removeHeader("Content-Type"); - assertThat(request.getHeaders()).as("Invalid content-type should not raise exception").hasSize(0); + assertThat(request.getHeaders()).as("Invalid content-type should not raise exception").isEmpty(); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/observation/DefaultServerRequestObservationConventionTests.java b/spring-web/src/test/java/org/springframework/http/server/observation/DefaultServerRequestObservationConventionTests.java similarity index 98% rename from spring-web/src/test/java/org/springframework/http/observation/DefaultServerRequestObservationConventionTests.java rename to spring-web/src/test/java/org/springframework/http/server/observation/DefaultServerRequestObservationConventionTests.java index 9f7e61eb957a..9964f6bf7b14 100644 --- a/spring-web/src/test/java/org/springframework/http/observation/DefaultServerRequestObservationConventionTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/observation/DefaultServerRequestObservationConventionTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.http.observation; +package org.springframework.http.server.observation; import io.micrometer.common.KeyValue; import io.micrometer.observation.Observation; diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/AsyncIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/AsyncIntegrationTests.java index a7c9e22d8301..1f7a964a7cf1 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/AsyncIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/AsyncIntegrationTests.java @@ -51,7 +51,7 @@ protected AsyncHandler createHttpHandler() { void basicTest(HttpServer httpServer) throws Exception { startServer(httpServer); - URI url = new URI("http://localhost:" + port); + URI url = URI.create("http://localhost:" + port); @SuppressWarnings("resource") ResponseEntity response = new RestTemplate().exchange(RequestEntity.get(url).build(), String.class); diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ChannelSendOperatorTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ChannelSendOperatorTests.java index 3de547ec2f23..b3a94b79a18c 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ChannelSendOperatorTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ChannelSendOperatorTests.java @@ -66,7 +66,7 @@ void completionBeforeFirstItem() throws Exception { assertThat(signal).isNotNull(); assertThat(signal.isOnComplete()).as("Unexpected signal: " + signal).isTrue(); - assertThat(this.writer.items.size()).isEqualTo(0); + assertThat(this.writer.items).isEmpty(); assertThat(this.writer.completed).isTrue(); } @@ -78,7 +78,7 @@ void writeOneItem() throws Exception { assertThat(signal).isNotNull(); assertThat(signal.isOnComplete()).as("Unexpected signal: " + signal).isTrue(); - assertThat(this.writer.items.size()).isEqualTo(1); + assertThat(this.writer.items).hasSize(1); assertThat(this.writer.items.get(0)).isEqualTo("one"); assertThat(this.writer.completed).isTrue(); } @@ -93,7 +93,7 @@ void writeMultipleItems() { assertThat(signal).isNotNull(); assertThat(signal.isOnComplete()).as("Unexpected signal: " + signal).isTrue(); - assertThat(this.writer.items.size()).isEqualTo(3); + assertThat(this.writer.items).hasSize(3); assertThat(this.writer.items.get(0)).isEqualTo("one"); assertThat(this.writer.items.get(1)).isEqualTo("two"); assertThat(this.writer.items.get(2)).isEqualTo("three"); @@ -117,7 +117,7 @@ void errorAfterMultipleItems() { assertThat(signal).isNotNull(); assertThat(signal.getThrowable()).as("Unexpected signal: " + signal).isSameAs(error); - assertThat(this.writer.items.size()).isEqualTo(3); + assertThat(this.writer.items).hasSize(3); assertThat(this.writer.items.get(0)).isEqualTo("1"); assertThat(this.writer.items.get(1)).isEqualTo("2"); assertThat(this.writer.items.get(2)).isEqualTo("3"); diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java index 13b29e63d2ec..85550cf4943c 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -51,25 +51,25 @@ protected HttpHandler createHttpHandler() { public void basicTest(HttpServer httpServer) throws Exception { startServer(httpServer); - URI url = new URI("http://localhost:" + port); + URI url = URI.create("http://localhost:" + port); String header = "SID=31d4d96e407aad42; lang=en-US"; @SuppressWarnings("resource") ResponseEntity response = new RestTemplate().exchange( RequestEntity.get(url).header("Cookie", header).build(), Void.class); Map> requestCookies = this.cookieHandler.requestCookies; - assertThat(requestCookies.size()).isEqualTo(2); + assertThat(requestCookies).hasSize(2); List list = requestCookies.get("SID"); - assertThat(list.size()).isEqualTo(1); + assertThat(list).hasSize(1); assertThat(list.iterator().next().getValue()).isEqualTo("31d4d96e407aad42"); list = requestCookies.get("lang"); - assertThat(list.size()).isEqualTo(1); + assertThat(list).hasSize(1); assertThat(list.iterator().next().getValue()).isEqualTo("en-US"); List headerValues = response.getHeaders().get("Set-Cookie"); - assertThat(headerValues.size()).isEqualTo(2); + assertThat(headerValues).hasSize(2); List cookie0 = splitCookie(headerValues.get(0)); assertThat(cookie0.remove("SID=31d4d96e407aad42")).as("SID").isTrue(); diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/EchoHandlerIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/EchoHandlerIntegrationTests.java index f3e6e546bcd6..aedf61a0fa07 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/EchoHandlerIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/EchoHandlerIntegrationTests.java @@ -53,7 +53,7 @@ public void echo(HttpServer httpServer) throws Exception { RestTemplate restTemplate = new RestTemplate(); byte[] body = randomBytes(); - RequestEntity request = RequestEntity.post(new URI("http://localhost:" + port)).body(body); + RequestEntity request = RequestEntity.post(URI.create("http://localhost:" + port)).body(body); ResponseEntity response = restTemplate.exchange(request, byte[].class); assertThat(response.getBody()).isEqualTo(body); diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ErrorHandlerIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ErrorHandlerIntegrationTests.java index ca7f22481aab..bcb336e2c10f 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ErrorHandlerIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ErrorHandlerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -53,7 +53,7 @@ void responseBodyError(HttpServer httpServer) throws Exception { RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(NO_OP_ERROR_HANDLER); - URI url = new URI("http://localhost:" + port + "/response-body-error"); + URI url = URI.create("http://localhost:" + port + "/response-body-error"); ResponseEntity response = restTemplate.getForEntity(url, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); @@ -67,7 +67,7 @@ void handlingError(HttpServer httpServer) throws Exception { RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(NO_OP_ERROR_HANDLER); - URI url = new URI("http://localhost:" + port + "/handling-error"); + URI url = URI.create("http://localhost:" + port + "/handling-error"); ResponseEntity response = restTemplate.getForEntity(url, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); @@ -81,7 +81,7 @@ void emptyPathSegments(HttpServer httpServer) throws Exception { RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(NO_OP_ERROR_HANDLER); - URI url = new URI("http://localhost:" + port + "//"); + URI url = URI.create("http://localhost:" + port + "//"); ResponseEntity response = restTemplate.getForEntity(url, String.class); // Jetty 10+ rejects empty path segments, see https://github.com/eclipse/jetty.project/issues/6302, diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java index 8f89a6b3bbde..8fdfc3c5efc9 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java @@ -64,7 +64,7 @@ void getFirstWithUnknownHeaderShouldReturnNull(MultiValueMap hea void sizeWithMultipleValuesForHeaderShouldCountHeaders(MultiValueMap headers) { headers.add("TestHeader", "first"); headers.add("TestHeader", "second"); - assertThat(headers.size()).isEqualTo(1); + assertThat(headers).hasSize(1); } @ParameterizedHeadersTest @@ -72,7 +72,7 @@ void keySetShouldNotDuplicateHeaderNames(MultiValueMap headers) headers.add("TestHeader", "first"); headers.add("OtherHeader", "test"); headers.add("TestHeader", "second"); - assertThat(headers.keySet().size()).isEqualTo(2); + assertThat(headers.keySet()).hasSize(2); } @ParameterizedHeadersTest @@ -94,7 +94,7 @@ void putShouldOverrideExisting(MultiValueMap headers) { headers.add("TestHeader", "first"); headers.put("TestHeader", Arrays.asList("override")); assertThat(headers.getFirst("TestHeader")).isEqualTo("override"); - assertThat(headers.get("TestHeader").size()).isEqualTo(1); + assertThat(headers.get("TestHeader")).hasSize(1); } @ParameterizedHeadersTest @@ -110,7 +110,7 @@ void shouldReflectChangesOnKeyset(MultiValueMap headers) { headers.add("TestHeader", "first"); assertThat(headers.keySet()).hasSize(1); headers.keySet().removeIf("TestHeader"::equals); - assertThat(headers.keySet()).hasSize(0); + assertThat(headers.keySet()).isEmpty(); } @ParameterizedHeadersTest diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ListenerWriteProcessorTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ListenerWriteProcessorTests.java index 4eabd3554db7..6971bbce8ece 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ListenerWriteProcessorTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ListenerWriteProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -64,7 +64,7 @@ public void writePublisherError() { this.processor.onError(new IllegalStateException()); assertThat(this.resultSubscriber.getError()).as("Error should flow to result publisher").isNotNull(); - assertThat(this.processor.getDiscardedBuffers().size()).isEqualTo(1); + assertThat(this.processor.getDiscardedBuffers()).hasSize(1); assertThat(this.processor.getDiscardedBuffers().get(0)).isSameAs(buffer); } @@ -80,7 +80,7 @@ public void ioExceptionDuringWrite() { this.processor.onNext(buffer); assertThat(this.resultSubscriber.getError()).as("Error should flow to result publisher").isNotNull(); - assertThat(this.processor.getDiscardedBuffers().size()).isEqualTo(1); + assertThat(this.processor.getDiscardedBuffers()).hasSize(1); assertThat(this.processor.getDiscardedBuffers().get(0)).isSameAs(buffer); } @@ -97,7 +97,7 @@ public void onNextWithoutDemand() { this.processor.onNext(buffer2); assertThat(this.resultSubscriber.getError()).as("Error should flow to result publisher").isNotNull(); - assertThat(this.processor.getDiscardedBuffers().size()).isEqualTo(2); + assertThat(this.processor.getDiscardedBuffers()).hasSize(2); assertThat(this.processor.getDiscardedBuffers().get(0)).isSameAs(buffer2); assertThat(this.processor.getDiscardedBuffers().get(1)).isSameAs(buffer1); } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/MultipartIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/MultipartHttpHandlerIntegrationTests.java similarity index 87% rename from spring-web/src/test/java/org/springframework/http/server/reactive/MultipartIntegrationTests.java rename to spring-web/src/test/java/org/springframework/http/server/reactive/MultipartHttpHandlerIntegrationTests.java index f41a40b8fd80..59c8a87da772 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/MultipartIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/MultipartHttpHandlerIntegrationTests.java @@ -42,11 +42,12 @@ import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpServer; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.type; /** * @author Sebastien Deleuze */ -class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTests { +class MultipartHttpHandlerIntegrationTests extends AbstractHttpHandlerIntegrationTests { @Override protected HttpHandler createHttpHandler() { @@ -60,7 +61,7 @@ void getFormParts(HttpServer httpServer) throws Exception { @SuppressWarnings("resource") RestTemplate restTemplate = new RestTemplate(); RequestEntity> request = RequestEntity - .post(new URI("http://localhost:" + port + "/form-parts")) + .post(URI.create("http://localhost:" + port + "/form-parts")) .contentType(MediaType.MULTIPART_FORM_DATA) .body(generateBody()); ResponseEntity response = restTemplate.exchange(request, Void.class); @@ -94,10 +95,10 @@ private Mono assertGetFormParts(ServerWebExchange exchange) { return exchange .getMultipartData() .doOnNext(parts -> { - assertThat(parts.size()).isEqualTo(2); - assertThat(parts.containsKey("fooPart")).isTrue(); + assertThat(parts).hasSize(2); + assertThat(parts).containsKey("fooPart"); assertFooPart(parts.getFirst("fooPart")); - assertThat(parts.containsKey("barPart")).isTrue(); + assertThat(parts).containsKey("barPart"); assertBarPart(parts.getFirst("barPart")); }) .then(); @@ -105,9 +106,9 @@ private Mono assertGetFormParts(ServerWebExchange exchange) { private void assertFooPart(Part part) { assertThat(part.name()).isEqualTo("fooPart"); - boolean condition = part instanceof FilePart; - assertThat(condition).isTrue(); - assertThat(((FilePart) part).filename()).isEqualTo("foo.txt"); + assertThat(part) + .asInstanceOf(type(FilePart.class)) + .extracting(FilePart::filename).isEqualTo("foo.txt"); StepVerifier.create(DataBufferUtils.join(part.content())) .consumeNextWith(buffer -> { @@ -121,9 +122,9 @@ private void assertFooPart(Part part) { private void assertBarPart(Part part) { assertThat(part.name()).isEqualTo("barPart"); - boolean condition = part instanceof FormFieldPart; - assertThat(condition).isTrue(); - assertThat(((FormFieldPart) part).value()).isEqualTo("bar"); + assertThat(part) + .asInstanceOf(type(FormFieldPart.class)) + .extracting(FormFieldPart::value).isEqualTo("bar"); } } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/RandomHandlerIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/RandomHandlerIntegrationTests.java index a6c862350cdd..350673828ba5 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/RandomHandlerIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/RandomHandlerIntegrationTests.java @@ -63,12 +63,12 @@ void random(HttpServer httpServer) throws Exception { RestTemplate restTemplate = new RestTemplate(); byte[] body = randomBytes(); - RequestEntity request = RequestEntity.post(new URI("http://localhost:" + port)).body(body); + RequestEntity request = RequestEntity.post(URI.create("http://localhost:" + port)).body(body); ResponseEntity response = restTemplate.exchange(request, byte[].class); assertThat(response.getBody()).isNotNull(); assertThat(response.getHeaders().getContentLength()).isEqualTo(RESPONSE_SIZE); - assertThat(response.getBody().length).isEqualTo(RESPONSE_SIZE); + assertThat(response.getBody()).hasSize(RESPONSE_SIZE); } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestIntegrationTests.java index f0c91f6e91d3..34ed853aa778 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -44,7 +44,7 @@ protected CheckRequestHandler createHttpHandler() { void checkUri(HttpServer httpServer) throws Exception { startServer(httpServer); - URI url = new URI("http://localhost:" + port + "/foo?param=bar"); + URI url = URI.create("http://localhost:" + port + "/foo?param=bar"); RequestEntity request = RequestEntity.post(url).build(); @SuppressWarnings("resource") ResponseEntity response = new RestTemplate().exchange(request, Void.class); diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java index d88f32d41194..7f39a9dfab7b 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -53,13 +53,13 @@ public class ServerHttpRequestTests { @Test public void queryParamsNone() throws Exception { MultiValueMap params = createRequest("/path").getQueryParams(); - assertThat(params.size()).isEqualTo(0); + assertThat(params).isEmpty(); } @Test public void queryParams() throws Exception { MultiValueMap params = createRequest("/path?a=A&b=B").getQueryParams(); - assertThat(params.size()).isEqualTo(2); + assertThat(params).hasSize(2); assertThat(params.get("a")).isEqualTo(Collections.singletonList("A")); assertThat(params.get("b")).isEqualTo(Collections.singletonList("B")); } @@ -67,28 +67,28 @@ public void queryParams() throws Exception { @Test public void queryParamsWithMultipleValues() throws Exception { MultiValueMap params = createRequest("/path?a=1&a=2").getQueryParams(); - assertThat(params.size()).isEqualTo(1); + assertThat(params).hasSize(1); assertThat(params.get("a")).isEqualTo(Arrays.asList("1", "2")); } @Test // SPR-15140 public void queryParamsWithEncodedValue() throws Exception { MultiValueMap params = createRequest("/path?a=%20%2B+%C3%A0").getQueryParams(); - assertThat(params.size()).isEqualTo(1); + assertThat(params).hasSize(1); assertThat(params.get("a")).isEqualTo(Collections.singletonList(" + \u00e0")); } @Test public void queryParamsWithEmptyValue() throws Exception { MultiValueMap params = createRequest("/path?a=").getQueryParams(); - assertThat(params.size()).isEqualTo(1); + assertThat(params).hasSize(1); assertThat(params.get("a")).isEqualTo(Collections.singletonList("")); } @Test public void queryParamsWithNoValue() throws Exception { MultiValueMap params = createRequest("/path?a").getQueryParams(); - assertThat(params.size()).isEqualTo(1); + assertThat(params).hasSize(1); assertThat(params.get("a")).isEqualTo(Collections.singletonList(null)); } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java index afb7d42b6089..e907e7ca7a89 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java @@ -67,7 +67,7 @@ void writeWith() { assertThat(response.headersWritten).isTrue(); assertThat(response.cookiesWritten).isTrue(); - assertThat(response.body.size()).isEqualTo(3); + assertThat(response.body).hasSize(3); assertThat(new String(response.body.get(0).toByteBuffer().array(), StandardCharsets.UTF_8)).isEqualTo("a"); assertThat(new String(response.body.get(1).toByteBuffer().array(), StandardCharsets.UTF_8)).isEqualTo("b"); assertThat(new String(response.body.get(2).toByteBuffer().array(), StandardCharsets.UTF_8)).isEqualTo("c"); @@ -83,7 +83,7 @@ void writeAndFlushWithFluxOfDefaultDataBuffer() { assertThat(response.headersWritten).isTrue(); assertThat(response.cookiesWritten).isTrue(); - assertThat(response.body.size()).isEqualTo(1); + assertThat(response.body).hasSize(1); assertThat(new String(response.body.get(0).toByteBuffer().array(), StandardCharsets.UTF_8)).isEqualTo("foo"); } @@ -138,7 +138,7 @@ void beforeCommitWithComplete() { assertThat(response.cookiesWritten).isTrue(); assertThat(response.getCookies().getFirst("ID")).isSameAs(cookie); - assertThat(response.body.size()).isEqualTo(3); + assertThat(response.body).hasSize(3); assertThat(new String(response.body.get(0).toByteBuffer().array(), StandardCharsets.UTF_8)).isEqualTo("a"); assertThat(new String(response.body.get(1).toByteBuffer().array(), StandardCharsets.UTF_8)).isEqualTo("b"); assertThat(new String(response.body.get(2).toByteBuffer().array(), StandardCharsets.UTF_8)).isEqualTo("c"); diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpsRequestIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpsRequestIntegrationTests.java index 3dba514e8533..63a5d728c04a 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpsRequestIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpsRequestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -86,7 +86,7 @@ void stopServer() { @Test void checkUri() throws Exception { - URI url = new URI("https://localhost:" + port + "/foo?param=bar"); + URI url = URI.create("https://localhost:" + port + "/foo?param=bar"); RequestEntity request = RequestEntity.post(url).build(); ResponseEntity response = this.restTemplate.exchange(request, Void.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/WriteOnlyHandlerIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/WriteOnlyHandlerIntegrationTests.java index fd3217f44820..266d19d65e41 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/WriteOnlyHandlerIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/WriteOnlyHandlerIntegrationTests.java @@ -58,9 +58,8 @@ void writeOnly(HttpServer httpServer) throws Exception { RestTemplate restTemplate = new RestTemplate(); this.body = randomBytes(); - RequestEntity request = RequestEntity.post( - new URI("http://localhost:" + port)).body( - "".getBytes(StandardCharsets.UTF_8)); + RequestEntity request = RequestEntity.post(URI.create("http://localhost:" + port)) + .body("".getBytes(StandardCharsets.UTF_8)); ResponseEntity response = restTemplate.exchange(request, byte[].class); assertThat(response.getBody()).isEqualTo(body); diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ZeroCopyIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ZeroCopyIntegrationTests.java index 6e4d9a067b08..36c672a417db 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ZeroCopyIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ZeroCopyIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -59,7 +59,7 @@ void zeroCopy(HttpServer httpServer) throws Exception { startServer(httpServer); - URI url = new URI("http://localhost:" + port); + URI url = URI.create("http://localhost:" + port); RequestEntity request = RequestEntity.get(url).build(); @SuppressWarnings("resource") ResponseEntity response = new RestTemplate().exchange(request, byte[].class); diff --git a/spring-web/src/test/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConventionTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/observation/DefaultServerRequestObservationConventionTests.java similarity index 87% rename from spring-web/src/test/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConventionTests.java rename to spring-web/src/test/java/org/springframework/http/server/reactive/observation/DefaultServerRequestObservationConventionTests.java index 37b47b562357..138b52737ad6 100644 --- a/spring-web/src/test/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConventionTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/observation/DefaultServerRequestObservationConventionTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.http.observation.reactive; +package org.springframework.http.server.reactive.observation; import io.micrometer.common.KeyValue; import io.micrometer.observation.Observation; @@ -23,13 +23,12 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; import org.springframework.web.testfixture.server.MockServerWebExchange; -import org.springframework.web.util.pattern.PathPattern; -import org.springframework.web.util.pattern.PathPatternParser; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link DefaultServerRequestObservationConvention}. + * * @author Brian Clozel */ class DefaultServerRequestObservationConventionTests { @@ -45,22 +44,22 @@ void shouldHaveDefaultName() { @Test void shouldHaveContextualName() { ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/resource")); - ServerRequestObservationContext context = new ServerRequestObservationContext(exchange); + ServerRequestObservationContext context = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); assertThat(convention.getContextualName(context)).isEqualTo("http get"); } @Test void contextualNameShouldUsePathPatternWhenAvailable() { ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/resource")); - ServerRequestObservationContext context = new ServerRequestObservationContext(exchange); - context.setPathPattern(PathPatternParser.defaultInstance.parse("/test/{name}")); + ServerRequestObservationContext context = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); + context.setPathPattern("/test/{name}"); assertThat(convention.getContextualName(context)).isEqualTo("http get /test/{name}"); } @Test void supportsOnlyHttpRequestsObservationContext() { ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource")); - ServerRequestObservationContext context = new ServerRequestObservationContext(exchange); + ServerRequestObservationContext context = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); assertThat(this.convention.supportsContext(context)).isTrue(); assertThat(this.convention.supportsContext(new Observation.Context())).isFalse(); } @@ -69,7 +68,7 @@ void supportsOnlyHttpRequestsObservationContext() { void addsKeyValuesForExchange() { ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource")); exchange.getResponse().setRawStatusCode(201); - ServerRequestObservationContext context = new ServerRequestObservationContext(exchange); + ServerRequestObservationContext context = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); assertThat(this.convention.getLowCardinalityKeyValues(context)).hasSize(5) .contains(KeyValue.of("method", "POST"), KeyValue.of("uri", "UNKNOWN"), KeyValue.of("status", "201"), @@ -82,9 +81,8 @@ void addsKeyValuesForExchange() { void addsKeyValuesForExchangeWithPathPattern() { ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/resource")); exchange.getResponse().setRawStatusCode(200); - ServerRequestObservationContext context = new ServerRequestObservationContext(exchange); - PathPattern pathPattern = getPathPattern("/test/{name}"); - context.setPathPattern(pathPattern); + ServerRequestObservationContext context = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); + context.setPathPattern("/test/{name}"); assertThat(this.convention.getLowCardinalityKeyValues(context)).hasSize(5) .contains(KeyValue.of("method", "GET"), KeyValue.of("uri", "/test/{name}"), KeyValue.of("status", "200"), @@ -97,7 +95,7 @@ void addsKeyValuesForExchangeWithPathPattern() { @Test void addsKeyValuesForErrorExchange() { ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/resource")); - ServerRequestObservationContext context = new ServerRequestObservationContext(exchange); + ServerRequestObservationContext context = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); context.setError(new IllegalArgumentException("custom error")); exchange.getResponse().setRawStatusCode(500); @@ -111,7 +109,7 @@ void addsKeyValuesForErrorExchange() { @Test void addsKeyValuesForRedirectExchange() { ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/redirect")); - ServerRequestObservationContext context = new ServerRequestObservationContext(exchange); + ServerRequestObservationContext context = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); exchange.getResponse().setRawStatusCode(302); exchange.getResponse().getHeaders().add("Location", "https://example.org/other"); @@ -125,7 +123,7 @@ void addsKeyValuesForRedirectExchange() { @Test void addsKeyValuesForNotFoundExchange() { ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/notFound")); - ServerRequestObservationContext context = new ServerRequestObservationContext(exchange); + ServerRequestObservationContext context = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); exchange.getResponse().setRawStatusCode(404); assertThat(this.convention.getLowCardinalityKeyValues(context)).hasSize(5) @@ -138,7 +136,7 @@ void addsKeyValuesForNotFoundExchange() { @Test void addsKeyValuesForCancelledExchange() { ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/resource")); - ServerRequestObservationContext context = new ServerRequestObservationContext(exchange); + ServerRequestObservationContext context = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); context.setConnectionAborted(true); exchange.getResponse().setRawStatusCode(200); @@ -152,16 +150,11 @@ void addsKeyValuesForCancelledExchange() { @Test void supportsNullStatusCode() { ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/resource")); - ServerRequestObservationContext context = new ServerRequestObservationContext(exchange); + ServerRequestObservationContext context = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); assertThat(this.convention.getLowCardinalityKeyValues(context)) .contains(KeyValue.of("status", "UNKNOWN"), KeyValue.of("exception", "none"), KeyValue.of("outcome", "UNKNOWN")); } - private static PathPattern getPathPattern(String pattern) { - PathPatternParser pathPatternParser = new PathPatternParser(); - return pathPatternParser.parse(pattern); - } - } diff --git a/spring-web/src/test/java/org/springframework/protobuf/Msg.java b/spring-web/src/test/java/org/springframework/protobuf/Msg.java index 4ea6bdcb6189..b81eaa6e31ef 100644 --- a/spring-web/src/test/java/org/springframework/protobuf/Msg.java +++ b/spring-web/src/test/java/org/springframework/protobuf/Msg.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java b/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java index 428984a439ac..cbf9cfb71102 100644 --- a/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java +++ b/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java @@ -20,10 +20,13 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.function.BiFunction; import org.junit.jupiter.api.Test; import org.springframework.beans.testfixture.beans.TestBean; +import org.springframework.context.MessageSource; import org.springframework.context.support.StaticMessageSource; import org.springframework.core.MethodParameter; import org.springframework.http.HttpHeaders; @@ -35,6 +38,7 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingMatrixVariableException; import org.springframework.web.bind.MissingPathVariableException; @@ -243,11 +247,12 @@ void methodArgumentNotValidException() { MessageSourceTestHelper messageSourceHelper = new MessageSourceTestHelper(MethodArgumentNotValidException.class); BindingResult bindingResult = messageSourceHelper.initBindingResult(); - ErrorResponse ex = new MethodArgumentNotValidException(this.methodParameter, bindingResult); + MethodArgumentNotValidException ex = new MethodArgumentNotValidException(this.methodParameter, bindingResult); assertStatus(ex, HttpStatus.BAD_REQUEST); assertDetail(ex, "Invalid request content."); messageSourceHelper.assertDetailMessage(ex); + messageSourceHelper.assertErrorMessages(ex::resolveErrorMessages); assertThat(ex.getHeaders()).isEmpty(); } @@ -316,7 +321,7 @@ void serverErrorException() { ServerErrorException ex = new ServerErrorException("Failure", null); assertStatus(ex, HttpStatus.INTERNAL_SERVER_ERROR); - assertDetail(ex, null); + assertDetail(ex, "Failure"); assertDetailMessageCode(ex, null, new Object[] {ex.getReason()}); assertThat(ex.getHeaders()).isEmpty(); @@ -361,6 +366,7 @@ void webExchangeBindException() { assertStatus(ex, HttpStatus.BAD_REQUEST); assertDetail(ex, "Invalid request content."); messageSourceHelper.assertDetailMessage(ex); + messageSourceHelper.assertErrorMessages(ex::resolveErrorMessages); assertThat(ex.getHeaders()).isEmpty(); } @@ -444,12 +450,8 @@ public BindingResult initBindingResult() { } private void assertDetailMessage(ErrorResponse ex) { - StaticMessageSource messageSource = new StaticMessageSource(); - messageSource.addMessage(this.code, Locale.UK, "Failures {0}. nested failures: {1}"); - messageSource.addMessage("bean.invalid.A", Locale.UK, "Bean A message"); - messageSource.addMessage("bean.invalid.B", Locale.UK, "Bean B message"); - messageSource.addMessage("name.required", Locale.UK, "Required name message"); - messageSource.addMessage("age.min", Locale.UK, "Minimum age message"); + + StaticMessageSource messageSource = initMessageSource(); String message = messageSource.getMessage( ex.getDetailMessageCode(), ex.getDetailMessageArguments(), Locale.UK); @@ -465,6 +467,24 @@ private void assertDetailMessage(ErrorResponse ex) { "Failures ['Bean A message', 'Bean B message']. " + "nested failures: [name: 'Required name message', age: 'Minimum age message']"); } + + private void assertErrorMessages(BiFunction> expectedMessages) { + StaticMessageSource messageSource = initMessageSource(); + Map map = expectedMessages.apply(messageSource, Locale.UK); + + assertThat(map).hasSize(4).containsValues( + "'Bean A message'", "'Bean B message'", "name: 'Required name message'", "age: 'Minimum age message'"); + } + + private StaticMessageSource initMessageSource() { + StaticMessageSource messageSource = new StaticMessageSource(); + messageSource.addMessage(this.code, Locale.UK, "Failures {0}. nested failures: {1}"); + messageSource.addMessage("bean.invalid.A", Locale.UK, "Bean A message"); + messageSource.addMessage("bean.invalid.B", Locale.UK, "Bean B message"); + messageSource.addMessage("name.required", Locale.UK, "Required name message"); + messageSource.addMessage("age.min", Locale.UK, "Minimum age message"); + return messageSource; + } } } diff --git a/spring-web/src/test/java/org/springframework/web/accept/HeaderContentNegotiationStrategyTests.java b/spring-web/src/test/java/org/springframework/web/accept/HeaderContentNegotiationStrategyTests.java index 49f945cea1de..f14a1fdd6300 100644 --- a/spring-web/src/test/java/org/springframework/web/accept/HeaderContentNegotiationStrategyTests.java +++ b/spring-web/src/test/java/org/springframework/web/accept/HeaderContentNegotiationStrategyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -49,7 +49,7 @@ public void resolveMediaTypes() throws Exception { this.servletRequest.addHeader("Accept", "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"); List mediaTypes = this.strategy.resolveMediaTypes(this.webRequest); - assertThat(mediaTypes.size()).isEqualTo(4); + assertThat(mediaTypes).hasSize(4); assertThat(mediaTypes.get(0).toString()).isEqualTo("text/html"); assertThat(mediaTypes.get(1).toString()).isEqualTo("text/x-c"); assertThat(mediaTypes.get(2).toString()).isEqualTo("text/x-dvi;q=0.8"); @@ -62,7 +62,7 @@ public void resolveMediaTypesFromMultipleHeaderValues() throws Exception { this.servletRequest.addHeader("Accept", "text/x-dvi; q=0.8, text/x-c"); List mediaTypes = this.strategy.resolveMediaTypes(this.webRequest); - assertThat(mediaTypes.size()).isEqualTo(4); + assertThat(mediaTypes).hasSize(4); assertThat(mediaTypes.get(0).toString()).isEqualTo("text/html"); assertThat(mediaTypes.get(1).toString()).isEqualTo("text/x-c"); assertThat(mediaTypes.get(2).toString()).isEqualTo("text/x-dvi;q=0.8"); diff --git a/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java b/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java index f7e7a550455e..b9ace3bbeda8 100644 --- a/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java +++ b/spring-web/src/test/java/org/springframework/web/accept/MappingContentNegotiationStrategyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -42,7 +42,7 @@ public void resolveMediaTypes() throws Exception { List mediaTypes = strategy.resolveMediaTypes(null); - assertThat(mediaTypes.size()).isEqualTo(1); + assertThat(mediaTypes).hasSize(1); assertThat(mediaTypes.get(0).toString()).isEqualTo("application/json"); } @@ -73,7 +73,7 @@ public void resolveMediaTypesHandleNoMatch() throws Exception { List mediaTypes = strategy.resolveMediaTypes(null); - assertThat(mediaTypes.size()).isEqualTo(1); + assertThat(mediaTypes).hasSize(1); assertThat(mediaTypes.get(0).toString()).isEqualTo("application/xml"); } diff --git a/spring-web/src/test/java/org/springframework/web/bind/support/WebExchangeDataBinderTests.java b/spring-web/src/test/java/org/springframework/web/bind/support/WebExchangeDataBinderTests.java index 8eb211f56247..7fdd726d4b05 100644 --- a/spring-web/src/test/java/org/springframework/web/bind/support/WebExchangeDataBinderTests.java +++ b/spring-web/src/test/java/org/springframework/web/bind/support/WebExchangeDataBinderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -214,7 +214,7 @@ public void testMultipart() throws Exception { assertThat(bean.getSomeList()).isEqualTo(Arrays.asList("123", "abc")); assertThat(bean.getSomeArray()).isEqualTo(new String[] {"dec", "456"}); assertThat(bean.getPart().filename()).isEqualTo("foo.txt"); - assertThat(bean.getSomePartList().size()).isEqualTo(2); + assertThat(bean.getSomePartList()).hasSize(2); assertThat(bean.getSomePartList().get(0).filename()).isEqualTo("foo.txt"); assertThat(bean.getSomePartList().get(1).filename()).isEqualTo("spring.png"); } diff --git a/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderIntegrationTests.java index f53a7eb494a3..bf98d5de8065 100644 --- a/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -125,7 +125,7 @@ void partListBinding() { template.postForLocation(baseUrl + "/partlist", parts); assertThat(bean.getPartList()).isNotNull(); - assertThat(bean.getPartList().size()).isEqualTo(parts.get("partList").size()); + assertThat(bean.getPartList()).hasSize(parts.get("partList").size()); } diff --git a/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java b/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java index 5122f33ca6ce..ddd747582c3a 100644 --- a/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java +++ b/spring-web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -269,7 +269,7 @@ public void testMultipartFileAsStringArray() { MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(); request.addFile(new MockMultipartFile("stringArray", "Juergen".getBytes())); binder.bind(new ServletWebRequest(request)); - assertThat(target.getStringArray().length).isEqualTo(1); + assertThat(target.getStringArray()).hasSize(1); assertThat(target.getStringArray()[0]).isEqualTo("Juergen"); } @@ -283,7 +283,7 @@ public void testMultipartFilesAsStringArray() { request.addFile(new MockMultipartFile("stringArray", "Juergen".getBytes())); request.addFile(new MockMultipartFile("stringArray", "Eva".getBytes())); binder.bind(new ServletWebRequest(request)); - assertThat(target.getStringArray().length).isEqualTo(2); + assertThat(target.getStringArray()).hasSize(2); assertThat(target.getStringArray()[0]).isEqualTo("Juergen"); assertThat(target.getStringArray()[1]).isEqualTo("Eva"); } diff --git a/spring-web/src/test/java/org/springframework/web/client/AbstractMockWebServerTests.java b/spring-web/src/test/java/org/springframework/web/client/AbstractMockWebServerTests.java index a141941665cc..99f949d10570 100644 --- a/spring-web/src/test/java/org/springframework/web/client/AbstractMockWebServerTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/AbstractMockWebServerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -91,7 +91,7 @@ private MockResponse getRequest(RecordedRequest request, byte[] body, String con private MockResponse postRequest(RecordedRequest request, String expectedRequestContent, String location, String contentType, byte[] responseBody) { - assertThat(request.getHeaders().values(CONTENT_LENGTH).size()).isEqualTo(1); + assertThat(request.getHeaders().values(CONTENT_LENGTH)).hasSize(1); assertThat(Integer.parseInt(request.getHeader(CONTENT_LENGTH))).as("Invalid request content-length").isGreaterThan(0); String requestContentType = request.getHeader(CONTENT_TYPE); assertThat(requestContentType).as("No content-type").isNotNull(); diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java index b1076e844e20..d5f1018ccc15 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java @@ -199,7 +199,7 @@ void postForLocation(ClientHttpRequestFactory clientHttpRequestFactory) throws E setUpClient(clientHttpRequestFactory); URI location = template.postForLocation(baseUrl + "/{method}", helloWorld, "post"); - assertThat(location).as("Invalid location").isEqualTo(new URI(baseUrl + "/post/1")); + assertThat(location).as("Invalid location").isEqualTo(URI.create(baseUrl + "/post/1")); } @ParameterizedRestTemplateTest @@ -210,7 +210,7 @@ void postForLocationEntity(ClientHttpRequestFactory clientHttpRequestFactory) th entityHeaders.setContentType(new MediaType("text", "plain", StandardCharsets.ISO_8859_1)); HttpEntity entity = new HttpEntity<>(helloWorld, entityHeaders); URI location = template.postForLocation(baseUrl + "/{method}", entity, "post"); - assertThat(location).as("Invalid location").isEqualTo(new URI(baseUrl + "/post/1")); + assertThat(location).as("Invalid location").isEqualTo(URI.create(baseUrl + "/post/1")); } @ParameterizedRestTemplateTest @@ -274,7 +274,7 @@ void serverError(ClientHttpRequestFactory clientHttpRequestFactory) { void optionsForAllow(ClientHttpRequestFactory clientHttpRequestFactory) throws Exception { setUpClient(clientHttpRequestFactory); - Set allowed = template.optionsForAllow(new URI(baseUrl + "/get")); + Set allowed = template.optionsForAllow(URI.create(baseUrl + "/get")); assertThat(allowed).as("Invalid response").isEqualTo(Set.of(HttpMethod.GET, HttpMethod.OPTIONS, HttpMethod.HEAD, HttpMethod.TRACE)); } @@ -371,7 +371,7 @@ void exchangePost(ClientHttpRequestFactory clientHttpRequestFactory) throws Exce requestHeaders.setContentType(MediaType.TEXT_PLAIN); HttpEntity entity = new HttpEntity<>(helloWorld, requestHeaders); HttpEntity result = template.exchange(baseUrl + "/{method}", POST, entity, Void.class, "post"); - assertThat(result.getHeaders().getLocation()).as("Invalid location").isEqualTo(new URI(baseUrl + "/post/1")); + assertThat(result.getHeaders().getLocation()).as("Invalid location").isEqualTo(URI.create(baseUrl + "/post/1")); assertThat(result.hasBody()).isFalse(); } @@ -425,7 +425,7 @@ void jsonPostForObjectWithJacksonTypeInfoList(ClientHttpRequestFactory clientHtt list.add(new Bar("bar")); ParameterizedTypeReference typeReference = new ParameterizedTypeReference>() {}; RequestEntity> entity = RequestEntity - .post(new URI(baseUrl + "/jsonpost")) + .post(URI.create(baseUrl + "/jsonpost")) .contentType(new MediaType("application", "json", StandardCharsets.UTF_8)) .body(list, typeReference.getType()); String content = template.exchange(entity, String.class).getBody(); diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java index b85de7fc2f27..69afc5269c9e 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java @@ -122,7 +122,7 @@ void executeAddsServerErrorAsOutcome() throws Exception { mockSentRequest(GET, url); mockResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR); willThrow(new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR)) - .given(errorHandler).handleError(new URI(url), GET, response); + .given(errorHandler).handleError(URI.create(url), GET, response); assertThatExceptionOfType(HttpServerErrorException.class).isThrownBy(() -> template.execute(url, GET, null, null)); @@ -164,7 +164,7 @@ private void mockSentRequest(HttpMethod method, String uri) throws Exception { } private void mockSentRequest(HttpMethod method, String uri, HttpHeaders requestHeaders) throws Exception { - given(requestFactory.createRequest(new URI(uri), method)).willReturn(request); + given(requestFactory.createRequest(URI.create(uri), method)).willReturn(request); given(request.getHeaders()).willReturn(requestHeaders); given(request.getMethod()).willReturn(method); given(request.getURI()).willReturn(URI.create(uri)); diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java index 6aa8d48b9669..3057543b31c3 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java @@ -196,7 +196,7 @@ void errorHandling() throws Exception { mockSentRequest(GET, url); mockResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR); willThrow(new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR)) - .given(errorHandler).handleError(new URI(url), GET, response); + .given(errorHandler).handleError(URI.create(url), GET, response); assertThatExceptionOfType(HttpServerErrorException.class).isThrownBy(() -> template.execute(url, GET, null, null)); @@ -318,7 +318,7 @@ void postForLocation() throws Exception { mockResponseStatus(HttpStatus.OK); String helloWorld = "Hello World"; HttpHeaders responseHeaders = new HttpHeaders(); - URI expected = new URI("https://example.com/hotels"); + URI expected = URI.create("https://example.com/hotels"); responseHeaders.setLocation(expected); given(response.getHeaders()).willReturn(responseHeaders); @@ -336,7 +336,7 @@ void postForLocationEntityContentType() throws Exception { String helloWorld = "Hello World"; HttpHeaders responseHeaders = new HttpHeaders(); - URI expected = new URI("https://example.com/hotels"); + URI expected = URI.create("https://example.com/hotels"); responseHeaders.setLocation(expected); given(response.getHeaders()).willReturn(responseHeaders); @@ -357,7 +357,7 @@ void postForLocationEntityCustomHeader() throws Exception { mockTextPlainHttpMessageConverter(); mockResponseStatus(HttpStatus.OK); HttpHeaders responseHeaders = new HttpHeaders(); - URI expected = new URI("https://example.com/hotels"); + URI expected = URI.create("https://example.com/hotels"); responseHeaders.setLocation(expected); given(response.getHeaders()).willReturn(responseHeaders); @@ -762,7 +762,7 @@ private void mockSentRequest(HttpMethod method, String uri) throws Exception { } private void mockSentRequest(HttpMethod method, String uri, HttpHeaders requestHeaders) throws Exception { - given(requestFactory.createRequest(new URI(uri), method)).willReturn(request); + given(requestFactory.createRequest(URI.create(uri), method)).willReturn(request); given(request.getHeaders()).willReturn(requestHeaders); } diff --git a/spring-web/src/test/java/org/springframework/web/context/request/ServletRequestAttributesTests.java b/spring-web/src/test/java/org/springframework/web/context/request/ServletRequestAttributesTests.java index 373df295887a..a8b0a7f16be4 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/ServletRequestAttributesTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/ServletRequestAttributesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -152,7 +152,7 @@ public void skipImmutableString() { @Test public void skipImmutableCharacter() { - doSkipImmutableValue(Character.valueOf('x')); + doSkipImmutableValue('x'); } @Test diff --git a/spring-web/src/test/java/org/springframework/web/context/request/ServletWebRequestTests.java b/spring-web/src/test/java/org/springframework/web/context/request/ServletWebRequestTests.java index 42ab49c58380..3eac9237f328 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/ServletWebRequestTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/ServletWebRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -61,18 +61,18 @@ public void parameters() { servletRequest.addParameter("param2", "value2a"); assertThat(request.getParameter("param1")).isEqualTo("value1"); - assertThat(request.getParameterValues("param1").length).isEqualTo(1); + assertThat(request.getParameterValues("param1")).hasSize(1); assertThat(request.getParameterValues("param1")[0]).isEqualTo("value1"); assertThat(request.getParameter("param2")).isEqualTo("value2"); - assertThat(request.getParameterValues("param2").length).isEqualTo(2); + assertThat(request.getParameterValues("param2")).hasSize(2); assertThat(request.getParameterValues("param2")[0]).isEqualTo("value2"); assertThat(request.getParameterValues("param2")[1]).isEqualTo("value2a"); Map paramMap = request.getParameterMap(); - assertThat(paramMap.size()).isEqualTo(2); - assertThat(paramMap.get("param1").length).isEqualTo(1); + assertThat(paramMap).hasSize(2); + assertThat(paramMap.get("param1")).hasSize(1); assertThat(paramMap.get("param1")[0]).isEqualTo("value1"); - assertThat(paramMap.get("param2").length).isEqualTo(2); + assertThat(paramMap.get("param2")).hasSize(2); assertThat(paramMap.get("param2")[0]).isEqualTo("value2"); assertThat(paramMap.get("param2")[1]).isEqualTo("value2a"); } diff --git a/spring-web/src/test/java/org/springframework/web/context/request/SessionScopeTests.java b/spring-web/src/test/java/org/springframework/web/context/request/SessionScopeTests.java index 16121ffc78d1..4bf119959d26 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/SessionScopeTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/SessionScopeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -238,8 +238,8 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw @Override public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { - if (bean instanceof BeanNameAware) { - ((BeanNameAware) bean).setBeanName(null); + if (bean instanceof BeanNameAware beanNameAware) { + beanNameAware.setBeanName(null); } } diff --git a/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java b/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java index 1d0ddfbe1b07..f40cf23e144d 100644 --- a/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -68,7 +68,7 @@ public void startAsync() throws Exception { MockAsyncContext context = (MockAsyncContext) this.request.getAsyncContext(); assertThat(context).isNotNull(); assertThat(context.getTimeout()).as("Timeout value not set").isEqualTo((44 * 1000)); - assertThat(context.getListeners().size()).isEqualTo(1); + assertThat(context.getListeners()).hasSize(1); assertThat(context.getListeners().get(0)).isSameAs(this.asyncRequest); } @@ -81,7 +81,7 @@ public void startAsyncMultipleTimes() throws Exception { MockAsyncContext context = (MockAsyncContext) this.request.getAsyncContext(); assertThat(context).isNotNull(); - assertThat(context.getListeners().size()).isEqualTo(1); + assertThat(context.getListeners()).hasSize(1); } @Test diff --git a/spring-web/src/test/java/org/springframework/web/filter/FormContentFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/FormContentFilterTests.java index 1273628b19ff..6bfd2c0e5f12 100644 --- a/spring-web/src/test/java/org/springframework/web/filter/FormContentFilterTests.java +++ b/spring-web/src/test/java/org/springframework/web/filter/FormContentFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -200,7 +200,7 @@ public void getParameterMap() throws Exception { Map parameters = this.filterChain.getRequest().getParameterMap(); assertThat(this.filterChain.getRequest()).as("Request not wrapped").isNotSameAs(this.request); - assertThat(parameters.size()).isEqualTo(2); + assertThat(parameters).hasSize(2); assertThat(parameters.get("name")).isEqualTo(new String[] {"value1", "value2", "value3"}); assertThat(parameters.get("name4")).isEqualTo(new String[] {"value4"}); } diff --git a/spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java index f9df6e9ce4bf..354e5c50a48f 100644 --- a/spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java +++ b/spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java @@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; -import org.springframework.http.observation.ServerRequestObservationContext; +import org.springframework.http.server.observation.ServerRequestObservationContext; import org.springframework.web.testfixture.servlet.MockFilterChain; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import org.springframework.web.testfixture.servlet.MockHttpServletResponse; @@ -97,7 +97,18 @@ void filterShouldUnwrapServletException() { ServerRequestObservationContext context = (ServerRequestObservationContext) this.request .getAttribute(ServerHttpObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE); assertThat(context.getError()).isEqualTo(customError); - assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS"); + assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SERVER_ERROR"); + } + + @Test + void filterShouldSetDefaultErrorStatusForBubblingExceptions() { + assertThatThrownBy(() -> { + this.filter.doFilter(this.request, this.response, (request, response) -> { + throw new ServletException(new IllegalArgumentException("custom error")); + }); + }).isInstanceOf(ServletException.class); + assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SERVER_ERROR") + .hasLowCardinalityKeyValue("status", "500"); } private TestObservationRegistryAssert.TestObservationRegistryAssertReturningObservationContextAssert assertThatHttpObservation() { diff --git a/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java index cc384f8340b7..e37e147b6ed0 100644 --- a/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java +++ b/spring-web/src/test/java/org/springframework/web/filter/ShallowEtagHeaderFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/test/java/org/springframework/web/filter/reactive/ServerHttpObservationFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/reactive/ServerHttpObservationFilterTests.java index 0f84861dc247..4bef48c1149d 100644 --- a/spring-web/src/test/java/org/springframework/web/filter/reactive/ServerHttpObservationFilterTests.java +++ b/spring-web/src/test/java/org/springframework/web/filter/reactive/ServerHttpObservationFilterTests.java @@ -27,7 +27,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import org.springframework.http.observation.reactive.ServerRequestObservationContext; +import org.springframework.http.server.reactive.observation.ServerRequestObservationContext; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilterChain; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/InitBinderDataBinderFactoryTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/InitBinderDataBinderFactoryTests.java index ce284f935e71..08fd411307c4 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/InitBinderDataBinderFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/InitBinderDataBinderFactoryTests.java @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.convert.ConversionService; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.web.bind.WebDataBinder; @@ -128,7 +128,7 @@ private WebDataBinderFactory createFactory(String methodName, Class... parame InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(handler, method); handlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); handlerMethod.setDataBinderFactory(new DefaultDataBinderFactory(null)); - handlerMethod.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer()); + handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer()); return new InitBinderDataBinderFactory( Collections.singletonList(handlerMethod), this.bindingInitializer); diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java index 1275c08b6950..f859cb880f06 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,7 +22,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; @@ -177,7 +177,7 @@ public void updateModelBindingResult() throws Exception { assertThat(container.getModel().get(commandName)).isEqualTo(command); String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + commandName; assertThat(container.getModel().get(bindingResultKey)).isSameAs(dataBinder.getBindingResult()); - assertThat(container.getModel().size()).isEqualTo(2); + assertThat(container.getModel()).hasSize(2); } @Test @@ -240,7 +240,7 @@ public void updateModelWhenRedirecting() throws Exception { modelFactory.updateModel(this.webRequest, container); assertThat(container.getModel().get(queryParamName)).isEqualTo(queryParam); - assertThat(container.getModel().size()).isEqualTo(1); + assertThat(container.getModel()).hasSize(1); assertThat(this.attributeStore.retrieveAttribute(this.webRequest, attributeName)).isEqualTo(attribute); } @@ -252,7 +252,7 @@ private ModelFactory createModelFactory(String methodName, Class... parameter InvocableHandlerMethod modelMethod = createHandlerMethod(methodName, parameterTypes); modelMethod.setHandlerMethodArgumentResolvers(resolvers); modelMethod.setDataBinderFactory(null); - modelMethod.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer()); + modelMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer()); return new ModelFactory(Collections.singletonList(modelMethod), null, this.attributeHandler); } diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java index 2c1693a6065d..37e3fa49613f 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMapMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -122,7 +122,7 @@ public void resolveMapOfMultipartFile() throws Exception { boolean condition = result instanceof Map; assertThat(condition).isTrue(); Map resultMap = (Map) result; - assertThat(resultMap.size()).isEqualTo(2); + assertThat(resultMap).hasSize(2); assertThat(resultMap.get("mfile")).isEqualTo(expected1); assertThat(resultMap.get("other")).isEqualTo(expected2); } @@ -145,11 +145,11 @@ public void resolveMultiValueMapOfMultipartFile() throws Exception { boolean condition = result instanceof MultiValueMap; assertThat(condition).isTrue(); MultiValueMap resultMap = (MultiValueMap) result; - assertThat(resultMap.size()).isEqualTo(2); - assertThat(resultMap.get("mfilelist").size()).isEqualTo(2); + assertThat(resultMap).hasSize(2); + assertThat(resultMap.get("mfilelist")).hasSize(2); assertThat(resultMap.get("mfilelist").get(0)).isEqualTo(expected1); assertThat(resultMap.get("mfilelist").get(1)).isEqualTo(expected2); - assertThat(resultMap.get("other").size()).isEqualTo(1); + assertThat(resultMap.get("other")).hasSize(1); assertThat(resultMap.get("other").get(0)).isEqualTo(expected3); } @@ -170,7 +170,7 @@ public void resolveMapOfPart() throws Exception { boolean condition = result instanceof Map; assertThat(condition).isTrue(); Map resultMap = (Map) result; - assertThat(resultMap.size()).isEqualTo(2); + assertThat(resultMap).hasSize(2); assertThat(resultMap.get("mfile")).isEqualTo(expected1); assertThat(resultMap.get("other")).isEqualTo(expected2); } @@ -194,11 +194,11 @@ public void resolveMultiValueMapOfPart() throws Exception { boolean condition = result instanceof MultiValueMap; assertThat(condition).isTrue(); MultiValueMap resultMap = (MultiValueMap) result; - assertThat(resultMap.size()).isEqualTo(2); - assertThat(resultMap.get("mfilelist").size()).isEqualTo(2); + assertThat(resultMap).hasSize(2); + assertThat(resultMap.get("mfilelist")).hasSize(2); assertThat(resultMap.get("mfilelist").get(0)).isEqualTo(expected1); assertThat(resultMap.get("mfilelist").get(1)).isEqualTo(expected2); - assertThat(resultMap.get("other").size()).isEqualTo(1); + assertThat(resultMap.get("other")).hasSize(1); assertThat(resultMap.get("other").get(0)).isEqualTo(expected3); } diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java index 684d3d76ca43..b17783edf29c 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -225,7 +225,7 @@ public void resolveMultipartFileArray() throws Exception { boolean condition = result instanceof MultipartFile[]; assertThat(condition).isTrue(); MultipartFile[] parts = (MultipartFile[]) result; - assertThat(parts.length).isEqualTo(2); + assertThat(parts).hasSize(2); assertThat(expected1).isEqualTo(parts[0]); assertThat(expected2).isEqualTo(parts[1]); } @@ -309,7 +309,7 @@ public void resolvePartArray() throws Exception { boolean condition = result instanceof Part[]; assertThat(condition).isTrue(); Part[] parts = (Part[]) result; - assertThat(parts.length).isEqualTo(2); + assertThat(parts).hasSize(2); assertThat(expected1).isEqualTo(parts[0]); assertThat(expected2).isEqualTo(parts[1]); } diff --git a/spring-web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java b/spring-web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java index e69d363df15b..0d743bed6023 100644 --- a/spring-web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/support/InvocableHandlerMethodTests.java @@ -61,8 +61,8 @@ public void resolveArg() throws Exception { Object value = getInvocable(Integer.class, String.class).invokeForRequest(request, null); - assertThat(getStubResolver(0).getResolvedParameters().size()).isEqualTo(1); - assertThat(getStubResolver(1).getResolvedParameters().size()).isEqualTo(1); + assertThat(getStubResolver(0).getResolvedParameters()).hasSize(1); + assertThat(getStubResolver(1).getResolvedParameters()).hasSize(1); assertThat(value).isEqualTo("99-value"); assertThat(getStubResolver(0).getResolvedParameters().get(0).getParameterName()).isEqualTo("intArg"); assertThat(getStubResolver(1).getResolvedParameters().get(0).getParameterName()).isEqualTo("stringArg"); @@ -75,8 +75,8 @@ public void resolveNoArgValue() throws Exception { Object returnValue = getInvocable(Integer.class, String.class).invokeForRequest(request, null); - assertThat(getStubResolver(0).getResolvedParameters().size()).isEqualTo(1); - assertThat(getStubResolver(1).getResolvedParameters().size()).isEqualTo(1); + assertThat(getStubResolver(0).getResolvedParameters()).hasSize(1); + assertThat(getStubResolver(1).getResolvedParameters()).hasSize(1); assertThat(returnValue).isEqualTo("null-null"); } diff --git a/spring-web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java b/spring-web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java index ca21a9a092a2..aa59ba6244c8 100644 --- a/spring-web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/support/ModelAndViewContainerTests.java @@ -43,7 +43,7 @@ public void setup() { @Test public void getModel() { this.mavContainer.addAttribute("name", "value"); - assertThat(this.mavContainer.getModel().size()).isEqualTo(1); + assertThat(this.mavContainer.getModel()).hasSize(1); assertThat(this.mavContainer.getModel().get("name")).isEqualTo("value"); } @@ -53,7 +53,7 @@ public void redirectScenarioWithRedirectModel() { this.mavContainer.setRedirectModel(new ModelMap("name2", "value2")); this.mavContainer.setRedirectModelScenario(true); - assertThat(this.mavContainer.getModel().size()).isEqualTo(1); + assertThat(this.mavContainer.getModel()).hasSize(1); assertThat(this.mavContainer.getModel().get("name2")).isEqualTo("value2"); } @@ -64,7 +64,7 @@ public void redirectScenarioWithoutRedirectModel() { this.mavContainer.addAttribute("name", "value"); this.mavContainer.setRedirectModelScenario(true); - assertThat(this.mavContainer.getModel().size()).isEqualTo(1); + assertThat(this.mavContainer.getModel()).hasSize(1); assertThat(this.mavContainer.getModel().get("name")).isEqualTo("value"); } @@ -81,7 +81,7 @@ public void ignoreDefaultModelAndWithoutRedirectModel() { this.mavContainer.setRedirectModelScenario(true); this.mavContainer.addAttribute("name", "value"); - assertThat(this.mavContainer.getModel().size()).isEqualTo(1); + assertThat(this.mavContainer.getModel()).hasSize(1); assertThat(this.mavContainer.getModel().get("name")).isEqualTo("value"); } diff --git a/spring-web/src/test/java/org/springframework/web/multipart/support/DefaultMultipartHttpServletRequestTests.java b/spring-web/src/test/java/org/springframework/web/multipart/support/DefaultMultipartHttpServletRequestTests.java index 62abdfdbea2c..a7c2429abd51 100644 --- a/spring-web/src/test/java/org/springframework/web/multipart/support/DefaultMultipartHttpServletRequestTests.java +++ b/spring-web/src/test/java/org/springframework/web/multipart/support/DefaultMultipartHttpServletRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -64,7 +64,7 @@ public void parameterMap() { Map map = createMultipartRequest().getParameterMap(); - assertThat(map.size()).isEqualTo(3); + assertThat(map).hasSize(3); assertThat(map.get("key1")).isEqualTo(new String[] {"p1", "q1"}); assertThat(map.get("key2")).isEqualTo(new String[] {"p2"}); assertThat(map.get("key3")).isEqualTo(new String[] {"q3"}); diff --git a/spring-web/src/test/java/org/springframework/web/multipart/support/RequestPartServletServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/web/multipart/support/RequestPartServletServerHttpRequestTests.java index 28aebfa7e82c..2298ec981c42 100644 --- a/spring-web/src/test/java/org/springframework/web/multipart/support/RequestPartServletServerHttpRequestTests.java +++ b/spring-web/src/test/java/org/springframework/web/multipart/support/RequestPartServletServerHttpRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -58,7 +58,7 @@ public void getURI() throws Exception { this.mockRequest.addFile(new MockMultipartFile("part", "", "application/json", "content".getBytes("UTF-8"))); ServerHttpRequest request = new RequestPartServletServerHttpRequest(this.mockRequest, "part"); - URI uri = new URI("https://example.com/path?query"); + URI uri = URI.create("https://example.com/path?query"); this.mockRequest.setScheme(uri.getScheme()); this.mockRequest.setServerName(uri.getHost()); this.mockRequest.setServerPort(uri.getPort()); diff --git a/spring-web/src/test/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequestTests.java b/spring-web/src/test/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequestTests.java index 529639843c71..a7590bcf2369 100644 --- a/spring-web/src/test/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequestTests.java +++ b/spring-web/src/test/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -21,11 +21,11 @@ import org.junit.jupiter.api.Test; -import org.springframework.http.MockHttpOutputMessage; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import org.springframework.web.testfixture.servlet.MockPart; diff --git a/spring-web/src/test/java/org/springframework/web/server/adapter/DefaultServerWebExchangeCheckNotModifiedTests.java b/spring-web/src/test/java/org/springframework/web/server/adapter/DefaultServerWebExchangeCheckNotModifiedTests.java index 2ab3a3a43c94..e65e85837e66 100644 --- a/spring-web/src/test/java/org/springframework/web/server/adapter/DefaultServerWebExchangeCheckNotModifiedTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/adapter/DefaultServerWebExchangeCheckNotModifiedTests.java @@ -80,7 +80,7 @@ void checkNotModifiedHeaderAlreadySet() { assertThat(exchange.checkNotModified(currentDate)).isTrue(); assertThat(exchange.getResponse().getStatusCode().value()).isEqualTo(304); - assertThat(exchange.getResponse().getHeaders().get("Last-Modified").size()).isEqualTo(1); + assertThat(exchange.getResponse().getHeaders().get("Last-Modified")).hasSize(1); assertThat(exchange.getResponse().getHeaders().getFirst("Last-Modified")).isEqualTo(CURRENT_TIME); } diff --git a/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java b/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java index d4897b8291d6..e5bed965ff21 100644 --- a/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -18,7 +18,6 @@ import java.net.InetSocketAddress; import java.net.URI; -import java.net.URISyntaxException; import org.junit.jupiter.api.Test; @@ -31,9 +30,10 @@ /** * Unit tests for {@link ForwardedHeaderTransformer}. + * * @author Rossen Stoyanchev */ -public class ForwardedHeaderTransformerTests { +class ForwardedHeaderTransformerTests { private static final String BASE_URL = "https://example.com/path"; @@ -65,7 +65,7 @@ void xForwardedHeaders() throws Exception { headers.add("foo", "bar"); ServerHttpRequest request = this.requestMutator.apply(getRequest(headers)); - assertThat(request.getURI()).isEqualTo(new URI("https://84.198.58.199/path")); + assertThat(request.getURI()).isEqualTo(URI.create("https://84.198.58.199/path")); assertForwardedHeadersRemoved(request); } @@ -75,7 +75,7 @@ void forwardedHeader() throws Exception { headers.add("Forwarded", "host=84.198.58.199;proto=https"); ServerHttpRequest request = this.requestMutator.apply(getRequest(headers)); - assertThat(request.getURI()).isEqualTo(new URI("https://84.198.58.199/path")); + assertThat(request.getURI()).isEqualTo(URI.create("https://84.198.58.199/path")); assertForwardedHeadersRemoved(request); } @@ -85,7 +85,7 @@ void xForwardedPrefix() throws Exception { headers.add("X-Forwarded-Prefix", "/prefix"); ServerHttpRequest request = this.requestMutator.apply(getRequest(headers)); - assertThat(request.getURI()).isEqualTo(new URI("https://example.com/prefix/path")); + assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/prefix/path")); assertThat(request.getPath().value()).isEqualTo("/prefix/path"); assertForwardedHeadersRemoved(request); } @@ -95,13 +95,13 @@ void xForwardedPrefixShouldNotLeadToDecodedPath() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.add("X-Forwarded-Prefix", "/prefix"); ServerHttpRequest request = MockServerHttpRequest - .method(HttpMethod.GET, new URI("https://example.com/a%20b?q=a%2Bb")) + .method(HttpMethod.GET, URI.create("https://example.com/a%20b?q=a%2Bb")) .headers(headers) .build(); request = this.requestMutator.apply(request); - assertThat(request.getURI()).isEqualTo(new URI("https://example.com/prefix/a%20b?q=a%2Bb")); + assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/prefix/a%20b?q=a%2Bb")); assertThat(request.getPath().value()).isEqualTo("/prefix/a%20b"); assertForwardedHeadersRemoved(request); } @@ -112,7 +112,7 @@ void xForwardedPrefixTrailingSlash() throws Exception { headers.add("X-Forwarded-Prefix", "/prefix////"); ServerHttpRequest request = this.requestMutator.apply(getRequest(headers)); - assertThat(request.getURI()).isEqualTo(new URI("https://example.com/prefix/path")); + assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/prefix/path")); assertThat(request.getPath().value()).isEqualTo("/prefix/path"); assertForwardedHeadersRemoved(request); } @@ -123,13 +123,13 @@ void shouldNotDoubleEncode() throws Exception { headers.add("Forwarded", "host=84.198.58.199;proto=https"); ServerHttpRequest request = MockServerHttpRequest - .method(HttpMethod.GET, new URI("https://example.com/a%20b?q=a%2Bb")) + .method(HttpMethod.GET, URI.create("https://example.com/a%20b?q=a%2Bb")) .headers(headers) .build(); request = this.requestMutator.apply(request); - assertThat(request.getURI()).isEqualTo(new URI("https://84.198.58.199/a%20b?q=a%2Bb")); + assertThat(request.getURI()).isEqualTo(URI.create("https://84.198.58.199/a%20b?q=a%2Bb")); assertForwardedHeadersRemoved(request); } @@ -139,7 +139,7 @@ void shouldConcatenatePrefixes() throws Exception { headers.add("X-Forwarded-Prefix", "/first,/second"); ServerHttpRequest request = this.requestMutator.apply(getRequest(headers)); - assertThat(request.getURI()).isEqualTo(new URI("https://example.com/first/second/path")); + assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/first/second/path")); assertThat(request.getPath().value()).isEqualTo("/first/second/path"); assertForwardedHeadersRemoved(request); } @@ -150,20 +150,20 @@ void shouldConcatenatePrefixesWithTrailingSlashes() throws Exception { headers.add("X-Forwarded-Prefix", "/first/,/second//"); ServerHttpRequest request = this.requestMutator.apply(getRequest(headers)); - assertThat(request.getURI()).isEqualTo(new URI("https://example.com/first/second/path")); + assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/first/second/path")); assertThat(request.getPath().value()).isEqualTo("/first/second/path"); assertForwardedHeadersRemoved(request); } @Test - public void forwardedForNotPresent() throws URISyntaxException { + void forwardedForNotPresent() { HttpHeaders headers = new HttpHeaders(); headers.add("Forwarded", "host=84.198.58.199;proto=https"); InetSocketAddress remoteAddress = new InetSocketAddress("example.client", 47011); ServerHttpRequest request = MockServerHttpRequest - .method(HttpMethod.GET, new URI("https://example.com/a%20b?q=a%2Bb")) + .method(HttpMethod.GET, URI.create("https://example.com/a%20b?q=a%2Bb")) .remoteAddress(remoteAddress) .headers(headers) .build(); @@ -173,14 +173,14 @@ public void forwardedForNotPresent() throws URISyntaxException { } @Test - public void forwardedFor() throws URISyntaxException { + void forwardedFor() { HttpHeaders headers = new HttpHeaders(); headers.add("Forwarded", "for=\"203.0.113.195:4711\";host=84.198.58.199;proto=https"); InetSocketAddress remoteAddress = new InetSocketAddress("example.client", 47011); ServerHttpRequest request = MockServerHttpRequest - .method(HttpMethod.GET, new URI("https://example.com/a%20b?q=a%2Bb")) + .method(HttpMethod.GET, URI.create("https://example.com/a%20b?q=a%2Bb")) .remoteAddress(remoteAddress) .headers(headers) .build(); @@ -192,12 +192,12 @@ public void forwardedFor() throws URISyntaxException { } @Test - public void xForwardedFor() throws URISyntaxException { + void xForwardedFor() { HttpHeaders headers = new HttpHeaders(); headers.add("x-forwarded-for", "203.0.113.195, 70.41.3.18, 150.172.238.178"); ServerHttpRequest request = MockServerHttpRequest - .method(HttpMethod.GET, new URI("https://example.com/a%20b?q=a%2Bb")) + .method(HttpMethod.GET, URI.create("https://example.com/a%20b?q=a%2Bb")) .headers(headers) .build(); 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 61288d9f6cd1..4d97e02c696e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/test/java/org/springframework/web/server/session/CookieWebSessionIdResolverTests.java b/spring-web/src/test/java/org/springframework/web/server/session/CookieWebSessionIdResolverTests.java index 68fdb229cd03..60b5716a0616 100644 --- a/spring-web/src/test/java/org/springframework/web/server/session/CookieWebSessionIdResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/session/CookieWebSessionIdResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -41,7 +41,7 @@ public void setSessionId() { this.resolver.setSessionId(exchange, "123"); MultiValueMap cookies = exchange.getResponse().getCookies(); - assertThat(cookies.size()).isEqualTo(1); + assertThat(cookies).hasSize(1); ResponseCookie cookie = cookies.getFirst(this.resolver.getCookieName()); assertThat(cookie).isNotNull(); assertThat(cookie.toString()).isEqualTo("SESSION=123; Path=/; Secure; HttpOnly; SameSite=Lax"); @@ -58,7 +58,7 @@ public void cookieInitializer() { this.resolver.setSessionId(exchange, "123"); MultiValueMap cookies = exchange.getResponse().getCookies(); - assertThat(cookies.size()).isEqualTo(1); + assertThat(cookies).hasSize(1); ResponseCookie cookie = cookies.getFirst(this.resolver.getCookieName()); assertThat(cookie).isNotNull(); assertThat(cookie.toString()).isEqualTo("SESSION=123; Path=/; Domain=example.org; HttpOnly; SameSite=Strict"); diff --git a/spring-web/src/test/java/org/springframework/web/server/session/InMemoryWebSessionStoreTests.java b/spring-web/src/test/java/org/springframework/web/server/session/InMemoryWebSessionStoreTests.java index a1795fc3b58f..3b87178e3227 100644 --- a/spring-web/src/test/java/org/springframework/web/server/session/InMemoryWebSessionStoreTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/session/InMemoryWebSessionStoreTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -138,15 +138,15 @@ public void expirationCheckPeriod() { // Create 100 sessions IntStream.range(0, 100).forEach(i -> insertSession()); - assertThat(sessions.size()).isEqualTo(100); + assertThat(sessions).hasSize(100); // Force a new clock (31 min later), don't use setter which would clean expired sessions accessor.setPropertyValue("clock", Clock.offset(this.store.getClock(), Duration.ofMinutes(31))); - assertThat(sessions.size()).isEqualTo(100); + assertThat(sessions).hasSize(100); // Create 1 more which forces a time-based check (clock moved forward) insertSession(); - assertThat(sessions.size()).isEqualTo(1); + assertThat(sessions).hasSize(1); } @Test diff --git a/spring-web/src/test/java/org/springframework/web/server/session/WebSessionIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/server/session/WebSessionIntegrationTests.java index 0fa99e340bae..3fed461b2fee 100644 --- a/spring-web/src/test/java/org/springframework/web/server/session/WebSessionIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/session/WebSessionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -17,7 +17,6 @@ package org.springframework.web.server.session; import java.net.URI; -import java.net.URISyntaxException; import java.time.Clock; import java.time.Duration; import java.util.List; @@ -85,7 +84,6 @@ public void createSession(HttpServer httpServer) throws Exception { public void expiredSessionIsRecreated(HttpServer httpServer) throws Exception { startServer(httpServer); - // First request: no session yet, new session created RequestEntity request = RequestEntity.get(createUri()).build(); ResponseEntity response = this.restTemplate.exchange(request, Void.class); @@ -123,7 +121,6 @@ public void expiredSessionIsRecreated(HttpServer httpServer) throws Exception { public void expiredSessionEnds(HttpServer httpServer) throws Exception { startServer(httpServer); - // First request: no session yet, new session created RequestEntity request = RequestEntity.get(createUri()).build(); ResponseEntity response = this.restTemplate.exchange(request, Void.class); @@ -137,7 +134,7 @@ public void expiredSessionEnds(HttpServer httpServer) throws Exception { store.setClock(Clock.offset(store.getClock(), Duration.ofMinutes(31))); // Second request: session expires - URI uri = new URI("http://localhost:" + this.port + "/?expire"); + URI uri = URI.create("http://localhost:" + this.port + "/?expire"); request = RequestEntity.get(uri).header("Cookie", "SESSION=" + id).build(); response = this.restTemplate.exchange(request, Void.class); @@ -151,7 +148,6 @@ public void expiredSessionEnds(HttpServer httpServer) throws Exception { public void changeSessionId(HttpServer httpServer) throws Exception { startServer(httpServer); - // First request: no session yet, new session created RequestEntity request = RequestEntity.get(createUri()).build(); ResponseEntity response = this.restTemplate.exchange(request, Void.class); @@ -162,7 +158,7 @@ public void changeSessionId(HttpServer httpServer) throws Exception { assertThat(this.handler.getSessionRequestCount()).isEqualTo(1); // Second request: session id changes - URI uri = new URI("http://localhost:" + this.port + "/?changeId"); + URI uri = URI.create("http://localhost:" + this.port + "/?changeId"); request = RequestEntity.get(uri).header("Cookie", "SESSION=" + oldId).build(); response = this.restTemplate.exchange(request, Void.class); @@ -186,7 +182,7 @@ public void invalidate(HttpServer httpServer) throws Exception { assertThat(id).isNotNull(); // Second request: invalidates session - URI uri = new URI("http://localhost:" + this.port + "/?invalidate"); + URI uri = URI.create("http://localhost:" + this.port + "/?invalidate"); request = RequestEntity.get(uri).header("Cookie", "SESSION=" + id).build(); response = this.restTemplate.exchange(request, Void.class); @@ -199,7 +195,7 @@ public void invalidate(HttpServer httpServer) throws Exception { private String extractSessionId(HttpHeaders headers) { List headerValues = headers.get("Set-Cookie"); assertThat(headerValues).isNotNull(); - assertThat(headerValues.size()).isEqualTo(1); + assertThat(headerValues).hasSize(1); for (String s : headerValues.get(0).split(";")){ if (s.startsWith("SESSION=")) { @@ -209,8 +205,8 @@ private String extractSessionId(HttpHeaders headers) { return null; } - private URI createUri() throws URISyntaxException { - return new URI("http://localhost:" + this.port + "/"); + private URI createUri() { + return URI.create("http://localhost:" + this.port + "/"); } diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/CookieValueArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/CookieValueArgumentResolverTests.java index 2227002bb51a..975268fad022 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/CookieValueArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/CookieValueArgumentResolverTests.java @@ -18,7 +18,6 @@ import java.util.List; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.util.ObjectUtils; @@ -29,6 +28,7 @@ /** * Unit tests for {@link CookieValueArgumentResolver}. + * *

    For base class functionality, see {@link NamedValueArgumentResolverTests}. * * @author Rossen Stoyanchev @@ -37,17 +37,8 @@ class CookieValueArgumentResolverTests { private final TestHttpClientAdapter client = new TestHttpClientAdapter(); - private Service service; - - - @BeforeEach - void setUp() throws Exception { - HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build(); - this.service = proxyFactory.createClient(Service.class); - } - + private final Service service = HttpServiceProxyFactory.builder(this.client).build().createClient(Service.class); - // Base class functionality should be tested in NamedValueArgumentResolverTests. @Test void cookieValue() { diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpMethodArgumentResolverTests.java index 4e7c2acc5418..97335dd58a97 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpMethodArgumentResolverTests.java @@ -16,7 +16,6 @@ package org.springframework.web.service.invoker; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; @@ -34,20 +33,11 @@ * @author Olga Maciaszek-Sharma * @author Rossen Stoyanchev */ -public class HttpMethodArgumentResolverTests { +class HttpMethodArgumentResolverTests { private final TestHttpClientAdapter client = new TestHttpClientAdapter(); - private Service service; - - - @BeforeEach - @SuppressWarnings("deprecation") - void setUp() throws Exception { - HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build(); - proxyFactory.afterPropertiesSet(); - this.service = proxyFactory.createClient(Service.class); - } + private final Service service = HttpServiceProxyFactory.builder(this.client).build().createClient(Service.class); @Test diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpRequestValuesTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpRequestValuesTests.java index 850eb3ea9742..55d26409c973 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpRequestValuesTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpRequestValuesTests.java @@ -17,6 +17,7 @@ package org.springframework.web.service.invoker; import java.net.URI; +import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -29,7 +30,6 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponentsBuilder; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; /** @@ -37,7 +37,7 @@ * * @author Rossen Stoyanchev */ -public class HttpRequestValuesTests { +class HttpRequestValuesTests { @Test void defaultUri() { @@ -49,6 +49,7 @@ void defaultUri() { @ParameterizedTest @ValueSource(strings = {"POST", "PUT", "PATCH"}) + @SuppressWarnings("unchecked") void requestParamAsFormData(String httpMethod) { HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.valueOf(httpMethod)) @@ -58,8 +59,9 @@ void requestParamAsFormData(String httpMethod) { .build(); Object body = requestValues.getBodyValue(); - assertThat(body).isNotNull().isInstanceOf(byte[].class); - assertThat(new String((byte[]) body, UTF_8)).isEqualTo("param1=1st+value¶m2=2nd+value+A¶m2=2nd+value+B"); + assertThat((MultiValueMap) body).hasSize(2) + .containsEntry("param1", List.of("1st value")) + .containsEntry("param2", List.of("2nd value A", "2nd value B")); } @Test diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java index 0b31b8916062..47ccdff83c74 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java @@ -21,7 +21,6 @@ import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Single; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -52,22 +51,13 @@ * * @author Rossen Stoyanchev */ -public class HttpServiceMethodTests { +class HttpServiceMethodTests { private static final ParameterizedTypeReference BODY_TYPE = new ParameterizedTypeReference<>() {}; - private final TestHttpClientAdapter client = new TestHttpClientAdapter(); - private HttpServiceProxyFactory proxyFactory; - - - @BeforeEach - @SuppressWarnings("deprecation") - void setUp() throws Exception { - this.proxyFactory = HttpServiceProxyFactory.builder(this.client).build(); - this.proxyFactory.afterPropertiesSet(); - } + private final HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build(); @Test diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java index ea09582c705b..b8c308a4804a 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java @@ -60,12 +60,10 @@ class NamedValueArgumentResolverTests { @BeforeEach - @SuppressWarnings("deprecation") void setUp() throws Exception { HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client) .customArgumentResolver(this.argumentResolver) .build(); - proxyFactory.afterPropertiesSet(); this.service = proxyFactory.createClient(Service.class); } diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/PathVariableArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/PathVariableArgumentResolverTests.java index e24304e4b6df..868db74b4fdb 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/PathVariableArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/PathVariableArgumentResolverTests.java @@ -16,7 +16,6 @@ package org.springframework.web.service.invoker; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.lang.Nullable; @@ -27,6 +26,7 @@ /** * Tests for {@link PathVariableArgumentResolver}. + * *

    For base class functionality, see {@link NamedValueArgumentResolverTests}. * * @author Olga Maciaszek-Sharma @@ -36,19 +36,8 @@ class PathVariableArgumentResolverTests { private final TestHttpClientAdapter client = new TestHttpClientAdapter(); - private Service service; - - - @BeforeEach - @SuppressWarnings("deprecation") - void setUp() throws Exception { - HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build(); - proxyFactory.afterPropertiesSet(); - this.service = proxyFactory.createClient(Service.class); - } - + private final Service service = HttpServiceProxyFactory.builder(this.client).build().createClient(Service.class); - // Base class functionality should be tested in NamedValueArgumentResolverTests. @Test void pathVariable() { diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolverTests.java index 61ec118c22c1..4a63af435864 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolverTests.java @@ -16,7 +16,6 @@ package org.springframework.web.service.invoker; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.lang.Nullable; @@ -35,18 +34,9 @@ class RequestAttributeArgumentResolverTests { private final TestHttpClientAdapter client = new TestHttpClientAdapter(); - private Service service; + private final Service service = HttpServiceProxyFactory.builder(this.client).build().createClient(Service.class); - @BeforeEach - void setUp() throws Exception { - HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build(); - this.service = proxyFactory.createClient(Service.class); - } - - - // Base class functionality should be tested in NamedValueArgumentResolverTests. - @Test void cookieValue() { this.service.execute("test"); diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java index 1fe0d7b657e7..d74da77f31cf 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java @@ -18,7 +18,6 @@ import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Single; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -36,18 +35,11 @@ * * @author Rossen Stoyanchev */ -public class RequestBodyArgumentResolverTests { +class RequestBodyArgumentResolverTests { private final TestHttpClientAdapter client = new TestHttpClientAdapter(); - private Service service; - - - @BeforeEach - void setUp() throws Exception { - HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build(); - this.service = proxyFactory.createClient(Service.class); - } + private final Service service = HttpServiceProxyFactory.builder(this.client).build().createClient(Service.class); @Test diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestParamArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestParamArgumentResolverTests.java index e663a4cfbfc7..8a6f29ddbfec 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestParamArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestParamArgumentResolverTests.java @@ -16,13 +16,14 @@ package org.springframework.web.service.invoker; -import org.junit.jupiter.api.BeforeEach; +import java.util.List; + import org.junit.jupiter.api.Test; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.service.annotation.PostExchange; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; /** @@ -36,30 +37,23 @@ * * @author Rossen Stoyanchev */ -public class RequestParamArgumentResolverTests { +class RequestParamArgumentResolverTests { private final TestHttpClientAdapter client = new TestHttpClientAdapter(); - private Service service; - - - @BeforeEach - void setUp() throws Exception { - HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build(); - this.service = proxyFactory.createClient(Service.class); - } - + private final Service service = HttpServiceProxyFactory.builder(this.client).build().createClient(Service.class); - // Base class functionality should be tested in NamedValueArgumentResolverTests. - // Form data vs query params tested in HttpRequestValuesTests. @Test + @SuppressWarnings("unchecked") void requestParam() { this.service.postForm("value 1", "value 2"); Object body = this.client.getRequestValues().getBodyValue(); - assertThat(body).isNotNull().isInstanceOf(byte[].class); - assertThat(new String((byte[]) body, UTF_8)).isEqualTo("param1=value+1¶m2=value+2"); + assertThat(body).isNotNull().isInstanceOf(MultiValueMap.class); + assertThat((MultiValueMap) body).hasSize(2) + .containsEntry("param1", List.of("value 1")) + .containsEntry("param2", List.of("value 2")); } diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestPartArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestPartArgumentResolverTests.java index 67d53b4da911..ba768c7a31bf 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestPartArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestPartArgumentResolverTests.java @@ -39,7 +39,7 @@ * * @author Rossen Stoyanchev */ -public class RequestPartArgumentResolverTests { +class RequestPartArgumentResolverTests { private final TestHttpClientAdapter client = new TestHttpClientAdapter(); diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/UrlArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/UrlArgumentResolverTests.java index dc1a55f7e2d0..3f236e0056f1 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/UrlArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/UrlArgumentResolverTests.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.web.service.annotation.GetExchange; import static org.assertj.core.api.Assertions.assertThat; @@ -31,7 +32,7 @@ * * @author Rossen Stoyanchev */ -public class UrlArgumentResolverTests { +class UrlArgumentResolverTests { private final TestHttpClientAdapter client = new TestHttpClientAdapter(); @@ -51,7 +52,7 @@ void url() { this.service.execute(dynamicUrl); assertThat(getRequestValues().getUri()).isEqualTo(dynamicUrl); - assertThat(getRequestValues().getUriTemplate()).isNull(); + assertThat(getRequestValues().getUriTemplate()).isEqualTo("/path"); } @Test @@ -67,7 +68,9 @@ void notUrl() { @Test void ignoreNull() { this.service.execute(null); + assertThat(getRequestValues().getUri()).isNull(); + assertThat(getRequestValues().getUriTemplate()).isEqualTo("/path"); } private HttpRequestValues getRequestValues() { @@ -78,7 +81,7 @@ private HttpRequestValues getRequestValues() { private interface Service { @GetExchange("/path") - void execute(URI uri); + void execute(@Nullable URI uri); @GetExchange void executeNotUri(String other); diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index a7d4e8d429d9..3de7ca3b2567 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -17,7 +17,6 @@ package org.springframework.web.util; import java.net.URI; -import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -78,7 +77,7 @@ void examplesInReferenceManual() { } @Test - void plain() throws URISyntaxException { + void plain() { UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); UriComponents result = builder.scheme("https").host("example.com") .path("foo").queryParam("bar").fragment("baz").build(); @@ -89,12 +88,12 @@ void plain() throws URISyntaxException { assertThat(result.getQuery()).isEqualTo("bar"); assertThat(result.getFragment()).isEqualTo("baz"); - URI expected = new URI("https://example.com/foo?bar#baz"); + URI expected = URI.create("https://example.com/foo?bar#baz"); assertThat(result.toUri()).as("Invalid result URI").isEqualTo(expected); } @Test - void multipleFromSameBuilder() throws URISyntaxException { + void multipleFromSameBuilder() { UriComponentsBuilder builder = UriComponentsBuilder.newInstance() .scheme("https").host("example.com").pathSegment("foo"); UriComponents result1 = builder.build(); @@ -104,7 +103,7 @@ void multipleFromSameBuilder() throws URISyntaxException { assertThat(result1.getScheme()).isEqualTo("https"); assertThat(result1.getHost()).isEqualTo("example.com"); assertThat(result1.getPath()).isEqualTo("/foo"); - URI expected = new URI("https://example.com/foo"); + URI expected = URI.create("https://example.com/foo"); assertThat(result1.toUri()).as("Invalid result URI").isEqualTo(expected); assertThat(result2.getScheme()).isEqualTo("https"); @@ -112,12 +111,12 @@ void multipleFromSameBuilder() throws URISyntaxException { assertThat(result2.getPath()).isEqualTo("/foo/foo2"); assertThat(result2.getQuery()).isEqualTo("bar"); assertThat(result2.getFragment()).isEqualTo("baz"); - expected = new URI("https://example.com/foo/foo2?bar#baz"); + expected = URI.create("https://example.com/foo/foo2?bar#baz"); assertThat(result2.toUri()).as("Invalid result URI").isEqualTo(expected); } @Test - void fromPath() throws URISyntaxException { + void fromPath() { UriComponents result = UriComponentsBuilder.fromPath("foo").queryParam("bar").fragment("baz").build(); assertThat(result.getPath()).isEqualTo("foo"); @@ -125,19 +124,19 @@ void fromPath() throws URISyntaxException { assertThat(result.getFragment()).isEqualTo("baz"); assertThat(result.toUriString()).as("Invalid result URI String").isEqualTo("foo?bar#baz"); - URI expected = new URI("foo?bar#baz"); + URI expected = URI.create("foo?bar#baz"); assertThat(result.toUri()).as("Invalid result URI").isEqualTo(expected); result = UriComponentsBuilder.fromPath("/foo").build(); assertThat(result.getPath()).isEqualTo("/foo"); - expected = new URI("/foo"); + expected = URI.create("/foo"); assertThat(result.toUri()).as("Invalid result URI").isEqualTo(expected); } @Test - void fromHierarchicalUri() throws URISyntaxException { - URI uri = new URI("https://example.com/foo?bar#baz"); + void fromHierarchicalUri() { + URI uri = URI.create("https://example.com/foo?bar#baz"); UriComponents result = UriComponentsBuilder.fromUri(uri).build(); assertThat(result.getScheme()).isEqualTo("https"); @@ -149,8 +148,8 @@ void fromHierarchicalUri() throws URISyntaxException { } @Test - void fromOpaqueUri() throws URISyntaxException { - URI uri = new URI("mailto:foo@bar.com#baz"); + void fromOpaqueUri() { + URI uri = URI.create("mailto:foo@bar.com#baz"); UriComponents result = UriComponentsBuilder.fromUri(uri).build(); assertThat(result.getScheme()).isEqualTo("mailto"); @@ -160,8 +159,8 @@ void fromOpaqueUri() throws URISyntaxException { } @Test // SPR-9317 - void fromUriEncodedQuery() throws URISyntaxException { - URI uri = new URI("https://www.example.org/?param=aGVsbG9Xb3JsZA%3D%3D"); + void fromUriEncodedQuery() { + URI uri = URI.create("https://www.example.org/?param=aGVsbG9Xb3JsZA%3D%3D"); String fromUri = UriComponentsBuilder.fromUri(uri).build().getQueryParams().get("param").get(0); String fromUriString = UriComponentsBuilder.fromUriString(uri.toString()) .build().getQueryParams().get("param").get(0); @@ -967,8 +966,8 @@ void queryParamWithoutValueWithoutEquals() { } @Test // gh-24444 - void opaqueUriDoesNotResetOnNullInput() throws URISyntaxException { - URI uri = new URI("urn:ietf:wg:oauth:2.0:oob"); + void opaqueUriDoesNotResetOnNullInput() { + URI uri = URI.create("urn:ietf:wg:oauth:2.0:oob"); UriComponents result = UriComponentsBuilder.fromUri(uri) .host(null) .port(-1) diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java index 7bbb2c342995..2031684a4f12 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java @@ -21,7 +21,6 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.URI; -import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collections; @@ -76,30 +75,30 @@ void encodeAndExpandWithDollarSign() { } @Test - void toUriEncoded() throws URISyntaxException { + void toUriEncoded() { UriComponents uri = UriComponentsBuilder.fromUriString("https://example.com/hotel list/Z\u00fcrich").build(); - assertThat(uri.encode().toUri()).isEqualTo(new URI("https://example.com/hotel%20list/Z%C3%BCrich")); + assertThat(uri.encode().toUri()).isEqualTo(URI.create("https://example.com/hotel%20list/Z%C3%BCrich")); } @Test - void toUriNotEncoded() throws URISyntaxException { + void toUriNotEncoded() { UriComponents uri = UriComponentsBuilder.fromUriString("https://example.com/hotel list/Z\u00fcrich").build(); - assertThat(uri.toUri()).isEqualTo(new URI("https://example.com/hotel%20list/Z\u00fcrich")); + assertThat(uri.toUri()).isEqualTo(URI.create("https://example.com/hotel%20list/Z\u00fcrich")); } @Test - void toUriAlreadyEncoded() throws URISyntaxException { + void toUriAlreadyEncoded() { UriComponents uri = UriComponentsBuilder.fromUriString("https://example.com/hotel%20list/Z%C3%BCrich").build(true); - assertThat(uri.encode().toUri()).isEqualTo(new URI("https://example.com/hotel%20list/Z%C3%BCrich")); + assertThat(uri.encode().toUri()).isEqualTo(URI.create("https://example.com/hotel%20list/Z%C3%BCrich")); } @Test - void toUriWithIpv6HostAlreadyEncoded() throws URISyntaxException { + void toUriWithIpv6HostAlreadyEncoded() { UriComponents uri = UriComponentsBuilder.fromUriString( "http://[1abc:2abc:3abc::5ABC:6abc]:8080/hotel%20list/Z%C3%BCrich").build(true); assertThat(uri.encode().toUri()).isEqualTo( - new URI("http://[1abc:2abc:3abc::5ABC:6abc]:8080/hotel%20list/Z%C3%BCrich")); + URI.create("http://[1abc:2abc:3abc::5ABC:6abc]:8080/hotel%20list/Z%C3%BCrich")); } @Test diff --git a/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java b/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java index 6405e5cabfea..49044594fc20 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -33,73 +33,73 @@ * @author Juergen Hoeller * @author Rossen Stoyanchev */ -public class UriTemplateTests { +class UriTemplateTests { @Test - public void getVariableNames() throws Exception { + void getVariableNames() { UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}"); List variableNames = template.getVariableNames(); assertThat(variableNames).as("Invalid variable names").isEqualTo(Arrays.asList("hotel", "booking")); } @Test - public void expandVarArgs() throws Exception { + void expandVarArgs() { UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}"); URI result = template.expand("1", "42"); - assertThat(result).as("Invalid expanded template").isEqualTo(new URI("/hotels/1/bookings/42")); + assertThat(result).as("Invalid expanded template").isEqualTo(URI.create("/hotels/1/bookings/42")); } @Test // SPR-9712 - public void expandVarArgsWithArrayValue() throws Exception { + void expandVarArgsWithArrayValue() { UriTemplate template = new UriTemplate("/sum?numbers={numbers}"); URI result = template.expand(new int[] {1, 2, 3}); - assertThat(result).isEqualTo(new URI("/sum?numbers=1,2,3")); + assertThat(result).isEqualTo(URI.create("/sum?numbers=1,2,3")); } @Test - public void expandVarArgsNotEnoughVariables() throws Exception { + void expandVarArgsNotEnoughVariables() { UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}"); assertThatIllegalArgumentException().isThrownBy(() -> template.expand("1")); } @Test - public void expandMap() throws Exception { + void expandMap() { Map uriVariables = new HashMap<>(2); uriVariables.put("booking", "42"); uriVariables.put("hotel", "1"); UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}"); URI result = template.expand(uriVariables); - assertThat(result).as("Invalid expanded template").isEqualTo(new URI("/hotels/1/bookings/42")); + assertThat(result).as("Invalid expanded template").isEqualTo(URI.create("/hotels/1/bookings/42")); } @Test - public void expandMapDuplicateVariables() throws Exception { + void expandMapDuplicateVariables() { UriTemplate template = new UriTemplate("/order/{c}/{c}/{c}"); assertThat(template.getVariableNames()).isEqualTo(Arrays.asList("c", "c", "c")); URI result = template.expand(Collections.singletonMap("c", "cheeseburger")); - assertThat(result).isEqualTo(new URI("/order/cheeseburger/cheeseburger/cheeseburger")); + assertThat(result).isEqualTo(URI.create("/order/cheeseburger/cheeseburger/cheeseburger")); } @Test - public void expandMapNonString() throws Exception { + void expandMapNonString() { Map uriVariables = new HashMap<>(2); uriVariables.put("booking", 42); uriVariables.put("hotel", 1); UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}"); URI result = template.expand(uriVariables); - assertThat(result).as("Invalid expanded template").isEqualTo(new URI("/hotels/1/bookings/42")); + assertThat(result).as("Invalid expanded template").isEqualTo(URI.create("/hotels/1/bookings/42")); } @Test - public void expandMapEncoded() throws Exception { + void expandMapEncoded() { Map uriVariables = Collections.singletonMap("hotel", "Z\u00fcrich"); UriTemplate template = new UriTemplate("/hotel list/{hotel}"); URI result = template.expand(uriVariables); - assertThat(result).as("Invalid expanded template").isEqualTo(new URI("/hotel%20list/Z%C3%BCrich")); + assertThat(result).as("Invalid expanded template").isEqualTo(URI.create("/hotel%20list/Z%C3%BCrich")); } @Test - public void expandMapUnboundVariables() throws Exception { + void expandMapUnboundVariables() { Map uriVariables = new HashMap<>(2); uriVariables.put("booking", "42"); uriVariables.put("bar", "1"); @@ -109,14 +109,14 @@ public void expandMapUnboundVariables() throws Exception { } @Test - public void expandEncoded() throws Exception { + void expandEncoded() { UriTemplate template = new UriTemplate("/hotel list/{hotel}"); URI result = template.expand("Z\u00fcrich"); - assertThat(result).as("Invalid expanded template").isEqualTo(new URI("/hotel%20list/Z%C3%BCrich")); + assertThat(result).as("Invalid expanded template").isEqualTo(URI.create("/hotel%20list/Z%C3%BCrich")); } @Test - public void matches() throws Exception { + void matches() { UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}"); assertThat(template.matches("/hotels/1/bookings/42")).as("UriTemplate does not match").isTrue(); assertThat(template.matches("/hotels/bookings")).as("UriTemplate matches").isFalse(); @@ -125,14 +125,14 @@ public void matches() throws Exception { } @Test - public void matchesCustomRegex() throws Exception { + void matchesCustomRegex() { UriTemplate template = new UriTemplate("/hotels/{hotel:\\d+}"); assertThat(template.matches("/hotels/42")).as("UriTemplate does not match").isTrue(); assertThat(template.matches("/hotels/foo")).as("UriTemplate matches").isFalse(); } @Test - public void match() throws Exception { + void match() { Map expected = new HashMap<>(2); expected.put("booking", "42"); expected.put("hotel", "1"); @@ -143,7 +143,7 @@ public void match() throws Exception { } @Test - public void matchCustomRegex() throws Exception { + void matchCustomRegex() { Map expected = new HashMap<>(2); expected.put("booking", "42"); expected.put("hotel", "1"); @@ -154,14 +154,14 @@ public void matchCustomRegex() throws Exception { } @Test // SPR-13627 - public void matchCustomRegexWithNestedCurlyBraces() throws Exception { + void matchCustomRegexWithNestedCurlyBraces() { UriTemplate template = new UriTemplate("/site.{domain:co.[a-z]{2}}"); Map result = template.match("/site.co.eu"); assertThat(result).as("Invalid match").isEqualTo(Collections.singletonMap("domain", "co.eu")); } @Test - public void matchDuplicate() throws Exception { + void matchDuplicate() { UriTemplate template = new UriTemplate("/order/{c}/{c}/{c}"); Map result = template.match("/order/cheeseburger/cheeseburger/cheeseburger"); Map expected = Collections.singletonMap("c", "cheeseburger"); @@ -169,7 +169,7 @@ public void matchDuplicate() throws Exception { } @Test - public void matchMultipleInOneSegment() throws Exception { + void matchMultipleInOneSegment() { UriTemplate template = new UriTemplate("/{foo}-{bar}"); Map result = template.match("/12-34"); Map expected = new HashMap<>(2); @@ -179,19 +179,19 @@ public void matchMultipleInOneSegment() throws Exception { } @Test // SPR-16169 - public void matchWithMultipleSegmentsAtTheEnd() throws Exception { + void matchWithMultipleSegmentsAtTheEnd() { UriTemplate template = new UriTemplate("/account/{accountId}"); assertThat(template.matches("/account/15/alias/5")).isFalse(); } @Test - public void queryVariables() throws Exception { + void queryVariables() { UriTemplate template = new UriTemplate("/search?q={query}"); assertThat(template.matches("/search?q=foo")).isTrue(); } @Test - public void fragments() throws Exception { + void fragments() { UriTemplate template = new UriTemplate("/search#{fragment}"); assertThat(template.matches("/search#foo")).isTrue(); @@ -200,19 +200,19 @@ public void fragments() throws Exception { } @Test // SPR-13705 - public void matchesWithSlashAtTheEnd() throws Exception { + void matchesWithSlashAtTheEnd() { assertThat(new UriTemplate("/test/").matches("/test/")).isTrue(); } @Test - public void expandWithDollar() throws Exception { + void expandWithDollar() { UriTemplate template = new UriTemplate("/{a}"); URI uri = template.expand("$replacement"); assertThat(uri.toString()).isEqualTo("/$replacement"); } @Test - public void expandWithAtSign() throws Exception { + void expandWithAtSign() { UriTemplate template = new UriTemplate("http://localhost/query={query}"); URI uri = template.expand("foo@bar"); assertThat(uri.toString()).isEqualTo("http://localhost/query=foo@bar"); diff --git a/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java index d64fc24243e9..e9d95d582c08 100644 --- a/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java @@ -63,7 +63,7 @@ void parseMatrixVariablesString() { MultiValueMap variables; variables = WebUtils.parseMatrixVariables(null); - assertThat(variables).hasSize(0); + assertThat(variables).isEmpty(); variables = WebUtils.parseMatrixVariables("year"); assertThat(variables).hasSize(1); diff --git a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java index b55e21ff43d5..d31142056181 100644 --- a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java @@ -577,7 +577,7 @@ public void pathRemainingEnhancements_spr15419() { pri = getPathRemaining(pp, "/aaa/bbb"); assertThat(pri.getPathRemaining().value()).isEqualTo(""); assertThat(pri.getPathMatched().value()).isEqualTo("/aaa/bbb"); - assertThat(pri.getUriVariables().size()).isEqualTo(0); + assertThat(pri.getUriVariables()).isEmpty(); pp = parse("/*/{foo}/b*"); pri = getPathRemaining(pp, "/foo"); @@ -686,6 +686,7 @@ public void extractPathWithinPattern() throws Exception { checkExtractPathWithinPattern("/docs/commit.html", "/docs/commit.html", ""); checkExtractPathWithinPattern("/docs/*", "/docs/cvs/commit", "cvs/commit"); checkExtractPathWithinPattern("/docs/cvs/*.html", "/docs/cvs/commit.html", "commit.html"); + checkExtractPathWithinPattern("/docs/cvs/file.*.html", "/docs/cvs/file.sha.html", "file.sha.html"); checkExtractPathWithinPattern("/docs/**", "/docs/cvs/commit", "cvs/commit"); checkExtractPathWithinPattern("/doo/{*foobar}", "/doo/customer.html", "customer.html"); checkExtractPathWithinPattern("/doo/{*foobar}", "/doo/daa/customer.html", "daa/customer.html"); @@ -807,7 +808,7 @@ public void extractUriTemplateVariables() throws Exception { assertThat((Object) checkCapture("/{one}/", "//")).isNull(); assertThat((Object) checkCapture("", "/abc")).isNull(); - assertThat(checkCapture("", "").getUriVariables().size()).isEqualTo(0); + assertThat(checkCapture("", "").getUriVariables()).isEmpty(); checkCapture("{id}", "99", "id", "99"); checkCapture("/customer/{customerId}", "/customer/78", "customerId", "78"); checkCapture("/customer/{customerId}/banana", "/customer/42/banana", "customerId", @@ -817,7 +818,7 @@ public void extractUriTemplateVariables() throws Exception { "apple"); checkCapture("/{bla}.*", "/testing.html", "bla", "testing"); PathPattern.PathMatchInfo extracted = checkCapture("/abc", "/abc"); - assertThat(extracted.getUriVariables().size()).isEqualTo(0); + assertThat(extracted.getUriVariables()).isEmpty(); checkCapture("/{bla}/foo","/a/foo"); } diff --git a/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt b/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt index 15f457a8253f..d98e2e2d72f9 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt @@ -32,9 +32,9 @@ import org.junit.jupiter.api.Test import org.springframework.core.Ordered import org.springframework.http.MediaType -import org.springframework.http.MockHttpInputMessage -import org.springframework.http.MockHttpOutputMessage import org.springframework.http.converter.HttpMessageNotReadableException +import org.springframework.web.testfixture.http.MockHttpInputMessage +import org.springframework.web.testfixture.http.MockHttpOutputMessage /** * Tests for the CBOR conversion using kotlinx.serialization. diff --git a/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt b/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt index de7789d7444e..2016766a209c 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt @@ -30,9 +30,9 @@ import kotlin.reflect.typeOf import org.springframework.core.Ordered import org.springframework.core.ResolvableType import org.springframework.http.MediaType -import org.springframework.http.MockHttpInputMessage -import org.springframework.http.MockHttpOutputMessage import org.springframework.http.converter.HttpMessageNotReadableException +import org.springframework.web.testfixture.http.MockHttpInputMessage +import org.springframework.web.testfixture.http.MockHttpOutputMessage /** * Tests for the JSON conversion using kotlinx.serialization. diff --git a/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt b/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt index 5e12b0f29c4d..d74d530734e6 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt @@ -25,9 +25,9 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.jupiter.api.Test import org.springframework.core.Ordered import org.springframework.http.MediaType -import org.springframework.http.MockHttpInputMessage -import org.springframework.http.MockHttpOutputMessage import org.springframework.http.converter.HttpMessageNotReadableException +import org.springframework.web.testfixture.http.MockHttpInputMessage +import org.springframework.web.testfixture.http.MockHttpOutputMessage import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.nio.charset.StandardCharsets diff --git a/spring-web/src/test/java/org/springframework/http/MockHttpInputMessage.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/MockHttpInputMessage.java similarity index 66% rename from spring-web/src/test/java/org/springframework/http/MockHttpInputMessage.java rename to spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/MockHttpInputMessage.java index 784ef50cd245..8b92cf246730 100644 --- a/spring-web/src/test/java/org/springframework/http/MockHttpInputMessage.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/MockHttpInputMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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. @@ -14,12 +14,14 @@ * limitations under the License. */ -package org.springframework.http; +package org.springframework.web.testfixture.http; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; import org.springframework.util.Assert; /** @@ -35,25 +37,31 @@ public class MockHttpInputMessage implements HttpInputMessage { private final InputStream body; - public MockHttpInputMessage(byte[] contents) { - Assert.notNull(contents, "'contents' must not be null"); - this.body = new ByteArrayInputStream(contents); + /** + * Create a {@code MockHttpInputMessage} with the supplied body. + */ + public MockHttpInputMessage(byte[] body) { + Assert.notNull(body, "Byte array must not be null"); + this.body = new ByteArrayInputStream(body); } + /** + * Create a {@code MockHttpInputMessage} with the supplied body. + */ public MockHttpInputMessage(InputStream body) { - Assert.notNull(body, "'body' must not be null"); + Assert.notNull(body, "InputStream must not be null"); this.body = body; } @Override public HttpHeaders getHeaders() { - return headers; + return this.headers; } @Override public InputStream getBody() throws IOException { - return body; + return this.body; } } diff --git a/spring-web/src/test/java/org/springframework/http/MockHttpOutputMessage.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/MockHttpOutputMessage.java similarity index 55% rename from spring-web/src/test/java/org/springframework/http/MockHttpOutputMessage.java rename to spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/MockHttpOutputMessage.java index c11c36db71cb..1482d673231e 100644 --- a/spring-web/src/test/java/org/springframework/http/MockHttpOutputMessage.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/MockHttpOutputMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -14,64 +14,61 @@ * limitations under the License. */ -package org.springframework.http; +package org.springframework.web.testfixture.http; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpOutputMessage; +import org.springframework.util.StreamUtils; /** - * @author Arjen Poutsma + * Mock implementation of {@link HttpOutputMessage}. + * * @author Rossen Stoyanchev + * @since 3.2 */ public class MockHttpOutputMessage implements HttpOutputMessage { private final HttpHeaders headers = new HttpHeaders(); - private final ByteArrayOutputStream body = new ByteArrayOutputStream(); - - private boolean headersWritten = false; - - private final HttpHeaders writtenHeaders = new HttpHeaders(); + private final ByteArrayOutputStream body = new ByteArrayOutputStream(1024); @Override public HttpHeaders getHeaders() { - return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); - } - - /** - * Return a copy of the actual headers written at the time of the call to - * getResponseBody, i.e. ignoring any further changes that may have been made to - * the underlying headers, e.g. via a previously obtained instance. - */ - public HttpHeaders getWrittenHeaders() { - return writtenHeaders; + return this.headers; } @Override public OutputStream getBody() throws IOException { - writeHeaders(); - return body; + return this.body; } + /** + * Return the body content as a byte array. + */ public byte[] getBodyAsBytes() { - writeHeaders(); - return body.toByteArray(); + return this.body.toByteArray(); } - public String getBodyAsString(Charset charset) { - byte[] bytes = getBodyAsBytes(); - return new String(bytes, charset); + /** + * Return the body content interpreted as a UTF-8 string. + */ + public String getBodyAsString() { + return getBodyAsString(StandardCharsets.UTF_8); } - private void writeHeaders() { - if (this.headersWritten) { - return; - } - this.headersWritten = true; - this.writtenHeaders.putAll(this.headers); + /** + * Return the body content interpreted as a string using the supplied character set. + * @param charset the charset to use to turn the body content into a String + */ + public String getBodyAsString(Charset charset) { + return StreamUtils.copyToString(this.body, charset); } } diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java index d6989daa8e73..826b193712ec 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java @@ -16,84 +16,67 @@ package org.springframework.web.testfixture.http.client; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.StreamUtils; +import org.springframework.web.testfixture.http.MockHttpOutputMessage; import org.springframework.web.util.UriComponentsBuilder; /** * Mock implementation of {@link ClientHttpRequest}. * - * @author Brian Clozel * @author Rossen Stoyanchev + * @author Brian Clozel + * @author Sam Brannen + * @since 3.2 */ -public class MockClientHttpRequest implements ClientHttpRequest { - - private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - - private final HttpHeaders headers = new HttpHeaders(); +public class MockClientHttpRequest extends MockHttpOutputMessage implements ClientHttpRequest { private HttpMethod httpMethod; private URI uri; - private final ByteArrayOutputStream body = new ByteArrayOutputStream(1024); - @Nullable private ClientHttpResponse clientHttpResponse; private boolean executed = false; + /** + * Create a {@code MockClientHttpRequest} with {@link HttpMethod#GET GET} as + * the HTTP request method and {@code "/"} as the {@link URI}. + */ public MockClientHttpRequest() { - this.httpMethod = HttpMethod.GET; - try { - this.uri = new URI("/"); - } - catch (URISyntaxException ex) { - throw new IllegalStateException(ex); - } - } - - public MockClientHttpRequest(HttpMethod httpMethod, String urlTemplate, Object... vars) { - this.httpMethod = httpMethod; - this.uri = UriComponentsBuilder.fromUriString(urlTemplate).buildAndExpand(vars).encode().toUri(); - } - - @Override - public HttpHeaders getHeaders() { - return this.headers; - } - - @Override - public OutputStream getBody() throws IOException { - return this.body; + this(HttpMethod.GET, URI.create("/")); } - public byte[] getBodyAsBytes() { - return this.body.toByteArray(); + /** + * Create a {@code MockClientHttpRequest} with the given {@link HttpMethod}, + * URI template, and URI template variable values. + * @since 6.0.3 + */ + public MockClientHttpRequest(HttpMethod httpMethod, String uriTemplate, Object... vars) { + this(httpMethod, UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(vars).encode().toUri()); } - public String getBodyAsString() { - return getBodyAsString(DEFAULT_CHARSET); + /** + * Create a {@code MockClientHttpRequest} with the given {@link HttpMethod} + * and {@link URI}. + */ + public MockClientHttpRequest(HttpMethod httpMethod, URI uri) { + this.httpMethod = httpMethod; + this.uri = uri; } - public String getBodyAsString(Charset charset) { - return StreamUtils.copyToString(this.body, charset); - } + /** + * Set the HTTP method of the request. + */ public void setMethod(HttpMethod httpMethod) { this.httpMethod = httpMethod; } @@ -103,13 +86,9 @@ public HttpMethod getMethod() { return this.httpMethod; } - @SuppressWarnings("removal") - @Override - @Deprecated - public String getMethodValue() { - return this.httpMethod.name(); - } - + /** + * Set the URI of the request. + */ public void setURI(URI uri) { this.uri = uri; } @@ -119,36 +98,53 @@ public URI getURI() { return this.uri; } + /** + * Set the {@link ClientHttpResponse} to be used as the result of executing + * the this request. + * @see #execute() + */ public void setResponse(ClientHttpResponse clientHttpResponse) { this.clientHttpResponse = clientHttpResponse; } + /** + * Get the {@link #isExecuted() executed} flag. + * @see #execute() + */ public boolean isExecuted() { return this.executed; } + /** + * Set the {@link #isExecuted() executed} flag to {@code true} and return the + * configured {@link #setResponse(ClientHttpResponse) response}. + * @see #executeInternal() + */ @Override public final ClientHttpResponse execute() throws IOException { this.executed = true; return executeInternal(); } + /** + * The default implementation returns the configured + * {@link #setResponse(ClientHttpResponse) response}. + *

    Override this method to execute the request and provide a response, + * potentially different from the configured response. + */ protected ClientHttpResponse executeInternal() throws IOException { Assert.state(this.clientHttpResponse != null, "No ClientHttpResponse"); return this.clientHttpResponse; } + @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(this.httpMethod); - sb.append(' ').append(this.uri); + sb.append(this.httpMethod).append(' ').append(this.uri); if (!getHeaders().isEmpty()) { sb.append(", headers: ").append(getHeaders()); } - if (sb.length() == 0) { - sb.append("Not yet initialized"); - } return sb.toString(); } diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpResponse.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpResponse.java index ba8ed077a069..45a3829e7810 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpResponse.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpResponse.java @@ -16,74 +16,89 @@ package org.springframework.web.testfixture.http.client; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.client.ClientHttpResponse; import org.springframework.util.Assert; +import org.springframework.web.testfixture.http.MockHttpInputMessage; /** * Mock implementation of {@link ClientHttpResponse}. * * @author Rossen Stoyanchev - * @author Brian Clozel + * @author Sam Brannen + * @since 3.2 */ -public class MockClientHttpResponse implements ClientHttpResponse { +public class MockClientHttpResponse extends MockHttpInputMessage implements ClientHttpResponse { - private final HttpHeaders headers = new HttpHeaders(); - - private final HttpStatus status; - - private InputStream body; + private final HttpStatusCode statusCode; + /** + * Create a {@code MockClientHttpResponse} with an empty response body and + * HTTP status code {@link HttpStatus#OK OK}. + * @since 6.0.3 + */ public MockClientHttpResponse() { - this.status = HttpStatus.OK; + this(new byte[0], HttpStatus.OK); } - public MockClientHttpResponse(HttpStatus statusCode) { - Assert.notNull(statusCode, "HttpStatus is required"); - this.status = statusCode; + /** + * Create a {@code MockClientHttpResponse} with response body as a byte array + * and the supplied HTTP status code. + */ + public MockClientHttpResponse(byte[] body, HttpStatusCode statusCode) { + super(body); + Assert.notNull(statusCode, "HttpStatusCode must not be null"); + this.statusCode = statusCode; } - @Override - public HttpStatus getStatusCode() throws IOException { - return this.status; + /** + * Create a {@code MockClientHttpResponse} with response body as a byte array + * and a custom HTTP status code. + * @since 5.3.17 + */ + public MockClientHttpResponse(byte[] body, int statusCode) { + this(body, HttpStatusCode.valueOf(statusCode)); } - @Override - @SuppressWarnings("deprecation") - public int getRawStatusCode() throws IOException { - return this.status.value(); + /** + * Create a {@code MockClientHttpResponse} with response body as {@link InputStream} + * and the supplied HTTP status code. + */ + public MockClientHttpResponse(InputStream body, HttpStatusCode statusCode) { + super(body); + Assert.notNull(statusCode, "HttpStatusCode must not be null"); + this.statusCode = statusCode; } - @Override - public String getStatusText() throws IOException { - return this.status.getReasonPhrase(); + /** + * Create a {@code MockClientHttpResponse} with response body as {@link InputStream} + * and a custom HTTP status code. + * @since 5.3.17 + */ + public MockClientHttpResponse(InputStream body, int statusCode) { + this(body, HttpStatusCode.valueOf(statusCode)); } - @Override - public HttpHeaders getHeaders() { - return this.headers; - } @Override - public InputStream getBody() throws IOException { - return this.body; + public HttpStatusCode getStatusCode() { + return this.statusCode; } - public void setBody(byte[] body) { - Assert.notNull(body, "body is required"); - this.body = new ByteArrayInputStream(body); + @Override + @Deprecated + public int getRawStatusCode() { + return this.statusCode.value(); } - public void setBody(String body) { - Assert.notNull(body, "body is required"); - this.body = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); + @Override + public String getStatusText() { + return (this.statusCode instanceof HttpStatus status ? status.getReasonPhrase() : ""); } @Override diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/MockClientHttpRequest.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/MockClientHttpRequest.java index a48b44dd8dc0..e117b6ce2b39 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/MockClientHttpRequest.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/MockClientHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/MockClientHttpResponse.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/MockClientHttpResponse.java index c139fd747716..8d304d9cd1d7 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/MockClientHttpResponse.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/MockClientHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/MockServerHttpRequest.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/MockServerHttpRequest.java index 8078a7db8323..3750a8e1ae39 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/MockServerHttpRequest.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/MockServerHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/ResolvableMethod.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/ResolvableMethod.java index 82f73b06ca97..163bc5479475 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/ResolvableMethod.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/method/ResolvableMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -39,7 +39,7 @@ import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.Factory; import org.springframework.cglib.proxy.MethodProxy; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodIntrospector; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; @@ -130,7 +130,7 @@ public class ResolvableMethod { private static final SpringObjenesis objenesis = new SpringObjenesis(); - private static final ParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + private static final ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer(); // Matches ValueConstants.DEFAULT_NONE (spring-web and spring-messaging) private static final String DEFAULT_VALUE_NONE = "\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n"; diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/HeaderValueHolder.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/HeaderValueHolder.java index 9884d2a74650..c1659d97cd8c 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/HeaderValueHolder.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/HeaderValueHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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,7 +16,6 @@ package org.springframework.web.testfixture.servlet; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; @@ -61,11 +60,7 @@ List getValues() { } List getStringValues() { - List stringList = new ArrayList<>(this.values.size()); - for (Object value : this.values) { - stringList.add(value.toString()); - } - return Collections.unmodifiableList(stringList); + return this.values.stream().map(Object::toString).toList(); } @Nullable diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockBodyContent.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockBodyContent.java index 5992baa04d60..63b566828f0f 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockBodyContent.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockBodyContent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -69,8 +69,8 @@ public MockBodyContent(String content, @Nullable HttpServletResponse response, @ } private static JspWriter adaptJspWriter(@Nullable Writer targetWriter, @Nullable HttpServletResponse response) { - if (targetWriter instanceof JspWriter) { - return (JspWriter) targetWriter; + if (targetWriter instanceof JspWriter jspWriter) { + return jspWriter; } else { return new MockJspWriter(response, targetWriter); diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletRequest.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletRequest.java index 44174534e426..9c269278af03 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletRequest.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletRequest.java @@ -549,11 +549,11 @@ public void setParameter(String name, String... values) { public void setParameters(Map params) { Assert.notNull(params, "Parameter map must not be null"); params.forEach((key, value) -> { - if (value instanceof String) { - setParameter(key, (String) value); + if (value instanceof String str) { + setParameter(key, str); } - else if (value instanceof String[]) { - setParameter(key, (String[]) value); + else if (value instanceof String[] strings) { + setParameter(key, strings); } else { throw new IllegalArgumentException( @@ -598,11 +598,11 @@ public void addParameter(String name, String... values) { public void addParameters(Map params) { Assert.notNull(params, "Parameter map must not be null"); params.forEach((key, value) -> { - if (value instanceof String) { - addParameter(key, (String) value); + if (value instanceof String str) { + addParameter(key, str); } - else if (value instanceof String[]) { - addParameter(key, (String[]) value); + else if (value instanceof String[] strings) { + addParameter(key, strings); } else { throw new IllegalArgumentException("Parameter map value must be single value " + @@ -1083,8 +1083,8 @@ private void doAddHeaderValue(String name, @Nullable Object value, boolean repla header = new HeaderValueHolder(); this.headers.put(name, header); } - if (value instanceof Collection) { - header.addValues((Collection) value); + if (value instanceof Collection collection) { + header.addValues(collection); } else if (value.getClass().isArray()) { header.addValueArray(value); @@ -1119,14 +1119,14 @@ public void removeHeader(String name) { public long getDateHeader(String name) { HeaderValueHolder header = this.headers.get(name); Object value = (header != null ? header.getValue() : null); - if (value instanceof Date) { - return ((Date) value).getTime(); + if (value instanceof Date date) { + return date.getTime(); } - else if (value instanceof Number) { - return ((Number) value).longValue(); + else if (value instanceof Number number) { + return number.longValue(); } - else if (value instanceof String) { - return parseDateHeader(name, (String) value); + else if (value instanceof String str) { + return parseDateHeader(name, str); } else if (value != null) { throw new IllegalArgumentException( @@ -1173,11 +1173,11 @@ public Enumeration getHeaderNames() { public int getIntHeader(String name) { HeaderValueHolder header = this.headers.get(name); Object value = (header != null ? header.getValue() : null); - if (value instanceof Number) { - return ((Number) value).intValue(); + if (value instanceof Number number) { + return number.intValue(); } - else if (value instanceof String) { - return Integer.parseInt((String) value); + else if (value instanceof String str) { + return Integer.parseInt(str); } else if (value != null) { throw new NumberFormatException("Value for header '" + name + "' is not a Number: " + value); @@ -1248,8 +1248,9 @@ public void addUserRole(String role) { @Override public boolean isUserInRole(String role) { - return (this.userRoles.contains(role) || (this.servletContext instanceof MockServletContext && - ((MockServletContext) this.servletContext).getDeclaredRoles().contains(role))); + return (this.userRoles.contains(role) || + (this.servletContext instanceof MockServletContext mockContext && + mockContext.getDeclaredRoles().contains(role))); } public void setUserPrincipal(@Nullable Principal userPrincipal) { @@ -1321,7 +1322,7 @@ public void setSession(HttpSession session) { public HttpSession getSession(boolean create) { checkActive(); // Reset session if invalidated. - if (this.session instanceof MockHttpSession && ((MockHttpSession) this.session).isInvalid()) { + if (this.session instanceof MockHttpSession mockSession && mockSession.isInvalid()) { this.session = null; } // Create new session if necessary. @@ -1346,8 +1347,8 @@ public HttpSession getSession() { @Override public String changeSessionId() { Assert.isTrue(this.session != null, "The request does not have a session"); - if (this.session instanceof MockHttpSession) { - return ((MockHttpSession) this.session).changeSessionId(); + if (this.session instanceof MockHttpSession mockSession) { + return mockSession.changeSessionId(); } return this.session.getId(); } diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpSession.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpSession.java index 99742394801b..0711d3884696 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpSession.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpSession.java @@ -167,11 +167,11 @@ public void setAttribute(String name, @Nullable Object value) { if (value != null) { Object oldValue = this.attributes.put(name, value); if (value != oldValue) { - if (oldValue instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) oldValue).valueUnbound(new HttpSessionBindingEvent(this, name, oldValue)); + if (oldValue instanceof HttpSessionBindingListener listener) { + listener.valueUnbound(new HttpSessionBindingEvent(this, name, oldValue)); } - if (value instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value)); + if (value instanceof HttpSessionBindingListener listener) { + listener.valueBound(new HttpSessionBindingEvent(this, name, value)); } } } @@ -185,8 +185,8 @@ public void removeAttribute(String name) { assertIsValid(); Assert.notNull(name, "Attribute name must not be null"); Object value = this.attributes.remove(name); - if (value instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value)); + if (value instanceof HttpSessionBindingListener listener) { + listener.valueUnbound(new HttpSessionBindingEvent(this, name, value)); } } @@ -199,8 +199,8 @@ public void clearAttributes() { String name = entry.getKey(); Object value = entry.getValue(); it.remove(); - if (value instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value)); + if (value instanceof HttpSessionBindingListener listener) { + listener.valueUnbound(new HttpSessionBindingEvent(this, name, value)); } } } @@ -251,14 +251,14 @@ public Serializable serializeState() { String name = entry.getKey(); Object value = entry.getValue(); it.remove(); - if (value instanceof Serializable) { - state.put(name, (Serializable) value); + if (value instanceof Serializable serializable) { + state.put(name, serializable); } else { // Not serializable... Servlet containers usually automatically // unbind the attribute in this case. - if (value instanceof HttpSessionBindingListener) { - ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value)); + if (value instanceof HttpSessionBindingListener listener) { + listener.valueUnbound(new HttpSessionBindingEvent(this, name, value)); } } } diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockJspWriter.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockJspWriter.java index 467a220a3f13..86c62131e82f 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockJspWriter.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockJspWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -65,8 +65,8 @@ public MockJspWriter(Writer targetWriter) { public MockJspWriter(@Nullable HttpServletResponse response, @Nullable Writer targetWriter) { super(DEFAULT_BUFFER, true); this.response = (response != null ? response : new MockHttpServletResponse()); - if (targetWriter instanceof PrintWriter) { - this.targetWriter = (PrintWriter) targetWriter; + if (targetWriter instanceof PrintWriter printWriter) { + this.targetWriter = printWriter; } else if (targetWriter != null) { this.targetWriter = new PrintWriter(targetWriter); diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPageContext.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPageContext.java index e3976304882f..3063910edf5c 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPageContext.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPageContext.java @@ -342,13 +342,17 @@ public void include(String path, boolean flush) throws ServletException, IOExcep } public byte[] getContentAsByteArray() { - Assert.state(this.response instanceof MockHttpServletResponse, "MockHttpServletResponse required"); - return ((MockHttpServletResponse) this.response).getContentAsByteArray(); + if (this.response instanceof MockHttpServletResponse mockResponse) { + return mockResponse.getContentAsByteArray(); + } + throw new IllegalStateException("MockHttpServletResponse is required"); } public String getContentAsString() throws UnsupportedEncodingException { - Assert.state(this.response instanceof MockHttpServletResponse, "MockHttpServletResponse required"); - return ((MockHttpServletResponse) this.response).getContentAsString(); + if (this.response instanceof MockHttpServletResponse mockResponse) { + return mockResponse.getContentAsString(); + } + throw new IllegalStateException("MockHttpServletResponse is required"); } @Override diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockRequestDispatcher.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockRequestDispatcher.java index 38379ac6ede6..8840efcd3f47 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockRequestDispatcher.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockRequestDispatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -78,11 +78,11 @@ public void include(ServletRequest request, ServletResponse response) { * {@link HttpServletResponseWrapper} decorators if necessary. */ protected MockHttpServletResponse getMockHttpServletResponse(ServletResponse response) { - if (response instanceof MockHttpServletResponse) { - return (MockHttpServletResponse) response; + if (response instanceof MockHttpServletResponse mockResponse) { + return mockResponse; } - if (response instanceof HttpServletResponseWrapper) { - return getMockHttpServletResponse(((HttpServletResponseWrapper) response).getResponse()); + if (response instanceof HttpServletResponseWrapper wrapper) { + return getMockHttpServletResponse(wrapper.getResponse()); } throw new IllegalArgumentException("MockRequestDispatcher requires MockHttpServletResponse"); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/DispatchExceptionHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/DispatchExceptionHandler.java new file mode 100644 index 000000000000..4486a38bfd36 --- /dev/null +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/DispatchExceptionHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2022 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.web.reactive; + +import reactor.core.publisher.Mono; + +import org.springframework.web.server.ServerWebExchange; + +/** + * Contract to map a {@link Throwable} to a {@link HandlerResult}. + * + *

    Supported by {@link DispatcherHandler} when used in the following ways: + *

      + *
    • Set on a {@link HandlerResult#setExceptionHandler HandlerResult}, allowing + * a {@link HandlerAdapter} to apply its exception handling to deferred exceptions + * from asynchronous return values, and to response rendering. + *
    • Implemented by a {@link HandlerAdapter} in order to handle exceptions that + * occur before a request is mapped to a handler. + *
    + * + * @author Rossen Stoyanchev + * @since 6.0 + * @see HandlerAdapter + * @see HandlerResult#setExceptionHandler(DispatchExceptionHandler) + */ +public interface DispatchExceptionHandler { + + /** + * Handle the given exception, mapping it to a {@link HandlerResult} that can + * then be used to render an HTTP response. + * @param exchange the current exchange + * @param ex the exception to handle + * @return a {@code Mono} that emits a {@code HandlerResult} or an error + * signal with the original exception if it remains not handled + */ + Mono handleError(ServerWebExchange exchange, Throwable ex); + +} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java index ee2f4603f8d3..766a1843544b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java @@ -150,8 +150,8 @@ public Mono handle(ServerWebExchange exchange) { .concatMap(mapping -> mapping.getHandler(exchange)) .next() .switchIfEmpty(createNotFoundError()) - .flatMap(handler -> invokeHandler(exchange, handler)) - .flatMap(result -> handleResult(exchange, result)); + .onErrorResume(ex -> handleDispatchError(exchange, ex)) + .flatMap(handler -> handleRequestWith(exchange, handler)); } private Mono createNotFoundError() { @@ -161,14 +161,27 @@ private Mono createNotFoundError() { }); } - private Mono invokeHandler(ServerWebExchange exchange, Object handler) { + private Mono handleDispatchError(ServerWebExchange exchange, Throwable ex) { + Mono resultMono = Mono.error(ex); + if (this.handlerAdapters != null) { + for (HandlerAdapter adapter : this.handlerAdapters) { + if (adapter instanceof DispatchExceptionHandler exceptionHandler) { + resultMono = resultMono.onErrorResume(ex2 -> exceptionHandler.handleError(exchange, ex2)); + } + } + } + return resultMono.flatMap(result -> handleResult(exchange, result)); + } + + private Mono handleRequestWith(ServerWebExchange exchange, Object handler) { if (ObjectUtils.nullSafeEquals(exchange.getResponse().getStatusCode(), HttpStatus.FORBIDDEN)) { return Mono.empty(); // CORS rejection } if (this.handlerAdapters != null) { - for (HandlerAdapter handlerAdapter : this.handlerAdapters) { - if (handlerAdapter.supports(handler)) { - return handlerAdapter.handle(exchange, handler); + for (HandlerAdapter adapter : this.handlerAdapters) { + if (adapter.supports(handler)) { + return adapter.handle(exchange, handler) + .flatMap(result -> handleResult(exchange, result)); } } } @@ -176,25 +189,29 @@ private Mono invokeHandler(ServerWebExchange exchange, Object han } private Mono handleResult(ServerWebExchange exchange, HandlerResult result) { - return getResultHandler(result).handleResult(exchange, result) - .checkpoint("Handler " + result.getHandler() + " [DispatcherHandler]") - .onErrorResume(ex -> - result.applyExceptionHandler(ex).flatMap(exResult -> { - String text = "Exception handler " + exResult.getHandler() + - ", error=\"" + ex.getMessage() + "\" [DispatcherHandler]"; - return getResultHandler(exResult).handleResult(exchange, exResult).checkpoint(text); - })); + Mono resultMono = doHandleResult(exchange, result, "Handler " + result.getHandler()); + if (result.getExceptionHandler() != null) { + resultMono = resultMono.onErrorResume(ex -> + result.getExceptionHandler().handleError(exchange, ex).flatMap(result2 -> + doHandleResult(exchange, result2, "Exception handler " + + result2.getHandler() + ", error=\"" + ex.getMessage() + "\""))); + } + return resultMono; } - private HandlerResultHandler getResultHandler(HandlerResult handlerResult) { + private Mono doHandleResult( + ServerWebExchange exchange, HandlerResult handlerResult, String description) { + if (this.resultHandlers != null) { for (HandlerResultHandler resultHandler : this.resultHandlers) { if (resultHandler.supports(handlerResult)) { - return resultHandler; + description += " [DispatcherHandler]"; + return resultHandler.handleResult(exchange, handlerResult).checkpoint(description); } } } - throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue()); + return Mono.error(new IllegalStateException( + "No HandlerResultHandler for " + handlerResult.getReturnValue())); } @Override diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerAdapter.java index 7361b54d9053..4a7172dff1d2 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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,15 +16,21 @@ package org.springframework.web.reactive; -import java.util.function.Function; - import reactor.core.publisher.Mono; import org.springframework.web.server.ServerWebExchange; /** - * Contract that decouples the {@link DispatcherHandler} from the details of - * invoking a handler and makes it possible to support any handler type. + * Contract to abstract the details of invoking a handler of a given type. + * + *

    An implementation can also choose to be an instance of + * {@link DispatchExceptionHandler} if it wants to handle exceptions that occur + * before the request is successfully mapped to a handler. This allows a + * {@code HandlerAdapter} to expose the same exception handling both for handler + * invocation errors and for errors before a handler is selected. + * In Reactive Streams terms, {@link #handle} handles the onNext signal, while + * {@link DispatchExceptionHandler#handleError} handles the onError signal + * from the dispatch processing chain. * * @author Rossen Stoyanchev * @author Sebastien Deleuze @@ -40,20 +46,27 @@ public interface HandlerAdapter { boolean supports(Object handler); /** - * Handle the request with the given handler. - *

    Implementations are encouraged to handle exceptions resulting from the - * invocation of a handler in order and if necessary to return an alternate - * result that represents an error response. - *

    Furthermore since an async {@code HandlerResult} may produce an error - * later during result handling implementations are also encouraged to - * {@link HandlerResult#setExceptionHandler(Function) set an exception - * handler} on the {@code HandlerResult} so that may also be applied later - * after result handling. + * Handle the request with the given handler, previously checked via + * {@link #supports(Object)}. + *

    Implementations should consider the following for exception handling: + *

      + *
    • Handle invocation exceptions within this method. + *
    • {@link HandlerResult#setExceptionHandler(DispatchExceptionHandler) + * Set an exception handler} on the returned {@code HandlerResult} to handle + * deferred exceptions from asynchronous return values, and to handle + * exceptions from response rendering. + *
    • Implement {@link DispatchExceptionHandler} to extend exception + * handling to exceptions that occur before a handler is selected. + *
    * @param exchange current server exchange * @param handler the selected handler which must have been previously * checked via {@link #supports(Object)} - * @return {@link Mono} that emits a single {@code HandlerResult} or none if - * the request has been fully handled and doesn't require further handling. + * @return {@link Mono} that emits a {@code HandlerResult}, or completes + * empty if the request is fully handled; any error signal would not be + * handled within the {@link DispatcherHandler}, and would instead be + * processed by the chain of registered + * {@link org.springframework.web.server.WebExceptionHandler}s at the end + * of the {@link org.springframework.web.server.WebFilter} chain */ Mono handle(ServerWebExchange exchange, Object handler); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerResult.java b/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerResult.java index 3426dd685151..9844e59f57c8 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerResult.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -44,7 +44,10 @@ public class HandlerResult { private final BindingContext bindingContext; @Nullable - private Function> exceptionHandler; + private DispatchExceptionHandler exceptionHandler; + + @Nullable + private Function> exceptionHandlerFunction; /** @@ -125,20 +128,47 @@ public Model getModel() { } /** - * Configure an exception handler that may be used to produce an alternative - * result when result handling fails. Especially for an async return value - * errors may occur after the invocation of the handler. + * {@link HandlerAdapter} classes can set this to have their exception + * handling mechanism applied to response rendering and to deferred + * exceptions when invoking a handler with an asynchronous return value. + * @param exceptionHandler the exception handler to use + * @since 6.0 + */ + public HandlerResult setExceptionHandler(DispatchExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + return this; + } + + /** + * Return the {@link #setExceptionHandler(DispatchExceptionHandler) + * configured} exception handler. + * @since 6.0 + */ + @Nullable + public DispatchExceptionHandler getExceptionHandler() { + return this.exceptionHandler; + } + + /** + * {@link HandlerAdapter} classes can set this to have their exception + * handling mechanism applied to response rendering and to deferred + * exceptions when invoking a handler with an asynchronous return value. * @param function the error handler * @return the current instance + * @deprecated in favor of {@link #setExceptionHandler(DispatchExceptionHandler)} */ + @Deprecated(since = "6.0", forRemoval = true) public HandlerResult setExceptionHandler(Function> function) { - this.exceptionHandler = function; + this.exceptionHandler = (exchange, ex) -> function.apply(ex); + this.exceptionHandlerFunction = function; return this; } /** * Whether there is an exception handler. + * @deprecated in favor of checking via {@link #getExceptionHandler()} */ + @Deprecated(since = "6.0", forRemoval = true) public boolean hasExceptionHandler() { return (this.exceptionHandler != null); } @@ -147,9 +177,12 @@ public boolean hasExceptionHandler() { * Apply the exception handler and return the alternative result. * @param failure the exception * @return the new result or the same error if there is no exception handler + * @deprecated without a replacement; for internal invocation only, not used as of 6.0 */ + @Deprecated(since = "6.0", forRemoval = true) public Mono applyExceptionHandler(Throwable failure) { - return (this.exceptionHandler != null ? this.exceptionHandler.apply(failure) : Mono.error(failure)); + return (this.exceptionHandlerFunction != null ? + this.exceptionHandlerFunction.apply(failure) : Mono.error(failure)); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerResultHandler.java index 6badfaa68479..2f183a0b6db9 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerResultHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. @@ -21,7 +21,7 @@ import org.springframework.web.server.ServerWebExchange; /** - * Process the {@link HandlerResult}, usually returned by an {@link HandlerAdapter}. + * Process the {@link HandlerResult}, usually returned by a {@link HandlerAdapter}. * * @author Rossen Stoyanchev * @author Sebastien Deleuze diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilder.java index 7a97cf61b229..e94f25a2d741 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/ResourceHandlerRegistry.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/ResourceHandlerRegistry.java index 86f3729f7161..a712bf4b9b1f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/ResourceHandlerRegistry.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/ResourceHandlerRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -148,8 +148,8 @@ protected AbstractUrlHandlerMapping getHandlerMapping() { private ResourceWebHandler getRequestHandler(ResourceHandlerRegistration registration) { ResourceWebHandler handler = registration.getRequestHandler(); for (ResourceTransformer transformer : handler.getResourceTransformers()) { - if (transformer instanceof ResourceTransformerSupport) { - ((ResourceTransformerSupport) transformer).setResourceUrlProvider(this.resourceUrlProvider); + if (transformer instanceof ResourceTransformerSupport resourceTransformerSupport) { + resourceTransformerSupport.setResourceUrlProvider(this.resourceUrlProvider); } } try { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java index 833b64da933c..67487b32390b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractor.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractor.java index 91b6a9fb5684..a85737e3342a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractor.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -38,7 +38,7 @@ public interface BodyExtractor { /** * Extract from the given input message. - * @param inputMessage the request to extract from + * @param inputMessage the message to extract from * @param context the configuration to use * @return the extracted data */ @@ -52,7 +52,7 @@ interface Context { /** * Return the {@link HttpMessageReader HttpMessageReaders} to be used for body extraction. - * @return the stream of message readers + * @return the list of message readers */ List> messageReaders(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java index 8fdf3af80363..4baa24b4f14c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java index ff94b50f418a..7822ff79ebed 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyInserters.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -92,7 +92,8 @@ public static BodyInserter empty() { */ public static BodyInserter fromValue(T body) { Assert.notNull(body, "'body' must not be null"); - Assert.isNull(registry.getAdapter(body.getClass()), "'body' should be an object, for reactive types use a variant specifying a publisher/producer and its related element type"); + Assert.isNull(registry.getAdapter(body.getClass()), + "'body' should be an object, for reactive types use a variant specifying a publisher/producer and its related element type"); return (message, context) -> writeWithMessageWriters(message, context, Mono.just(body), ResolvableType.forInstance(body), null); } @@ -362,8 +363,8 @@ private static Mono writeWithMessage M outputMessage, BodyInserter.Context context, Object body, ResolvableType bodyType, @Nullable ReactiveAdapter adapter) { Publisher publisher; - if (body instanceof Publisher) { - publisher = (Publisher) body; + if (body instanceof Publisher publisherBody) { + publisher = publisherBody; } else if (adapter != null) { publisher = adapter.toPublisher(body); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java index 3cde48309fb1..294dfa039c05 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java @@ -24,9 +24,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.util.StringUtils; - -import static org.springframework.web.reactive.function.client.ClientHttpObservationDocumentation.HighCardinalityKeyNames; -import static org.springframework.web.reactive.function.client.ClientHttpObservationDocumentation.LowCardinalityKeyNames; +import org.springframework.web.reactive.function.client.ClientHttpObservationDocumentation.HighCardinalityKeyNames; +import org.springframework.web.reactive.function.client.ClientHttpObservationDocumentation.LowCardinalityKeyNames; /** * Default implementation for a {@link ClientRequestObservationConvention}, diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java index a9012deb149f..53f7e7ca8050 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java @@ -133,11 +133,11 @@ public MultiValueMap cookies() { public T body(BodyExtractor extractor) { T result = extractor.extract(this.response, this.bodyExtractorContext); String description = "Body from " + this.requestDescription + " [DefaultClientResponse]"; - if (result instanceof Mono) { - return (T) ((Mono) result).checkpoint(description); + if (result instanceof Mono mono) { + return (T) mono.checkpoint(description); } - else if (result instanceof Flux) { - return (T) ((Flux) result).checkpoint(description); + else if (result instanceof Flux flux) { + return (T) flux.checkpoint(description); } else { return result; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java index ef68d4f8cdaf..5ba831ebd8ae 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -113,7 +113,7 @@ public HttpHeaders getHeaders() { @Override public DefaultClientResponseBuilder statusCode(HttpStatusCode statusCode) { - Assert.notNull(statusCode, "StatusCode must not be null"); + Assert.notNull(statusCode, "HttpStatusCode must not be null"); this.statusCode = statusCode; return this; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java index 93732fc2e48c..697cf5a0afd0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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,8 +16,6 @@ package org.springframework.web.reactive.function.client; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -83,12 +81,8 @@ private static class DefaultExchangeStrategies implements ExchangeStrategies { public DefaultExchangeStrategies(ClientCodecConfigurer codecConfigurer) { this.codecConfigurer = codecConfigurer; - this.readers = unmodifiableCopy(this.codecConfigurer.getReaders()); - this.writers = unmodifiableCopy(this.codecConfigurer.getWriters()); - } - - private static List unmodifiableCopy(List list) { - return Collections.unmodifiableList(new ArrayList<>(list)); + this.readers = List.copyOf(this.codecConfigurer.getReaders()); + this.writers = List.copyOf(this.codecConfigurer.getWriters()); } @Override diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index 681f92b51674..2f099e9e40ed 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -17,6 +17,7 @@ package org.springframework.web.reactive.function.client; import java.net.URI; +import java.net.URISyntaxException; import java.nio.charset.Charset; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -33,6 +34,7 @@ import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -52,6 +54,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.BodyExtractor; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; @@ -75,11 +78,6 @@ class DefaultWebClient implements WebClient { private static final DefaultClientRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientRequestObservationConvention(); - /** - * Aligned with ObservationThreadLocalAccessor#KEY from micrometer-core. - */ - private static final String MICROMETER_OBSERVATION = "micrometer.observation"; - private final ExchangeFunction exchangeFunction; private final UriBuilderFactory uriBuilderFactory; @@ -127,7 +125,7 @@ private static List initStatusHandlers( handlerMap.entrySet().stream() .map(entry -> new DefaultResponseSpec.StatusHandler(entry.getKey(), entry.getValue())) .toList()); - }; + } @Override @@ -463,7 +461,7 @@ public Mono exchange() { DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry); observationContext.setCarrier(requestBuilder); observation - .parentObservation(contextView.getOrDefault(MICROMETER_OBSERVATION, null)) + .parentObservation(contextView.getOrDefault(ObservationThreadLocalAccessor.KEY, null)) .start(); ClientRequest request = requestBuilder.build(); observationContext.setUriTemplate((String) request.attribute(URI_TEMPLATE_ATTRIBUTE).orElse(null)); @@ -717,10 +715,22 @@ private Mono applyStatusHandlers(ClientResponse response) { } private Mono insertCheckpoint(Mono result, HttpStatusCode statusCode, HttpRequest request) { - HttpMethod httpMethod = request.getMethod(); + HttpMethod method = request.getMethod(); + URI uri = getUriToLog(request); + return result.checkpoint(statusCode + " from " + method + " " + uri + " [DefaultWebClient]"); + } + + private static URI getUriToLog(HttpRequest request) { URI uri = request.getURI(); - String description = statusCode + " from " + httpMethod + " " + uri + " [DefaultWebClient]"; - return result.checkpoint(description); + if (StringUtils.hasText(uri.getQuery())) { + try { + uri = new URI(uri.getScheme(), uri.getHost(), uri.getPath(), null); + } + catch (URISyntaxException ex) { + // ignore + } + } + return uri; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java index cde4daea9e36..cb77cac3fd2d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/ClientResponseWrapper.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/ClientResponseWrapper.java index cd811c396f29..20bb068a1506 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/ClientResponseWrapper.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/ClientResponseWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java index c62e255028ec..560eb4b201bd 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java @@ -133,35 +133,4 @@ public static WebClientAdapter forClient(WebClient webClient) { return new WebClientAdapter(webClient); } - /** - * Static method to create a {@link HttpServiceProxyFactory} configured to - * use the given {@link WebClient} instance. Effectively a shortcut for: - *
    -	 * WebClientAdapter adapter = WebClientAdapter.forClient(webClient);
    -	 * HttpServiceProxyFactory proxyFactory = new HttpServiceProxyFactory(adapter);
    -	 * 
    - * @param webClient the client to use - * @return the created {@code HttpServiceProxyFactory} instance - * @deprecated in favor of using {@link #forClient(WebClient)} and - * {@link HttpServiceProxyFactory#builder(HttpClientAdapter)} - */ - @SuppressWarnings("removal") - @Deprecated(since = "6.0.0-RC1", forRemoval = true) - public static HttpServiceProxyFactory createHttpServiceProxyFactory(WebClient webClient) { - return new HttpServiceProxyFactory(new WebClientAdapter(webClient)); - } - - /** - * Variant of {@link #createHttpServiceProxyFactory(WebClient)} that accepts - * a {@link WebClient.Builder} and uses it to create the client. - * @param webClientBuilder a builder to create the client to use with - * @return the created {@code HttpServiceProxyFactory} instance - * @deprecated in favor of using {@link #forClient(WebClient)} and - * {@link HttpServiceProxyFactory#builder(HttpClientAdapter)} - */ - @Deprecated(since = "6.0.0-RC1", forRemoval = true) - public static HttpServiceProxyFactory createHttpServiceProxyFactory(WebClient.Builder webClientBuilder) { - return createHttpServiceProxyFactory(webClientBuilder.build()); - } - } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java index 3565d7f7679e..f5782e054a9c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -17,7 +17,6 @@ package org.springframework.web.reactive.function.server; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -126,18 +125,14 @@ public DefaultHandlerStrategies( List exceptionHandlers, LocaleContextResolver localeContextResolver) { - this.messageReaders = unmodifiableCopy(messageReaders); - this.messageWriters = unmodifiableCopy(messageWriters); - this.viewResolvers = unmodifiableCopy(viewResolvers); - this.webFilters = unmodifiableCopy(webFilters); - this.exceptionHandlers = unmodifiableCopy(exceptionHandlers); + this.messageReaders = List.copyOf(messageReaders); + this.messageWriters = List.copyOf(messageWriters); + this.viewResolvers = List.copyOf(viewResolvers); + this.webFilters = List.copyOf(webFilters); + this.exceptionHandlers = List.copyOf(exceptionHandlers); this.localeContextResolver = localeContextResolver; } - private static List unmodifiableCopy(List list) { - return Collections.unmodifiableList(new ArrayList<>(list)); - } - @Override public List> messageReaders() { return this.messageReaders; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java index 3dea118d31b7..a947d25d1be7 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -107,7 +107,7 @@ public RenderingResponse.Builder cookies(Consumer) attribute).isEmpty()) { + if (attribute instanceof Collection collection && collection.isEmpty()) { return this; } return modelAttribute(Conventions.getVariableName(attribute), attribute); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java index 1a0f6c046545..2cc94918677e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -21,7 +21,6 @@ import java.nio.charset.Charset; import java.security.Principal; import java.time.Instant; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -88,7 +87,7 @@ class DefaultServerRequest implements ServerRequest { DefaultServerRequest(ServerWebExchange exchange, List> messageReaders) { this.exchange = exchange; - this.messageReaders = Collections.unmodifiableList(new ArrayList<>(messageReaders)); + this.messageReaders = List.copyOf(messageReaders); this.headers = new DefaultHeaders(); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java index 60caf7c19e38..86e31573887f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -129,8 +129,8 @@ private boolean isResourceUnderLocation(Resource resource) throws IOException { resourcePath = resource.getURL().toExternalForm(); locationPath = StringUtils.cleanPath(this.location.getURL().toString()); } - else if (resource instanceof ClassPathResource) { - resourcePath = ((ClassPathResource) resource).getPath(); + else if (resource instanceof ClassPathResource classPathResource) { + resourcePath = classPathResource.getPath(); locationPath = StringUtils.cleanPath(((ClassPathResource) this.location).getPath()); } else { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java index c7b7cff43ae7..003c1571eb16 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java @@ -795,11 +795,11 @@ public void accept(Visitor visitor) { @Override public void changeParser(PathPatternParser parser) { - if (this.left instanceof ChangePathPatternParserVisitor.Target) { - ((ChangePathPatternParserVisitor.Target) this.left).changeParser(parser); + if (this.left instanceof ChangePathPatternParserVisitor.Target leftTarget) { + leftTarget.changeParser(parser); } - if (this.right instanceof ChangePathPatternParserVisitor.Target) { - ((ChangePathPatternParserVisitor.Target) this.right).changeParser(parser); + if (this.right instanceof ChangePathPatternParserVisitor.Target rightTarget) { + rightTarget.changeParser(parser); } } @@ -841,8 +841,8 @@ public void accept(Visitor visitor) { @Override public void changeParser(PathPatternParser parser) { - if (this.delegate instanceof ChangePathPatternParserVisitor.Target) { - ((ChangePathPatternParserVisitor.Target) this.delegate).changeParser(parser); + if (this.delegate instanceof ChangePathPatternParserVisitor.Target target) { + target.changeParser(parser); } } @@ -909,11 +909,11 @@ public void accept(Visitor visitor) { @Override public void changeParser(PathPatternParser parser) { - if (this.left instanceof ChangePathPatternParserVisitor.Target) { - ((ChangePathPatternParserVisitor.Target) this.left).changeParser(parser); + if (this.left instanceof ChangePathPatternParserVisitor.Target leftTarget) { + leftTarget.changeParser(parser); } - if (this.right instanceof ChangePathPatternParserVisitor.Target) { - ((ChangePathPatternParserVisitor.Target) this.right).changeParser(parser); + if (this.right instanceof ChangePathPatternParserVisitor.Target rightTarget) { + rightTarget.changeParser(parser); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java index 9c0757a6cd5d..2bb5c9454b00 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java @@ -42,6 +42,7 @@ import org.springframework.http.codec.json.Jackson2CodecSupport; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.MultiValueMap; +import org.springframework.web.ErrorResponse; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.result.view.ViewResolver; @@ -104,6 +105,18 @@ static BodyBuilder from(ServerResponse other) { return new DefaultServerResponseBuilder(other); } + /** + * Create a {@code ServerResponse} from the given {@link ErrorResponse}. + * @param response the {@link ErrorResponse} to initialize from + * @return {@code Mono} with the built response + * @since 6.0 + */ + static Mono from(ErrorResponse response) { + return status(response.getStatusCode()) + .headers(headers -> headers.putAll(response.getHeaders())) + .bodyValue(response.getBody()); + } + /** * Create a builder with the given HTTP status. * @param status the response status diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java index ae214f41b34e..2dd4a8142d95 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java @@ -172,7 +172,7 @@ private void setAttributes( if (matchingPattern != null) { attributes.put(BEST_MATCHING_PATTERN_ATTRIBUTE, matchingPattern); ServerHttpObservationFilter.findObservationContext(serverRequest.exchange()) - .ifPresent(context -> context.setPathPattern(matchingPattern)); + .ifPresent(context -> context.setPathPattern(matchingPattern.toString())); } Map uriVariables = (Map) attributes.get(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java index 55788fa304a1..391695382b4f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java @@ -233,8 +233,8 @@ protected boolean hasCorsConfigurationSource(Object handler) { */ @Nullable protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) { - if (handler instanceof CorsConfigurationSource) { - return ((CorsConfigurationSource) handler).getCorsConfiguration(exchange); + if (handler instanceof CorsConfigurationSource ccs) { + return ccs.getCorsConfiguration(exchange); } return null; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java index 39fd49080fa0..a4997b3ab8f7 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -167,7 +167,7 @@ protected Object lookupHandler(PathContainer lookupPath, ServerWebExchange excha exchange.getAttributes().put(BEST_MATCHING_HANDLER_ATTRIBUTE, handler); exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE, pattern); ServerHttpObservationFilter.findObservationContext(exchange) - .ifPresent(context -> context.setPathPattern(pattern)); + .ifPresent(context -> context.setPathPattern(pattern.toString())); exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping); exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, matchInfo.getUriVariables()); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMapping.java index 0044698acfac..bf83751cd261 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -153,8 +153,8 @@ protected void registerHandlers(Map urlMap) throws BeansExceptio url = "/" + url; } // Remove whitespace from handler bean name. - if (handler instanceof String) { - handler = ((String) handler).trim(); + if (handler instanceof String handlerName) { + handler = handlerName.trim(); } registerHandler(url, handler); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java index e56af089603a..5750df71486a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/EncodedResourceResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -272,8 +272,8 @@ public String getDescription() { @Override public HttpHeaders getResponseHeaders() { HttpHeaders headers; - if (this.original instanceof HttpResource) { - headers = ((HttpResource) this.original).getResponseHeaders(); + if (this.original instanceof HttpResource httpResource) { + headers = httpResource.getResponseHeaders(); } else { headers = new HttpHeaders(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java index c22bb68863ef..a8ca7a322f4f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/WebJarsResourceResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/WebJarsResourceResolver.java index 461d72dec4a2..af58c274d91c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/WebJarsResourceResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/WebJarsResourceResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. 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 c798efd35e0f..448557c77001 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -107,9 +107,8 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap public Map getHandlerMethods() { this.mappingRegistry.acquireReadLock(); try { - return Collections.unmodifiableMap( - this.mappingRegistry.getRegistrations().entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().handlerMethod))); + return this.mappingRegistry.getRegistrations().entrySet().stream() + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, entry -> entry.getValue().handlerMethod)); } finally { this.mappingRegistry.releaseReadLock(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java index 04c8c6300bff..16eb34e777dd 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -129,15 +129,16 @@ public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) { * @param providedArgs optional list of argument values to match by type * @return a Mono with a {@link HandlerResult} */ - @SuppressWarnings("KotlinInternalInJava") + @SuppressWarnings({"KotlinInternalInJava", "unchecked"}) public Mono invoke( ServerWebExchange exchange, BindingContext bindingContext, Object... providedArgs) { return getMethodArgumentValues(exchange, bindingContext, providedArgs).flatMap(args -> { Object value; + Method method = getBridgedMethod(); + boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method); try { - Method method = getBridgedMethod(); - if (KotlinDetector.isSuspendingFunction(method)) { + if (isSuspendingFunction) { value = CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args); } else { @@ -163,10 +164,16 @@ public Mono invoke( } MethodParameter returnType = getReturnType(); - ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(returnType.getParameterType()); - boolean asyncVoid = isAsyncVoidReturnType(returnType, adapter); - if ((value == null || asyncVoid) && isResponseHandled(args, exchange)) { - return (asyncVoid ? Mono.from(adapter.toPublisher(value)) : Mono.empty()); + if (isResponseHandled(args, exchange)) { + Class parameterType = returnType.getParameterType(); + ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(parameterType); + boolean asyncVoid = isAsyncVoidReturnType(returnType, adapter); + if (value == null || asyncVoid) { + return (asyncVoid ? Mono.from(adapter.toPublisher(value)) : Mono.empty()); + } + if (isSuspendingFunction && parameterType == void.class) { + return (Mono) value; + } } HandlerResult result = new HandlerResult(this, value, returnType, bindingContext); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java index d16ee75ca9eb..f4b8a60a6dfc 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java @@ -36,6 +36,7 @@ import org.springframework.http.server.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.filter.reactive.ServerHttpObservationFilter; @@ -141,7 +142,7 @@ protected void handleMatch(RequestMappingInfo info, HandlerMethod handlerMethod, exchange.getAttributes().put(BEST_MATCHING_HANDLER_ATTRIBUTE, handlerMethod); exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern); ServerHttpObservationFilter.findObservationContext(exchange) - .ifPresent(context -> context.setPathPattern(bestPattern)); + .ifPresent(context -> context.setPathPattern(bestPattern.toString())); exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables); exchange.getAttributes().put(MATRIX_VARIABLES_ATTRIBUTE, matrixVariables); @@ -169,8 +170,11 @@ protected void handleMatch(RequestMappingInfo info, HandlerMethod handlerMethod, protected HandlerMethod handleNoMatch(Set infos, ServerWebExchange exchange) throws Exception { - PartialMatchHelper helper = new PartialMatchHelper(infos, exchange); + if (CollectionUtils.isEmpty(infos)) { + return null; + } + PartialMatchHelper helper = new PartialMatchHelper(infos, exchange); if (helper.isEmpty()) { return null; } @@ -219,18 +223,19 @@ protected HandlerMethod handleNoMatch(Set infos, /** * Aggregate all partial matches and expose methods checking across them. */ - private static class PartialMatchHelper { + private static final class PartialMatchHelper { private final List partialMatches = new ArrayList<>(); - public PartialMatchHelper(Set infos, ServerWebExchange exchange) { - this.partialMatches.addAll(infos.stream() - .filter(info -> info.getPatternsCondition().getMatchingCondition(exchange) != null) - .map(info -> new PartialMatch(info, exchange)).toList()); + PartialMatchHelper(Set infos, ServerWebExchange exchange) { + for (RequestMappingInfo info : infos) { + if (info.getPatternsCondition().getMatchingCondition(exchange) != null) { + this.partialMatches.add(new PartialMatch(info, exchange)); + } + } } - /** * Whether there are any partial matches. */ diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/SyncInvocableHandlerMethod.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/SyncInvocableHandlerMethod.java index 51d83a654788..f6ed41ac86d1 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/SyncInvocableHandlerMethod.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/SyncInvocableHandlerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java index b53873b8fa2e..ea82434edd55 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java @@ -34,8 +34,10 @@ import org.springframework.core.codec.Hints; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.server.reactive.ServerHttpRequest; @@ -145,7 +147,16 @@ protected Mono readBody(MethodParameter bodyParam, @Nullable MethodParam ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); - MediaType contentType = request.getHeaders().getContentType(); + MediaType contentType; + HttpHeaders headers = request.getHeaders(); + try { + contentType = headers.getContentType(); + } + catch (InvalidMediaTypeException ex) { + throw new UnsupportedMediaTypeStatusException( + "Can't parse Content-Type [" + headers.getFirst("Content-Type") + "]: " + ex.getMessage()); + } + MediaType mediaType = (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM); Object[] hints = extractValidationHints(bodyParam); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java index e8ff100c455d..50a73f1a2dbf 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -330,38 +330,47 @@ private InvocableHandlerMethod createAttributeMethod(Object bean, Method method) } /** - * Find an {@code @ExceptionHandler} method in {@code @ControllerAdvice} - * components or in the controller of the given {@code @RequestMapping} method. + * Look for an {@code @ExceptionHandler} method within the class of the given + * controller method, and also within {@code @ControllerAdvice} classes that + * are applicable to the class of the given controller method. + * @param ex the exception to find a handler for + * @param handlerMethod the controller method that raised the exception, or + * if {@code null}, check only {@code @ControllerAdvice} classes. */ @Nullable - public InvocableHandlerMethod getExceptionHandlerMethod(Throwable ex, HandlerMethod handlerMethod) { - Class handlerType = handlerMethod.getBeanType(); - - // Controller-local first... - Object targetBean = handlerMethod.getBean(); - Method targetMethod = this.exceptionHandlerCache - .computeIfAbsent(handlerType, ExceptionHandlerMethodResolver::new) - .resolveMethodByThrowable(ex); + public InvocableHandlerMethod getExceptionHandlerMethod(Throwable ex, @Nullable HandlerMethod handlerMethod) { + + Class handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null); + Object exceptionHandlerObject = null; + Method exceptionHandlerMethod = null; + + if (handlerType != null) { + // Controller-local first... + exceptionHandlerObject = handlerMethod.getBean(); + exceptionHandlerMethod = this.exceptionHandlerCache + .computeIfAbsent(handlerType, ExceptionHandlerMethodResolver::new) + .resolveMethodByThrowable(ex); + } - if (targetMethod == null) { + if (exceptionHandlerMethod == null) { // Global exception handlers... for (Map.Entry entry : this.exceptionHandlerAdviceCache.entrySet()) { ControllerAdviceBean advice = entry.getKey(); if (advice.isApplicableToBeanType(handlerType)) { - targetBean = advice.resolveBean(); - targetMethod = entry.getValue().resolveMethodByThrowable(ex); - if (targetMethod != null) { + exceptionHandlerMethod = entry.getValue().resolveMethodByThrowable(ex); + if (exceptionHandlerMethod != null) { + exceptionHandlerObject = advice.resolveBean(); break; } } } } - if (targetMethod == null) { + if (exceptionHandlerObject == null || exceptionHandlerMethod == null) { return null; } - InvocableHandlerMethod invocable = new InvocableHandlerMethod(targetBean, targetMethod); + InvocableHandlerMethod invocable = new InvocableHandlerMethod(exceptionHandlerObject, exceptionHandlerMethod); invocable.setArgumentResolvers(this.exceptionHandlerResolvers); return invocable; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java index b1c37b591dd6..24a0eaf3c2a3 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -247,7 +247,7 @@ private Mono constructAttribute(Constructor ctor, String attributeName, } } } - value = (value instanceof List ? ((List) value).toArray() : value); + value = (value instanceof List list ? list.toArray() : value); MethodParameter methodParam = new MethodParameter(ctor, i); if (value == null && methodParam.isOptional()) { args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java index 7cefca16f687..9b0e2924a18a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java index ceb8085ed61b..ffecf6f21bbf 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.function.Function; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -38,6 +37,7 @@ import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.method.HandlerMethod; import org.springframework.web.reactive.BindingContext; +import org.springframework.web.reactive.DispatchExceptionHandler; import org.springframework.web.reactive.HandlerAdapter; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.HandlerResult; @@ -52,7 +52,8 @@ * @author Rossen Stoyanchev * @since 5.0 */ -public class RequestMappingHandlerAdapter implements HandlerAdapter, ApplicationContextAware, InitializingBean { +public class RequestMappingHandlerAdapter + implements HandlerAdapter, DispatchExceptionHandler, ApplicationContextAware, InitializingBean { private static final Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class); @@ -192,19 +193,20 @@ public Mono handle(ServerWebExchange exchange, Object handler) { InvocableHandlerMethod invocableMethod = this.methodResolver.getRequestMappingMethod(handlerMethod); - Function> exceptionHandler = - ex -> handleException(ex, handlerMethod, bindingContext, exchange); + DispatchExceptionHandler exceptionHandler = + (exchange2, ex) -> handleException(exchange, ex, handlerMethod, bindingContext); return this.modelInitializer .initModel(handlerMethod, bindingContext, exchange) .then(Mono.defer(() -> invocableMethod.invoke(exchange, bindingContext))) .doOnNext(result -> result.setExceptionHandler(exceptionHandler)) .doOnNext(result -> bindingContext.saveModel()) - .onErrorResume(exceptionHandler); + .onErrorResume(ex -> exceptionHandler.handleError(exchange, ex)); } - private Mono handleException(Throwable exception, HandlerMethod handlerMethod, - BindingContext bindingContext, ServerWebExchange exchange) { + private Mono handleException( + ServerWebExchange exchange, Throwable exception, + @Nullable HandlerMethod handlerMethod, @Nullable BindingContext bindingContext) { Assert.state(this.methodResolver != null, "Not initialized"); @@ -212,14 +214,21 @@ private Mono handleException(Throwable exception, HandlerMethod h exchange.getAttributes().remove(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); exchange.getResponse().getHeaders().clearContentHeaders(); - InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(exception, handlerMethod); + InvocableHandlerMethod invocable = + this.methodResolver.getExceptionHandlerMethod(exception, handlerMethod); + if (invocable != null) { ArrayList exceptions = new ArrayList<>(); try { if (logger.isDebugEnabled()) { logger.debug(exchange.getLogPrefix() + "Using @ExceptionHandler " + invocable); } - bindingContext.getModel().asMap().clear(); + if (bindingContext != null) { + bindingContext.getModel().asMap().clear(); + } + else { + bindingContext = new BindingContext(); + } // Expose causes as provided arguments as well Throwable exToExpose = exception; @@ -245,4 +254,9 @@ private Mono handleException(Throwable exception, HandlerMethod h return Mono.error(exception); } + @Override + public Mono handleError(ServerWebExchange exchange, Throwable ex) { + return handleException(exchange, ex, null, null); + } + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java index ad9335ecc2f3..174e2fa4d730 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -177,8 +177,8 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class handler @Nullable private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); - RequestCondition condition = (element instanceof Class ? - getCustomTypeCondition((Class) element) : getCustomMethodCondition((Method) element)); + RequestCondition condition = (element instanceof Class clazz ? + getCustomTypeCondition(clazz) : getCustomMethodCondition((Method) element)); return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandler.java index 99f8e386fa3e..367bd8de351c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandler.java @@ -53,8 +53,8 @@ * for global exception handling in an application. Subclasses can override * individual methods that handle a specific exception, override * {@link #handleExceptionInternal} to override common handling of all exceptions, - * or {@link #createResponseEntity} to intercept the final step of creating the - * {@link ResponseEntity} from the selected HTTP status code, headers, and body. + * or override {@link #createResponseEntity} to intercept the final step of creating + * the {@link ResponseEntity} from the selected HTTP status code, headers, and body. * * @author Rossen Stoyanchev * @since 6.0 @@ -75,9 +75,18 @@ public void setMessageSource(MessageSource messageSource) { this.messageSource = messageSource; } + /** + * Get the {@link MessageSource} that this exception handler uses. + * @since 6.0.3 + */ + @Nullable + protected MessageSource getMessageSource() { + return this.messageSource; + } + /** - * Handle all exceptions raised within Spring MVC handling of the request . + * Handle all exceptions raised within Spring MVC handling of the request. * @param ex the exception to handle * @param exchange the current request-response */ @@ -309,10 +318,14 @@ protected ProblemDetail createProblemDetail( Exception ex, HttpStatusCode status, String defaultDetail, @Nullable String detailMessageCode, @Nullable Object[] detailMessageArguments, ServerWebExchange exchange) { - ErrorResponse response = ErrorResponse.createFor( - ex, status, null, defaultDetail, detailMessageCode, detailMessageArguments); - - return response.updateAndGetBody(this.messageSource, getLocale(exchange)); + ErrorResponse.Builder builder = ErrorResponse.builder(ex, status, defaultDetail); + if (detailMessageCode != null) { + builder.detailMessageCode(detailMessageCode); + } + if (detailMessageArguments != null) { + builder.detailMessageArguments(detailMessageArguments); + } + return builder.build().updateAndGetBody(this.messageSource, getLocale(exchange)); } private static Locale getLocale(ServerWebExchange exchange) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java index 3021273dc398..b520d952c01f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java @@ -137,8 +137,8 @@ public Mono handleResult(ServerWebExchange exchange, HandlerResult result) return returnValueMono.flatMap(returnValue -> { HttpEntity httpEntity; - if (returnValue instanceof HttpEntity) { - httpEntity = (HttpEntity) returnValue; + if (returnValue instanceof HttpEntity he) { + httpEntity = he; } else if (returnValue instanceof ErrorResponse response) { httpEntity = new ResponseEntity<>(response.getBody(), response.getHeaders(), response.getStatusCode()); @@ -146,8 +146,8 @@ else if (returnValue instanceof ErrorResponse response) { else if (returnValue instanceof ProblemDetail detail) { httpEntity = ResponseEntity.of(detail).build(); } - else if (returnValue instanceof HttpHeaders) { - httpEntity = new ResponseEntity<>((HttpHeaders) returnValue, HttpStatus.OK); + else if (returnValue instanceof HttpHeaders headers) { + httpEntity = new ResponseEntity<>(headers, HttpStatus.OK); } else { throw new IllegalArgumentException( diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeMethodArgumentResolver.java index 953f1a0dcbe8..8837aea44115 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -124,8 +124,8 @@ else if (UriBuilder.class == paramType || UriComponentsBuilder.class == paramTyp @Nullable private TimeZone getTimeZone(LocaleContext localeContext) { TimeZone timeZone = null; - if (localeContext instanceof TimeZoneAwareLocaleContext) { - timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone(); + if (localeContext instanceof TimeZoneAwareLocaleContext timeZoneAwareLocaleContext) { + timeZone = timeZoneAwareLocaleContext.getTimeZone(); } return timeZone; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/BindStatus.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/BindStatus.java index 4d9b75518d8d..91b3a6ca2faf 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/BindStatus.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/BindStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -126,8 +126,8 @@ else if (this.expression.endsWith("*")) { this.objectErrors = this.errors.getFieldErrors(this.expression); this.value = this.errors.getFieldValue(this.expression); this.valueType = this.errors.getFieldType(this.expression); - if (this.errors instanceof BindingResult) { - this.bindingResult = (BindingResult) this.errors; + if (this.errors instanceof BindingResult br) { + this.bindingResult = br; this.actualValue = this.bindingResult.getRawFieldValue(this.expression); this.editor = this.bindingResult.findEditor(this.expression, null); } @@ -162,8 +162,8 @@ else if (this.expression.endsWith("*")) { this.errorMessages = new String[0]; } - if (htmlEscape && this.value instanceof String) { - this.value = HtmlUtils.htmlEscape((String) this.value); + if (htmlEscape && this.value instanceof String text) { + this.value = HtmlUtils.htmlEscape(text); } } @@ -238,8 +238,8 @@ public Object getActualValue() { * will get HTML-escaped. */ public String getDisplayValue() { - if (this.value instanceof String) { - return (String) this.value; + if (this.value instanceof String displayValue) { + return displayValue; } if (this.value != null) { return (this.htmlEscape ? diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/RedirectView.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/RedirectView.java index d004cf558b89..1ffcdd33ef27 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/RedirectView.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/RedirectView.java @@ -160,13 +160,6 @@ public String[] getHosts() { return this.hosts; } - - @Override - public void afterPropertiesSet() throws Exception { - super.afterPropertiesSet(); - } - - @Override public boolean isRedirectView() { return true; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java index 98220c067d29..21022710346c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -324,8 +324,8 @@ protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) ApplicationContext context = getApplicationContext(); if (context != null) { Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName); - if (initialized instanceof View) { - return (View) initialized; + if (initialized instanceof View initializedView) { + return initializedView; } } return view; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java index 480fd0775d08..f6389c5bc133 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -223,7 +223,7 @@ else if (Rendering.class.isAssignableFrom(clazz)) { if (view == null) { view = getDefaultViewName(exchange); } - viewsMono = (view instanceof String ? resolveViews((String) view, locale) : + viewsMono = (view instanceof String viewName ? resolveViews(viewName, locale) : Mono.just(Collections.singletonList((View) view))); } else if (Model.class.isAssignableFrom(clazz)) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/HandshakeInfo.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/HandshakeInfo.java index 0bef75a832ca..a12a667a31f4 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/HandshakeInfo.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/HandshakeInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -117,9 +117,9 @@ public HandshakeInfo(URI uri, HttpHeaders headers, MultiValueMapAlso implements {@code Subscriber} so it can be used to subscribe to diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/AbstractWebSocketSession.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/AbstractWebSocketSession.java index 1fc285962c3d..6d04480e3e14 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/AbstractWebSocketSession.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/AbstractWebSocketSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -64,18 +64,21 @@ public abstract class AbstractWebSocketSession implements WebSocketSession { * Create a new WebSocket session. */ protected AbstractWebSocketSession(T delegate, String id, HandshakeInfo info, DataBufferFactory bufferFactory) { - Assert.notNull(delegate, "Native session is required."); - Assert.notNull(id, "Session id is required."); - Assert.notNull(info, "HandshakeInfo is required."); - Assert.notNull(bufferFactory, "DataBuffer factory is required."); + Assert.notNull(delegate, "Native session is required"); + Assert.notNull(id, "Session id is required"); + Assert.notNull(info, "HandshakeInfo is required"); + Assert.notNull(bufferFactory, "DataBuffer factory is required"); this.delegate = delegate; this.id = id; this.handshakeInfo = info; this.bufferFactory = bufferFactory; - this.attributes.putAll(info.getAttributes()); this.logPrefix = initLogPrefix(info, id); + info.getAttributes().entrySet().stream() + .filter(entry -> (entry.getKey() != null && entry.getValue() != null)) + .forEach(entry -> this.attributes.put(entry.getKey(), entry.getValue())); + if (logger.isDebugEnabled()) { logger.debug(getLogPrefix() + "Session id \"" + getId() + "\" for " + getHandshakeInfo().getUri()); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/StandardWebSocketHandlerAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/StandardWebSocketHandlerAdapter.java index 68fed3f2b5db..2f78f073e712 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/StandardWebSocketHandlerAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/adapter/StandardWebSocketHandlerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -36,8 +36,8 @@ import org.springframework.web.reactive.socket.WebSocketSession; /** - * Adapter for Java WebSocket API (JSR-356) that delegates events to a reactive - * {@link WebSocketHandler} and its session. + * Adapter for the Jakarta WebSocket API (JSR-356) that delegates events to a + * reactive {@link WebSocketHandler} and its session. * * @author Violeta Georgieva * @author Rossen Stoyanchev diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java index f7782aa552d3..94264479c91e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java @@ -39,6 +39,7 @@ /** * A {@link WebSocketClient} implementation for use with Jetty * {@link org.eclipse.jetty.websocket.client.WebSocketClient}. + * Only supported on Jetty 11, superseded by {@link StandardWebSocketClient}. * *

    Note: the Jetty {@code WebSocketClient} requires * lifecycle management and must be started and stopped. This is automatically @@ -49,7 +50,9 @@ * @author Rossen Stoyanchev * @author Juergen Hoeller * @since 5.0 + * @deprecated as of 6.0.3, in favor of {@link StandardWebSocketClient} */ +@Deprecated(since = "6.0.3", forRemoval = true) public class JettyWebSocketClient implements WebSocketClient, Lifecycle { private static final Log logger = LogFactory.getLog(JettyWebSocketClient.class); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/StandardWebSocketClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/StandardWebSocketClient.java index 74e01b480ee9..a57ccc063e3e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/StandardWebSocketClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/StandardWebSocketClient.java @@ -43,7 +43,7 @@ import org.springframework.web.reactive.socket.adapter.StandardWebSocketSession; /** - * {@link WebSocketClient} implementation for use with the Java WebSocket API. + * {@link WebSocketClient} implementation for use with the Jakarta WebSocket API. * * @author Violeta Georgieva * @author Rossen Stoyanchev diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/TomcatWebSocketClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/TomcatWebSocketClient.java index 72c44fc496b8..55d9b991f22f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/TomcatWebSocketClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/TomcatWebSocketClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -26,14 +26,15 @@ import org.springframework.web.reactive.socket.adapter.TomcatWebSocketSession; /** - * {@link WebSocketClient} implementation for use with the Java WebSocket API. + * {@link WebSocketClient} implementation for use with Tomcat, + * based on the Jakarta WebSocket API. * * @author Violeta Georgieva * @since 5.0 + * @see StandardWebSocketClient */ public class TomcatWebSocketClient extends StandardWebSocketClient { - public TomcatWebSocketClient() { this(new WsWebSocketContainer()); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/RequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/RequestUpgradeStrategy.java index 059e52376dc1..09ed2403981c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/RequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/RequestUpgradeStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -33,11 +33,12 @@ * *

    Typically there is one such strategy for every {@link ServerHttpRequest} * and {@link ServerHttpResponse} type except in the case of Servlet containers - * for which the standard Java WebSocket API JSR-356 does not define a way to + * for which the standard Jakarta WebSocket API (JSR-356) does not define a way to * upgrade a request so a custom strategy is needed for every Servlet container. * * @author Rossen Stoyanchev * @since 5.0 + * @see org.springframework.web.reactive.socket.server.upgrade.StandardWebSocketUpgradeStrategy */ public interface RequestUpgradeStrategy { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java index 567e5448eb13..e29b696cccc5 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/HandshakeWebSocketService.java @@ -38,12 +38,17 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.MultiValueMap; -import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.reactive.socket.HandshakeInfo; import org.springframework.web.reactive.socket.WebSocketHandler; import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy; import org.springframework.web.reactive.socket.server.WebSocketService; +import org.springframework.web.reactive.socket.server.upgrade.JettyRequestUpgradeStrategy; +import org.springframework.web.reactive.socket.server.upgrade.ReactorNetty2RequestUpgradeStrategy; +import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy; +import org.springframework.web.reactive.socket.server.upgrade.StandardWebSocketUpgradeStrategy; +import org.springframework.web.reactive.socket.server.upgrade.TomcatRequestUpgradeStrategy; +import org.springframework.web.reactive.socket.server.upgrade.UndertowRequestUpgradeStrategy; import org.springframework.web.server.MethodNotAllowedException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; @@ -55,6 +60,7 @@ * also be explicitly configured. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 5.0 */ public class HandshakeWebSocketService implements WebSocketService, Lifecycle { @@ -66,28 +72,32 @@ public class HandshakeWebSocketService implements WebSocketService, Lifecycle { private static final Mono> EMPTY_ATTRIBUTES = Mono.just(Collections.emptyMap()); - private static final boolean tomcatPresent; + private static final boolean tomcatWsPresent; - private static final boolean jettyPresent; + private static final boolean jettyWsPresent; - private static final boolean undertowPresent; + private static final boolean undertowWsPresent; private static final boolean reactorNettyPresent; private static final boolean reactorNetty2Present; static { - ClassLoader loader = HandshakeWebSocketService.class.getClassLoader(); - tomcatPresent = ClassUtils.isPresent("org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", loader); - jettyPresent = ClassUtils.isPresent("org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer", loader); - undertowPresent = ClassUtils.isPresent("io.undertow.websockets.WebSocketProtocolHandshakeHandler", loader); - reactorNettyPresent = ClassUtils.isPresent("reactor.netty.http.server.HttpServerResponse", loader); - reactorNetty2Present = ClassUtils.isPresent("reactor.netty5.http.server.HttpServerResponse", loader); + ClassLoader classLoader = HandshakeWebSocketService.class.getClassLoader(); + tomcatWsPresent = ClassUtils.isPresent( + "org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", classLoader); + jettyWsPresent = ClassUtils.isPresent( + "org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer", classLoader); + undertowWsPresent = ClassUtils.isPresent( + "io.undertow.websockets.WebSocketProtocolHandshakeHandler", classLoader); + reactorNettyPresent = ClassUtils.isPresent( + "reactor.netty.http.server.HttpServerResponse", classLoader); + reactorNetty2Present = ClassUtils.isPresent( + "reactor.netty5.http.server.HttpServerResponse", classLoader); } - protected static final Log logger = LogFactory.getLog(HandshakeWebSocketService.class); - + private static final Log logger = LogFactory.getLog(HandshakeWebSocketService.class); private final RequestUpgradeStrategy upgradeStrategy; @@ -114,40 +124,6 @@ public HandshakeWebSocketService(RequestUpgradeStrategy upgradeStrategy) { this.upgradeStrategy = upgradeStrategy; } - static RequestUpgradeStrategy initUpgradeStrategy() { - String className; - if (tomcatPresent) { - className = "TomcatRequestUpgradeStrategy"; - } - else if (jettyPresent) { - className = "JettyRequestUpgradeStrategy"; - } - else if (undertowPresent) { - className = "UndertowRequestUpgradeStrategy"; - } - else if (reactorNettyPresent) { - // As late as possible (Reactor Netty commonly used for WebClient) - className = "ReactorNettyRequestUpgradeStrategy"; - } - else if (reactorNetty2Present) { - // As late as possible (Reactor Netty commonly used for WebClient) - className = "ReactorNetty2RequestUpgradeStrategy"; - } - else { - throw new IllegalStateException("No suitable default RequestUpgradeStrategy found"); - } - - try { - className = "org.springframework.web.reactive.socket.server.upgrade." + className; - Class clazz = ClassUtils.forName(className, HandshakeWebSocketService.class.getClassLoader()); - return (RequestUpgradeStrategy) ReflectionUtils.accessibleConstructor(clazz).newInstance(); - } - catch (Throwable ex) { - throw new IllegalStateException( - "Failed to instantiate RequestUpgradeStrategy: " + className, ex); - } - } - /** * Return the {@link RequestUpgradeStrategy} for WebSocket requests. @@ -188,8 +164,8 @@ public void start() { } protected void doStart() { - if (getUpgradeStrategy() instanceof Lifecycle) { - ((Lifecycle) getUpgradeStrategy()).start(); + if (getUpgradeStrategy() instanceof Lifecycle lifecycle) { + lifecycle.start(); } } @@ -202,8 +178,8 @@ public void stop() { } protected void doStop() { - if (getUpgradeStrategy() instanceof Lifecycle) { - ((Lifecycle) getUpgradeStrategy()).stop(); + if (getUpgradeStrategy() instanceof Lifecycle lifecycle) { + lifecycle.stop(); } } @@ -292,4 +268,44 @@ private HandshakeInfo createHandshakeInfo(ServerWebExchange exchange, ServerHttp return new HandshakeInfo(uri, headers, cookies, principal, protocol, remoteAddress, attributes, logPrefix); } + + static RequestUpgradeStrategy initUpgradeStrategy() { + if (tomcatWsPresent) { + return new TomcatRequestUpgradeStrategy(); + } + else if (jettyWsPresent) { + return new JettyRequestUpgradeStrategy(); + } + else if (undertowWsPresent) { + return new UndertowRequestUpgradeStrategy(); + } + else if (reactorNettyPresent) { + // As late as possible (Reactor Netty commonly used for WebClient) + return ReactorNettyStrategyDelegate.forReactorNetty1(); + } + else if (reactorNetty2Present) { + // As late as possible (Reactor Netty commonly used for WebClient) + return ReactorNettyStrategyDelegate.forReactorNetty2(); + } + else { + // Let's assume Jakarta WebSocket API 2.1+ + return new StandardWebSocketUpgradeStrategy(); + } + } + + + /** + * Inner class to avoid a reachable dependency on Reactor Netty API. + */ + private static class ReactorNettyStrategyDelegate { + + public static RequestUpgradeStrategy forReactorNetty1() { + return new ReactorNettyRequestUpgradeStrategy(); + } + + public static RequestUpgradeStrategy forReactorNetty2() { + return new ReactorNetty2RequestUpgradeStrategy(); + } + } + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/WebSocketHandlerAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/WebSocketHandlerAdapter.java index eadb886e170f..ef8c697038e2 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/WebSocketHandlerAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/WebSocketHandlerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java index c95782934866..7f56ca7565db 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -40,7 +40,7 @@ import org.springframework.web.server.ServerWebExchange; /** - * A {@link RequestUpgradeStrategy} for Jetty 11. + * A WebSocket {@code RequestUpgradeStrategy} for Jetty 11. * * @author Rossen Stoyanchev * @since 5.3.4 diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNetty2RequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNetty2RequestUpgradeStrategy.java index 14623902a5fd..57c97e5b87a8 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNetty2RequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNetty2RequestUpgradeStrategy.java @@ -35,7 +35,7 @@ import org.springframework.web.server.ServerWebExchange; /** - * A {@link RequestUpgradeStrategy} for use with Reactor Netty for Netty 5. + * A WebSocket {@code RequestUpgradeStrategy} for Reactor Netty for Netty 5. * *

    This class is based on {@link ReactorNettyRequestUpgradeStrategy}. *\ diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNettyRequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNettyRequestUpgradeStrategy.java index d8f98768a67d..a7ac18a564b4 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNettyRequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNettyRequestUpgradeStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -35,7 +35,7 @@ import org.springframework.web.server.ServerWebExchange; /** - * A {@link RequestUpgradeStrategy} for use with Reactor Netty. + * A WebSocket {@code RequestUpgradeStrategy} for Reactor Netty. * * @author Rossen Stoyanchev * @since 5.0 diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/StandardWebSocketUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/StandardWebSocketUpgradeStrategy.java new file mode 100644 index 000000000000..d5341505e0b6 --- /dev/null +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/StandardWebSocketUpgradeStrategy.java @@ -0,0 +1,199 @@ +/* + * Copyright 2002-2022 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.web.reactive.socket.server.upgrade; + +import java.util.Collections; +import java.util.Map; +import java.util.function.Supplier; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.websocket.Endpoint; +import jakarta.websocket.server.ServerContainer; +import jakarta.websocket.server.ServerEndpointConfig; +import reactor.core.publisher.Mono; + +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.web.reactive.socket.HandshakeInfo; +import org.springframework.web.reactive.socket.WebSocketHandler; +import org.springframework.web.reactive.socket.adapter.ContextWebSocketHandler; +import org.springframework.web.reactive.socket.adapter.StandardWebSocketHandlerAdapter; +import org.springframework.web.reactive.socket.adapter.TomcatWebSocketSession; +import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy; +import org.springframework.web.server.ServerWebExchange; + +/** + * A WebSocket {@code RequestUpgradeStrategy} for the Jakarta WebSocket API 2.1+. + * + *

    This strategy serves as a fallback if no specific server has been detected. + * It can also be used with Jakarta EE 10 level servers such as Tomcat 10.1 and + * Undertow 2.3 directly, relying on their built-in Jakarta WebSocket 2.1 support. + * + * @author Juergen Hoeller + * @author Violeta Georgieva + * @author Rossen Stoyanchev + * @since 6.0 + * @see jakarta.websocket.server.ServerContainer#upgradeHttpToWebSocket + */ +public class StandardWebSocketUpgradeStrategy implements RequestUpgradeStrategy { + + private static final String SERVER_CONTAINER_ATTR = "jakarta.websocket.server.ServerContainer"; + + + @Nullable + private Long asyncSendTimeout; + + @Nullable + private Long maxSessionIdleTimeout; + + @Nullable + private Integer maxTextMessageBufferSize; + + @Nullable + private Integer maxBinaryMessageBufferSize; + + @Nullable + private ServerContainer serverContainer; + + + /** + * Exposes the underlying config option on + * {@link ServerContainer#setAsyncSendTimeout(long)}. + */ + public void setAsyncSendTimeout(Long timeoutInMillis) { + this.asyncSendTimeout = timeoutInMillis; + } + + @Nullable + public Long getAsyncSendTimeout() { + return this.asyncSendTimeout; + } + + /** + * Exposes the underlying config option on + * {@link ServerContainer#setDefaultMaxSessionIdleTimeout(long)}. + */ + public void setMaxSessionIdleTimeout(Long timeoutInMillis) { + this.maxSessionIdleTimeout = timeoutInMillis; + } + + @Nullable + public Long getMaxSessionIdleTimeout() { + return this.maxSessionIdleTimeout; + } + + /** + * Exposes the underlying config option on + * {@link ServerContainer#setDefaultMaxTextMessageBufferSize(int)}. + */ + public void setMaxTextMessageBufferSize(Integer bufferSize) { + this.maxTextMessageBufferSize = bufferSize; + } + + @Nullable + public Integer getMaxTextMessageBufferSize() { + return this.maxTextMessageBufferSize; + } + + /** + * Exposes the underlying config option on + * {@link ServerContainer#setDefaultMaxBinaryMessageBufferSize(int)}. + */ + public void setMaxBinaryMessageBufferSize(Integer bufferSize) { + this.maxBinaryMessageBufferSize = bufferSize; + } + + @Nullable + public Integer getMaxBinaryMessageBufferSize() { + return this.maxBinaryMessageBufferSize; + } + + @Override + public Mono upgrade(ServerWebExchange exchange, WebSocketHandler handler, + @Nullable String subProtocol, Supplier handshakeInfoFactory){ + + ServerHttpRequest request = exchange.getRequest(); + ServerHttpResponse response = exchange.getResponse(); + + HttpServletRequest servletRequest = ServerHttpRequestDecorator.getNativeRequest(request); + HttpServletResponse servletResponse = ServerHttpResponseDecorator.getNativeResponse(response); + + HandshakeInfo handshakeInfo = handshakeInfoFactory.get(); + DataBufferFactory bufferFactory = response.bufferFactory(); + + // Trigger WebFlux preCommit actions and upgrade + return exchange.getResponse().setComplete() + .then(Mono.deferContextual(contextView -> { + Endpoint endpoint = new StandardWebSocketHandlerAdapter( + ContextWebSocketHandler.decorate(handler, contextView), + session -> new TomcatWebSocketSession(session, handshakeInfo, bufferFactory)); + + String requestURI = servletRequest.getRequestURI(); + DefaultServerEndpointConfig config = new DefaultServerEndpointConfig(requestURI, endpoint); + config.setSubprotocols(subProtocol != null ? + Collections.singletonList(subProtocol) : Collections.emptyList()); + + try { + upgradeHttpToWebSocket(servletRequest, servletResponse, config, Collections.emptyMap()); + } + catch (Exception ex) { + return Mono.error(ex); + } + return Mono.empty(); + })); + } + + + protected void upgradeHttpToWebSocket(HttpServletRequest request, HttpServletResponse response, + ServerEndpointConfig endpointConfig, Map pathParams) throws Exception { + + getContainer(request).upgradeHttpToWebSocket(request, response, endpointConfig, pathParams); + } + + protected ServerContainer getContainer(HttpServletRequest request) { + if (this.serverContainer == null) { + Object container = request.getServletContext().getAttribute(SERVER_CONTAINER_ATTR); + Assert.state(container instanceof ServerContainer, + "ServletContext attribute 'jakarta.websocket.server.ServerContainer' not found."); + this.serverContainer = (ServerContainer) container; + initServerContainer(this.serverContainer); + } + return this.serverContainer; + } + + private void initServerContainer(ServerContainer serverContainer) { + if (this.asyncSendTimeout != null) { + serverContainer.setAsyncSendTimeout(this.asyncSendTimeout); + } + if (this.maxSessionIdleTimeout != null) { + serverContainer.setDefaultMaxSessionIdleTimeout(this.maxSessionIdleTimeout); + } + if (this.maxTextMessageBufferSize != null) { + serverContainer.setDefaultMaxTextMessageBufferSize(this.maxTextMessageBufferSize); + } + if (this.maxBinaryMessageBufferSize != null) { + serverContainer.setDefaultMaxBinaryMessageBufferSize(this.maxBinaryMessageBufferSize); + } + } + +} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java index ce3a3719d60a..8837a4543f69 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java @@ -16,171 +16,31 @@ package org.springframework.web.reactive.socket.server.upgrade; -import java.util.Collections; -import java.util.function.Supplier; +import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.websocket.Endpoint; -import jakarta.websocket.server.ServerContainer; +import jakarta.websocket.server.ServerEndpointConfig; import org.apache.tomcat.websocket.server.WsServerContainer; -import reactor.core.publisher.Mono; - -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpRequestDecorator; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpResponseDecorator; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.web.reactive.socket.HandshakeInfo; -import org.springframework.web.reactive.socket.WebSocketHandler; -import org.springframework.web.reactive.socket.adapter.ContextWebSocketHandler; -import org.springframework.web.reactive.socket.adapter.StandardWebSocketHandlerAdapter; -import org.springframework.web.reactive.socket.adapter.TomcatWebSocketSession; -import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy; -import org.springframework.web.server.ServerWebExchange; /** - * A {@link RequestUpgradeStrategy} for use with Tomcat. + * A WebSocket {@code RequestUpgradeStrategy} for Apache Tomcat. Compatible with Tomcat 10 + * and higher, in particular with Tomcat 10.0 (not based on Jakarta WebSocket 2.1 yet). * * @author Violeta Georgieva * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 5.0 + * @see org.apache.tomcat.websocket.server.WsServerContainer#upgradeHttpToWebSocket */ -public class TomcatRequestUpgradeStrategy implements RequestUpgradeStrategy { - - private static final String SERVER_CONTAINER_ATTR = "jakarta.websocket.server.ServerContainer"; - - - @Nullable - private Long asyncSendTimeout; - - @Nullable - private Long maxSessionIdleTimeout; - - @Nullable - private Integer maxTextMessageBufferSize; - - @Nullable - private Integer maxBinaryMessageBufferSize; - - @Nullable - private WsServerContainer serverContainer; - - - /** - * Exposes the underlying config option on - * {@link jakarta.websocket.server.ServerContainer#setAsyncSendTimeout(long)}. - */ - public void setAsyncSendTimeout(Long timeoutInMillis) { - this.asyncSendTimeout = timeoutInMillis; - } - - @Nullable - public Long getAsyncSendTimeout() { - return this.asyncSendTimeout; - } - - /** - * Exposes the underlying config option on - * {@link jakarta.websocket.server.ServerContainer#setDefaultMaxSessionIdleTimeout(long)}. - */ - public void setMaxSessionIdleTimeout(Long timeoutInMillis) { - this.maxSessionIdleTimeout = timeoutInMillis; - } - - @Nullable - public Long getMaxSessionIdleTimeout() { - return this.maxSessionIdleTimeout; - } - - /** - * Exposes the underlying config option on - * {@link jakarta.websocket.server.ServerContainer#setDefaultMaxTextMessageBufferSize(int)}. - */ - public void setMaxTextMessageBufferSize(Integer bufferSize) { - this.maxTextMessageBufferSize = bufferSize; - } - - @Nullable - public Integer getMaxTextMessageBufferSize() { - return this.maxTextMessageBufferSize; - } - - /** - * Exposes the underlying config option on - * {@link jakarta.websocket.server.ServerContainer#setDefaultMaxBinaryMessageBufferSize(int)}. - */ - public void setMaxBinaryMessageBufferSize(Integer bufferSize) { - this.maxBinaryMessageBufferSize = bufferSize; - } - - @Nullable - public Integer getMaxBinaryMessageBufferSize() { - return this.maxBinaryMessageBufferSize; - } +public class TomcatRequestUpgradeStrategy extends StandardWebSocketUpgradeStrategy { @Override - public Mono upgrade(ServerWebExchange exchange, WebSocketHandler handler, - @Nullable String subProtocol, Supplier handshakeInfoFactory){ - - ServerHttpRequest request = exchange.getRequest(); - ServerHttpResponse response = exchange.getResponse(); - - HttpServletRequest servletRequest = ServerHttpRequestDecorator.getNativeRequest(request); - HttpServletResponse servletResponse = ServerHttpResponseDecorator.getNativeResponse(response); - - HandshakeInfo handshakeInfo = handshakeInfoFactory.get(); - DataBufferFactory bufferFactory = response.bufferFactory(); - - // Trigger WebFlux preCommit actions and upgrade - return exchange.getResponse().setComplete() - .then(Mono.deferContextual(contextView -> { - Endpoint endpoint = new StandardWebSocketHandlerAdapter( - ContextWebSocketHandler.decorate(handler, contextView), - session -> new TomcatWebSocketSession(session, handshakeInfo, bufferFactory)); - - String requestURI = servletRequest.getRequestURI(); - DefaultServerEndpointConfig config = new DefaultServerEndpointConfig(requestURI, endpoint); - config.setSubprotocols(subProtocol != null ? - Collections.singletonList(subProtocol) : Collections.emptyList()); - - WsServerContainer container = getContainer(servletRequest); - try { - container.upgradeHttpToWebSocket(servletRequest, servletResponse, config, Collections.emptyMap()); - } - catch (Exception ex) { - return Mono.error(ex); - } - return Mono.empty(); - })); - } - - private WsServerContainer getContainer(HttpServletRequest request) { - if (this.serverContainer == null) { - Object container = request.getServletContext().getAttribute(SERVER_CONTAINER_ATTR); - Assert.state(container instanceof WsServerContainer, - "ServletContext attribute 'jakarta.websocket.server.ServerContainer' not found."); - this.serverContainer = (WsServerContainer) container; - initServerContainer(this.serverContainer); - } - return this.serverContainer; - } + protected void upgradeHttpToWebSocket(HttpServletRequest request, HttpServletResponse response, + ServerEndpointConfig endpointConfig, Map pathParams) throws Exception { - private void initServerContainer(ServerContainer serverContainer) { - if (this.asyncSendTimeout != null) { - serverContainer.setAsyncSendTimeout(this.asyncSendTimeout); - } - if (this.maxSessionIdleTimeout != null) { - serverContainer.setDefaultMaxSessionIdleTimeout(this.maxSessionIdleTimeout); - } - if (this.maxTextMessageBufferSize != null) { - serverContainer.setDefaultMaxTextMessageBufferSize(this.maxTextMessageBufferSize); - } - if (this.maxBinaryMessageBufferSize != null) { - serverContainer.setDefaultMaxBinaryMessageBufferSize(this.maxBinaryMessageBufferSize); - } + ((WsServerContainer) getContainer(request)).upgradeHttpToWebSocket( + request, response, endpointConfig, pathParams); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/UndertowRequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/UndertowRequestUpgradeStrategy.java index 91e01527ee32..60f3742318b0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/UndertowRequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/UndertowRequestUpgradeStrategy.java @@ -42,7 +42,7 @@ import org.springframework.web.server.ServerWebExchange; /** - * A {@link RequestUpgradeStrategy} for use with Undertow. + * A WebSocket {@code RequestUpgradeStrategy} for Undertow. * * @author Violeta Georgieva * @author Rossen Stoyanchev diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/HeaderContentTypeResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/HeaderContentTypeResolverTests.java index 8c2a104e8a85..83da5f1f13c8 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/HeaderContentTypeResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/HeaderContentTypeResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -44,7 +44,7 @@ public void resolveMediaTypes() throws Exception { List mediaTypes = this.resolver.resolveMediaTypes( MockServerWebExchange.from(MockServerHttpRequest.get("/").header("accept", header))); - assertThat(mediaTypes.size()).isEqualTo(4); + assertThat(mediaTypes).hasSize(4); assertThat(mediaTypes.get(0).toString()).isEqualTo("text/html"); assertThat(mediaTypes.get(1).toString()).isEqualTo("text/x-c"); assertThat(mediaTypes.get(2).toString()).isEqualTo("text/x-dvi;q=0.8"); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/CorsRegistryTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/CorsRegistryTests.java index 92cbe9a4f9a8..f03254e7bff3 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/CorsRegistryTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/CorsRegistryTests.java @@ -45,7 +45,7 @@ public void noMapping() { public void multipleMappings() { this.registry.addMapping("/foo"); this.registry.addMapping("/bar"); - assertThat(this.registry.getCorsConfigurations().size()).isEqualTo(2); + assertThat(this.registry.getCorsConfigurations()).hasSize(2); } @Test @@ -54,7 +54,7 @@ public void customizedMapping() { .allowedMethods("DELETE").allowCredentials(false).allowedHeaders("header1", "header2") .exposedHeaders("header3", "header4").maxAge(3600); Map configs = this.registry.getCorsConfigurations(); - assertThat(configs.size()).isEqualTo(1); + assertThat(configs).hasSize(1); CorsConfiguration config = configs.get("/foo"); assertThat(config.getAllowedOrigins()).isEqualTo(Arrays.asList("https://domain2.com", "https://domain2.com")); assertThat(config.getAllowedMethods()).isEqualTo(Collections.singletonList("DELETE")); @@ -83,7 +83,7 @@ void combine() { this.registry.addMapping("/api/**").combine(otherConfig); Map configs = this.registry.getCorsConfigurations(); - assertThat(configs.size()).isEqualTo(1); + assertThat(configs).hasSize(1); CorsConfiguration config = configs.get("/api/**"); assertThat(config.getAllowedOrigins()).isEqualTo(Collections.singletonList("http://localhost:3000")); assertThat(config.getAllowedMethods()).isEqualTo(Collections.singletonList("*")); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java index 6714853e39d0..050ec4f347a4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java @@ -112,7 +112,7 @@ public void requestMappingHandlerAdapter() { boolean condition = initializer.getValidator() instanceof LocalValidatorFactoryBean; assertThat(condition).isTrue(); assertThat(initializer.getConversionService()).isSameAs(formatterRegistry.getValue()); - assertThat(codecsConfigurer.getValue().getReaders().size()).isEqualTo(16); + assertThat(codecsConfigurer.getValue().getReaders()).hasSize(16); } @Test diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/ResourceHandlerRegistryTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/ResourceHandlerRegistryTests.java index 64934cf08c33..a88884c09836 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/ResourceHandlerRegistryTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/ResourceHandlerRegistryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -106,7 +106,7 @@ void mediaTypes() { this.registration.setMediaTypes(Collections.singletonMap("bar", mediaType)); ResourceWebHandler requestHandler = this.registration.getRequestHandler(); - assertThat(requestHandler.getMediaTypes()).size().isEqualTo(1); + assertThat(requestHandler.getMediaTypes()).hasSize(1); assertThat(requestHandler.getMediaTypes()).containsEntry("bar", mediaType); } @@ -161,7 +161,7 @@ void resourceChainWithoutCaching() { assertThat(resolvers.get(1)).isInstanceOf(PathResourceResolver.class); List transformers = handler.getResourceTransformers(); - assertThat(transformers).hasSize(0); + assertThat(transformers).isEmpty(); } @Test diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/ViewResolverRegistryTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/ViewResolverRegistryTests.java index 1f69cb33f35f..77bbfef15bfa 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/ViewResolverRegistryTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/ViewResolverRegistryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -71,7 +71,7 @@ public void hasRegistrations() { @Test public void noResolvers() { assertThat(this.registry.getViewResolvers()).isNotNull(); - assertThat(this.registry.getViewResolvers().size()).isEqualTo(0); + assertThat(this.registry.getViewResolvers()).isEmpty(); assertThat(this.registry.hasRegistrations()).isFalse(); } @@ -81,7 +81,7 @@ public void customViewResolver() { this.registry.viewResolver(viewResolver); assertThat(this.registry.getViewResolvers().get(0)).isSameAs(viewResolver); - assertThat(this.registry.getViewResolvers().size()).isEqualTo(1); + assertThat(this.registry.getViewResolvers()).hasSize(1); } @Test @@ -89,7 +89,7 @@ public void defaultViews() throws Exception { View view = new HttpMessageWriterView(new Jackson2JsonEncoder()); this.registry.defaultViews(view); - assertThat(this.registry.getDefaultViews().size()).isEqualTo(1); + assertThat(this.registry.getDefaultViews()).hasSize(1); assertThat(this.registry.getDefaultViews().get(0)).isSameAs(view); } @@ -98,7 +98,7 @@ public void scriptTemplate() { this.registry.scriptTemplate().prefix("/").suffix(".html"); List viewResolvers = this.registry.getViewResolvers(); - assertThat(viewResolvers.size()).isEqualTo(1); + assertThat(viewResolvers).hasSize(1); assertThat(viewResolvers.get(0).getClass()).isEqualTo(ScriptTemplateViewResolver.class); DirectFieldAccessor accessor = new DirectFieldAccessor(viewResolvers.get(0)); assertThat(accessor.getPropertyValue("prefix")).isEqualTo("/"); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java index 777cbec7533e..922ce42a1423 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java @@ -131,7 +131,7 @@ public void customPathMatchConfig() { assertThat(mapping).isNotNull(); Map map = mapping.getHandlerMethods(); - assertThat(map.size()).isEqualTo(1); + assertThat(map).hasSize(1); assertThat(map.keySet().iterator().next().getPatternsCondition().getPatterns()) .isEqualTo(Collections.singleton(new PathPatternParser().parse("/api/user/{id}"))); } @@ -145,7 +145,7 @@ public void requestMappingHandlerAdapter() { assertThat(adapter).isNotNull(); List> readers = adapter.getMessageReaders(); - assertThat(readers.size()).isEqualTo(16); + assertThat(readers).hasSize(16); ResolvableType multiValueMapType = forClassWithGenerics(MultiValueMap.class, String.class, String.class); @@ -183,7 +183,7 @@ public void customMessageConverterConfig() { assertThat(adapter).isNotNull(); List> messageReaders = adapter.getMessageReaders(); - assertThat(messageReaders.size()).isEqualTo(2); + assertThat(messageReaders).hasSize(2); assertHasMessageReader(messageReaders, forClass(String.class), TEXT_PLAIN); assertHasMessageReader(messageReaders, forClass(TestBean.class), APPLICATION_XML); @@ -200,7 +200,7 @@ public void responseEntityResultHandler() { assertThat(handler.getOrder()).isEqualTo(0); List> writers = handler.getMessageWriters(); - assertThat(writers.size()).isEqualTo(14); + assertThat(writers).hasSize(16); assertHasMessageWriter(writers, forClass(byte[].class), APPLICATION_OCTET_STREAM); assertHasMessageWriter(writers, forClass(ByteBuffer.class), APPLICATION_OCTET_STREAM); @@ -228,7 +228,7 @@ public void responseBodyResultHandler() { assertThat(handler.getOrder()).isEqualTo(100); List> writers = handler.getMessageWriters(); - assertThat(writers.size()).isEqualTo(14); + assertThat(writers).hasSize(16); assertHasMessageWriter(writers, forClass(byte[].class), APPLICATION_OCTET_STREAM); assertHasMessageWriter(writers, forClass(ByteBuffer.class), APPLICATION_OCTET_STREAM); @@ -256,11 +256,11 @@ public void viewResolutionResultHandler() { assertThat(handler.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE); List resolvers = handler.getViewResolvers(); - assertThat(resolvers.size()).isEqualTo(1); + assertThat(resolvers).hasSize(1); assertThat(resolvers.get(0).getClass()).isEqualTo(FreeMarkerViewResolver.class); List views = handler.getDefaultViews(); - assertThat(views.size()).isEqualTo(1); + assertThat(views).hasSize(1); MimeType type = MimeTypeUtils.parseMimeType("application/json"); assertThat(views.get(0).getSupportedMediaTypes().get(0)).isEqualTo(type); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartRouterFunctionIntegrationTests.java similarity index 97% rename from spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartIntegrationTests.java rename to spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartRouterFunctionIntegrationTests.java index 9b59c236b9fc..42687fbe5b86 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartRouterFunctionIntegrationTests.java @@ -59,7 +59,7 @@ /** * @author Sebastien Deleuze */ -class MultipartIntegrationTests extends AbstractRouterFunctionIntegrationTests { +class MultipartRouterFunctionIntegrationTests extends AbstractRouterFunctionIntegrationTests { private final WebClient webClient = WebClient.create(); @@ -208,7 +208,7 @@ public Mono multipartData(ServerRequest request) { .flatMap(map -> { Map parts = map.toSingleValueMap(); try { - assertThat(parts.size()).isEqualTo(2); + assertThat(parts).hasSize(2); assertThat(((FilePart) parts.get("fooPart")).filename()).isEqualTo("foo.txt"); assertThat(((FormFieldPart) parts.get("barPart")).value()).isEqualTo("bar"); return Flux.fromIterable(parts.values()) @@ -225,7 +225,7 @@ public Mono parts(ServerRequest request) { return request.body(BodyExtractors.toParts()).collectList() .flatMap(parts -> { try { - assertThat(parts.size()).isEqualTo(2); + assertThat(parts).hasSize(2); assertThat(((FilePart) parts.get(0)).filename()).isEqualTo("foo.txt"); assertThat(((FormFieldPart) parts.get(1)).value()).isEqualTo("bar"); return Flux.fromIterable(parts) @@ -240,7 +240,7 @@ public Mono parts(ServerRequest request) { public Mono transferTo(ServerRequest request) { return request.body(BodyExtractors.toParts()) - .filter(part -> part instanceof FilePart) + .filter(FilePart.class::isInstance) .next() .cast(FilePart.class) .flatMap(part -> createTempFile() diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilderTests.java index 14521cbac400..84d8d430ae06 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -17,7 +17,6 @@ package org.springframework.web.reactive.function.client; import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; @@ -49,12 +48,12 @@ * Unit tests for {@link DefaultClientRequestBuilder}. * @author Arjen Poutsma */ -public class DefaultClientRequestBuilderTests { +class DefaultClientRequestBuilderTests { private static final URI DEFAULT_URL = URI.create("https://example.com"); @Test - public void from() { + void from() { ClientRequest other = ClientRequest.create(GET, DEFAULT_URL) .header("foo", "bar") .cookie("baz", "qux") @@ -70,9 +69,9 @@ public void from() { assertThat(result.url()).isEqualTo(DEFAULT_URL); assertThat(result.method()).isEqualTo(GET); - assertThat(result.headers().size()).isEqualTo(1); + assertThat(result.headers()).hasSize(1); assertThat(result.headers().getFirst("foo")).isEqualTo("baar"); - assertThat(result.cookies().size()).isEqualTo(1); + assertThat(result.cookies()).hasSize(1); assertThat(result.cookies().getFirst("baz")).isEqualTo("quux"); assertThat(result.httpRequest()).isNotNull(); assertThat(result.attributes().get("attributeKey")).isEqualTo("attributeValue"); @@ -80,7 +79,7 @@ public void from() { } @Test - public void fromCopiesBody() { + void fromCopiesBody() { String body = "foo"; BodyInserter inserter = (response, strategies) -> { byte[] bodyBytes = body.getBytes(UTF_8); @@ -106,7 +105,7 @@ public void fromCopiesBody() { } @Test - public void method() { + void method() { ClientRequest.Builder builder = ClientRequest.create(DELETE, DEFAULT_URL); assertThat(builder.build().method()).isEqualTo(DELETE); @@ -115,9 +114,9 @@ public void method() { } @Test - public void url() throws URISyntaxException { - URI url1 = new URI("https://example.com/foo"); - URI url2 = new URI("https://example.com/bar"); + void url() { + URI url1 = URI.create("https://example.com/foo"); + URI url2 = URI.create("https://example.com/bar"); ClientRequest.Builder builder = ClientRequest.create(DELETE, url1); assertThat(builder.build().url()).isEqualTo(url1); @@ -126,13 +125,13 @@ public void url() throws URISyntaxException { } @Test - public void cookie() { + void cookie() { ClientRequest result = ClientRequest.create(GET, DEFAULT_URL).cookie("foo", "bar").build(); assertThat(result.cookies().getFirst("foo")).isEqualTo("bar"); } @Test - public void build() { + void build() { ClientRequest result = ClientRequest.create(GET, DEFAULT_URL) .header("MyKey", "MyValue") .cookie("foo", "bar") @@ -155,7 +154,7 @@ public void build() { } @Test - public void bodyInserter() { + void bodyInserter() { String body = "foo"; BodyInserter inserter = (response, strategies) -> { byte[] bodyBytes = body.getBytes(UTF_8); @@ -180,7 +179,7 @@ public void bodyInserter() { } @Test - public void bodyClass() { + void bodyClass() { String body = "foo"; Publisher publisher = Mono.just(body); ClientRequest result = ClientRequest.create(POST, DEFAULT_URL).body(publisher, String.class).build(); @@ -199,7 +198,7 @@ public void bodyClass() { } @Test - public void bodyParameterizedTypeReference() { + void bodyParameterizedTypeReference() { String body = "foo"; Publisher publisher = Mono.just(body); ParameterizedTypeReference typeReference = new ParameterizedTypeReference<>() {}; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilderTests.java index 27eb214a6e53..571321fafc14 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilderTests.java @@ -88,10 +88,10 @@ void mutate() { assertThat(result.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - assertThat(result.headers().asHttpHeaders().size()).isEqualTo(3); + assertThat(result.headers().asHttpHeaders()).hasSize(3); assertThat(result.headers().asHttpHeaders().getFirst("foo")).isEqualTo("baar"); assertThat(result.headers().asHttpHeaders().getFirst("bar")).isEqualTo("baz"); - assertThat(result.cookies().size()).isEqualTo(1); + assertThat(result.cookies()).hasSize(1); assertThat(result.cookies().getFirst("baz").getValue()).isEqualTo("quux"); assertThat(result.logPrefix()).isEqualTo("my-prefix"); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java index 0bccd8e28e46..f9e058a76e43 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java @@ -293,19 +293,19 @@ public void mutateDoesCopy() { // Now, verify what each client has. WebClient.Builder builder1 = client1.mutate(); - builder1.filters(filters -> assertThat(filters.size()).isEqualTo(1)); - builder1.defaultHeaders(headers -> assertThat(headers.size()).isEqualTo(1)); - builder1.defaultCookies(cookies -> assertThat(cookies.size()).isEqualTo(1)); + builder1.filters(filters -> assertThat(filters).hasSize(1)); + builder1.defaultHeaders(headers -> assertThat(headers).hasSize(1)); + builder1.defaultCookies(cookies -> assertThat(cookies).hasSize(1)); WebClient.Builder builder2 = client2.mutate(); - builder2.filters(filters -> assertThat(filters.size()).isEqualTo(2)); - builder2.defaultHeaders(headers -> assertThat(headers.size()).isEqualTo(2)); - builder2.defaultCookies(cookies -> assertThat(cookies.size()).isEqualTo(2)); + builder2.filters(filters -> assertThat(filters).hasSize(2)); + builder2.defaultHeaders(headers -> assertThat(headers).hasSize(2)); + builder2.defaultCookies(cookies -> assertThat(cookies).hasSize(2)); WebClient.Builder builder1a = client1a.mutate(); - builder1a.filters(filters -> assertThat(filters.size()).isEqualTo(2)); - builder1a.defaultHeaders(headers -> assertThat(headers.size()).isEqualTo(2)); - builder1a.defaultCookies(cookies -> assertThat(cookies.size()).isEqualTo(2)); + builder1a.filters(filters -> assertThat(filters).hasSize(2)); + builder1a.defaultHeaders(headers -> assertThat(headers).hasSize(2)); + builder1a.defaultCookies(cookies -> assertThat(cookies).hasSize(2)); } @Test diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java index 57357d0854ac..f13fac8e6d1b 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java @@ -84,8 +84,8 @@ private void setUp(DataBufferFactory bufferFactory) { private ReactorClientHttpConnector initConnector() { assertThat(super.bufferFactory).isNotNull(); - if (super.bufferFactory instanceof NettyDataBufferFactory) { - ByteBufAllocator allocator = ((NettyDataBufferFactory) super.bufferFactory).getByteBufAllocator(); + if (super.bufferFactory instanceof NettyDataBufferFactory nettyDataBufferFactory) { + ByteBufAllocator allocator = nettyDataBufferFactory.getByteBufAllocator(); return new ReactorClientHttpConnector(this.factory, client -> client.option(ChannelOption.ALLOCATOR, allocator)); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientHttpServiceProxyTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientHttpServiceProxyTests.java index 9934cc7d0ebc..80d490f17e98 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientHttpServiceProxyTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientHttpServiceProxyTests.java @@ -18,6 +18,7 @@ import java.io.IOException; +import java.net.URI; import java.time.Duration; import java.util.HashMap; import java.util.Map; @@ -25,15 +26,22 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import org.springframework.lang.Nullable; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.service.annotation.GetExchange; +import org.springframework.web.service.annotation.PostExchange; import org.springframework.web.service.invoker.HttpServiceProxyFactory; import static org.assertj.core.api.Assertions.assertThat; @@ -65,8 +73,7 @@ void shutdown() throws IOException { @Test - void greeting() throws Exception { - + void greeting() { prepareResponse(response -> response.setHeader("Content-Type", "text/plain").setBody("Hello Spring!")); @@ -77,8 +84,7 @@ void greeting() throws Exception { } @Test - void greetingWithRequestAttribute() throws Exception { - + void greetingWithRequestAttribute() { Map attributes = new HashMap<>(); WebClient webClient = WebClient.builder() @@ -100,7 +106,34 @@ void greetingWithRequestAttribute() throws Exception { assertThat(attributes).containsEntry("myAttribute", "myAttributeValue"); } - private TestHttpService initHttpService() throws Exception { + @Test // gh-29624 + void uri() throws Exception { + String expectedBody = "hello"; + prepareResponse(response -> response.setResponseCode(200).setBody(expectedBody)); + + URI dynamicUri = this.server.url("/greeting/123").uri(); + String actualBody = initHttpService().getGreetingById(dynamicUri, "456"); + + assertThat(actualBody).isEqualTo(expectedBody); + assertThat(this.server.takeRequest().getRequestUrl().uri()).isEqualTo(dynamicUri); + } + + @Test + void formData() throws Exception { + prepareResponse(response -> response.setResponseCode(201)); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("param1", "value 1"); + map.add("param2", "value 2"); + + initHttpService().postForm(map); + + RecordedRequest request = this.server.takeRequest(); + assertThat(request.getHeaders().get("Content-Type")).isEqualTo("application/x-www-form-urlencoded;charset=UTF-8"); + assertThat(request.getBody().readUtf8()).isEqualTo("param1=value+1¶m2=value+2"); + } + + private TestHttpService initHttpService() { WebClient webClient = WebClient.builder().baseUrl(this.server.url("/").toString()).build(); return initHttpService(webClient); } @@ -127,7 +160,12 @@ private interface TestHttpService { @GetExchange("/greeting") Mono getGreetingWithAttribute(@RequestAttribute String myAttribute); - } + @GetExchange("/greetings/{id}") + String getGreetingById(@Nullable URI uri, @PathVariable String id); + + @PostExchange(contentType = "application/x-www-form-urlencoded") + void postForm(@RequestParam MultiValueMap params); + } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java index 987a2a855881..b4df3715aa58 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestTests.java index 0a6d06296bb6..783b593c3ca4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestTests.java @@ -403,7 +403,7 @@ public void formData() { Mono> resultData = request.formData(); StepVerifier.create(resultData) .consumeNextWith(formData -> { - assertThat(formData.size()).isEqualTo(2); + assertThat(formData).hasSize(2); assertThat(formData.getFirst("foo")).isEqualTo("bar"); assertThat(formData.getFirst("baz")).isEqualTo("qux"); }) @@ -436,7 +436,7 @@ public void multipartData() { Mono> resultData = request.multipartData(); StepVerifier.create(resultData) .consumeNextWith(formData -> { - assertThat(formData.size()).isEqualTo(2); + assertThat(formData).hasSize(2); Part part = formData.getFirst("foo"); boolean condition1 = part instanceof FormFieldPart; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java index 2862324575d7..b55889c27500 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java @@ -97,7 +97,7 @@ void flux(HttpServer httpServer) throws Exception { assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); List body = result.getBody(); - assertThat(body.size()).isEqualTo(2); + assertThat(body).hasSize(2); assertThat(body.get(0).getName()).isEqualTo("John"); assertThat(body.get(1).getName()).isEqualTo("Jane"); } @@ -208,13 +208,13 @@ public Mono attributes(ServerRequest request) { Map pathVariables = (Map) request.attributes().get(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE); assertThat(pathVariables).isNotNull(); - assertThat(pathVariables.size()).isEqualTo(1); + assertThat(pathVariables).hasSize(1); assertThat(pathVariables.get("foo")).isEqualTo("bar"); pathVariables = (Map) request.attributes().get(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); assertThat(pathVariables).isNotNull(); - assertThat(pathVariables.size()).isEqualTo(1); + assertThat(pathVariables).hasSize(1); assertThat(pathVariables.get("foo")).isEqualTo("bar"); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/PublisherHandlerFunctionIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/PublisherHandlerFunctionIntegrationTests.java index 02e47f48a93f..c2eb9b72977d 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/PublisherHandlerFunctionIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/PublisherHandlerFunctionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -75,7 +75,7 @@ void flux(HttpServer httpServer) throws Exception { assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); List body = result.getBody(); - assertThat(body.size()).isEqualTo(2); + assertThat(body).hasSize(2); assertThat(body.get(0).getName()).isEqualTo("John"); assertThat(body.get(1).getName()).isEqualTo("Jane"); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RenderingResponseIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RenderingResponseIntegrationTests.java index 00f3b136e5b6..32064132e07e 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RenderingResponseIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RenderingResponseIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -81,7 +81,7 @@ void normal(HttpServer httpServer) throws Exception { assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); Map body = parseBody(result.getBody()); - assertThat(body.size()).isEqualTo(2); + assertThat(body).hasSize(2); assertThat(body.get("name")).isEqualTo("foo"); assertThat(body.get("bar")).isEqualTo("baz"); } @@ -95,7 +95,7 @@ void filter(HttpServer httpServer) throws Exception { assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); Map body = parseBody(result.getBody()); - assertThat(body.size()).isEqualTo(3); + assertThat(body).hasSize(3); assertThat(body.get("name")).isEqualTo("foo"); assertThat(body.get("bar")).isEqualTo("baz"); assertThat(body.get("qux")).isEqualTo("quux"); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/support/RouterFunctionMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/support/RouterFunctionMappingTests.java index 344c57da3c11..c8615618357e 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/support/RouterFunctionMappingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/support/RouterFunctionMappingTests.java @@ -22,7 +22,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.http.codec.ServerCodecConfigurer; -import org.springframework.http.observation.reactive.ServerRequestObservationContext; +import org.springframework.http.server.reactive.observation.ServerRequestObservationContext; import org.springframework.web.filter.reactive.ServerHttpObservationFilter; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.function.server.HandlerFunction; @@ -137,7 +137,7 @@ void mappedRequestShouldHoldAttributes() { assertThat(matchingPattern).isNotNull(); assertThat(matchingPattern.getPatternString()).isEqualTo("/match"); assertThat(ServerHttpObservationFilter.findObservationContext(exchange)) - .hasValueSatisfying(context -> assertThat(context.getPathPattern()).isEqualTo(matchingPattern)); + .hasValueSatisfying(context -> assertThat(context.getPathPattern()).isEqualTo(matchingPattern.getPatternString())); ServerRequest serverRequest = exchange.getAttribute(RouterFunctions.REQUEST_ATTRIBUTE); assertThat(serverRequest).isNotNull(); @@ -148,7 +148,7 @@ void mappedRequestShouldHoldAttributes() { private ServerWebExchange createExchange(String urlTemplate) { MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(urlTemplate)); - ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange); + ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); exchange.getAttributes().put(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext); return exchange; } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/Msg.java b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/Msg.java index dd4d32df0b8b..f57c3ca4de23 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/Msg.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/protobuf/Msg.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/SimpleUrlHandlerMappingIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/SimpleUrlHandlerMappingIntegrationTests.java index 01390877cb64..b9a4ce1c90d5 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/SimpleUrlHandlerMappingIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/SimpleUrlHandlerMappingIntegrationTests.java @@ -56,9 +56,7 @@ class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandlerIntegra @Override protected HttpHandler createHttpHandler() { - AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext(); - wac.register(WebConfig.class); - wac.refresh(); + AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext(WebConfig.class); return WebHttpHandlerBuilder.webHandler(new DispatcherHandler(wac)) .exceptionHandler(new ResponseStatusExceptionHandler()) @@ -70,7 +68,7 @@ protected HttpHandler createHttpHandler() { void requestToFooHandler(HttpServer httpServer) throws Exception { startServer(httpServer); - URI url = new URI("http://localhost:" + this.port + "/foo"); + URI url = URI.create("http://localhost:" + this.port + "/foo"); RequestEntity request = RequestEntity.get(url).build(); @SuppressWarnings("resource") ResponseEntity response = new RestTemplate().exchange(request, byte[].class); @@ -83,7 +81,7 @@ void requestToFooHandler(HttpServer httpServer) throws Exception { public void requestToBarHandler(HttpServer httpServer) throws Exception { startServer(httpServer); - URI url = new URI("http://localhost:" + this.port + "/bar"); + URI url = URI.create("http://localhost:" + this.port + "/bar"); RequestEntity request = RequestEntity.get(url).build(); @SuppressWarnings("resource") ResponseEntity response = new RestTemplate().exchange(request, byte[].class); @@ -96,7 +94,7 @@ public void requestToBarHandler(HttpServer httpServer) throws Exception { void requestToHeaderSettingHandler(HttpServer httpServer) throws Exception { startServer(httpServer); - URI url = new URI("http://localhost:" + this.port + "/header"); + URI url = URI.create("http://localhost:" + this.port + "/header"); RequestEntity request = RequestEntity.get(url).build(); @SuppressWarnings("resource") ResponseEntity response = new RestTemplate().exchange(request, byte[].class); @@ -110,7 +108,7 @@ void requestToHeaderSettingHandler(HttpServer httpServer) throws Exception { void handlerNotFound(HttpServer httpServer) throws Exception { startServer(httpServer); - URI url = new URI("http://localhost:" + this.port + "/oops"); + URI url = URI.create("http://localhost:" + this.port + "/oops"); RequestEntity request = RequestEntity.get(url).build(); try { new RestTemplate().exchange(request, byte[].class); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/HeadersRequestConditionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/HeadersRequestConditionTests.java index 5b0dd59c8129..5385d3cb1beb 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/HeadersRequestConditionTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/HeadersRequestConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -148,7 +148,7 @@ public void combine() { HeadersRequestCondition result = condition1.combine(condition2); Collection conditions = result.getContent(); - assertThat(conditions.size()).isEqualTo(2); + assertThat(conditions).hasSize(2); } @Test diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/ParamsRequestConditionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/ParamsRequestConditionTests.java index 7cf95f593839..e366c8ce8121 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/ParamsRequestConditionTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/ParamsRequestConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -119,7 +119,7 @@ public void combine() { ParamsRequestCondition result = condition1.combine(condition2); Collection conditions = result.getContent(); - assertThat(conditions.size()).isEqualTo(2); + assertThat(conditions).hasSize(2); } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/PatternsRequestConditionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/PatternsRequestConditionTests.java index 05c7ca81907e..0b7c8bde64f3 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/PatternsRequestConditionTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/PatternsRequestConditionTests.java @@ -168,7 +168,7 @@ public void compareToConsistentWithEquals() { @Test public void equallyMatchingPatternsAreBothPresent() { PatternsRequestCondition c = createPatternsCondition("/a", "/b"); - assertThat(c.getPatterns().size()).isEqualTo(2); + assertThat(c.getPatterns()).hasSize(2); Iterator itr = c.getPatterns().iterator(); assertThat(itr.next().getPatternString()).isEqualTo("/a"); assertThat(itr.next().getPatternString()).isEqualTo("/b"); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java index 9e08ded149dd..b0fbbe4d5d8c 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java @@ -56,7 +56,7 @@ public void createEmpty() { PathPattern emptyPattern = (new PathPatternParser()).parse(""); assertThat(info.getPatternsCondition().getPatterns()).isEqualTo(Collections.singleton(emptyPattern)); - assertThat(info.getMethodsCondition().getMethods().size()).isEqualTo(0); + assertThat(info.getMethodsCondition().getMethods()).isEmpty(); assertThat(info.getConsumesCondition().isEmpty()).isTrue(); assertThat(info.getProducesCondition().isEmpty()).isTrue(); assertThat(info.getParamsCondition()).isNotNull(); @@ -93,7 +93,7 @@ public void throwWhenInvalidPattern() { public void prependPatternWithSlash() { RequestMappingInfo actual = paths("foo").build(); List patterns = new ArrayList<>(actual.getPatternsCondition().getPatterns()); - assertThat(patterns.size()).isEqualTo(1); + assertThat(patterns).hasSize(1); assertThat(patterns.get(0).getPatternString()).isEqualTo("/foo"); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMethodsRequestConditionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMethodsRequestConditionTests.java index b2392b1cd7d6..5d90c01c66ca 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMethodsRequestConditionTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMethodsRequestConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -119,7 +119,7 @@ public void combine() { RequestMethodsRequestCondition condition2 = new RequestMethodsRequestCondition(POST); RequestMethodsRequestCondition result = condition1.combine(condition2); - assertThat(result.getContent().size()).isEqualTo(2); + assertThat(result.getContent()).hasSize(2); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java index acf0cf9ce98d..f3d12370a03c 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -36,7 +37,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.http.observation.reactive.ServerRequestObservationContext; +import org.springframework.http.server.reactive.observation.ServerRequestObservationContext; import org.springframework.lang.Nullable; import org.springframework.stereotype.Controller; import org.springframework.util.ClassUtils; @@ -263,12 +264,11 @@ public void handleMatchBestMatchingPatternAttribute() { public void handleMatchBestMatchingPatternAttributeInObservationContext() { RequestMappingInfo key = paths("/{path1}/2", "/**").build(); ServerWebExchange exchange = MockServerWebExchange.from(get("/1/2")); - ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange); + ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); exchange.getAttributes().put(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext); this.handlerMapping.handleMatch(key, handlerMethod, exchange); - assertThat(observationContext.getPathPattern()).isNotNull(); - assertThat(observationContext.getPathPattern().toString()).isEqualTo("/{path1}/2"); + assertThat(observationContext.getPathPattern()).isEqualTo("/{path1}/2"); } @Test // gh-22543 @@ -307,7 +307,7 @@ public void handleMatchMatrixVariables() { // segment is a sequence of name-value pairs. assertThat(matrixVariables).isNotNull(); - assertThat(matrixVariables.size()).isEqualTo(1); + assertThat(matrixVariables).hasSize(1); assertThat(matrixVariables.getFirst("b")).isEqualTo("c"); assertThat(uriVariables.get("foo")).isEqualTo("a=42"); } @@ -346,6 +346,17 @@ public void handlePatchUnsupportedMediaType() { } + @Test // gh-29611 + public void handleNoMatchWithoutPartialMatches() throws Exception { + ServerWebExchange exchange = MockServerWebExchange.from(post("/non-existent")); + + HandlerMethod handlerMethod = this.handlerMapping.handleNoMatch(new HashSet<>(), exchange); + assertThat(handlerMethod).isNull(); + + handlerMethod = this.handlerMapping.handleNoMatch(null, exchange); + assertThat(handlerMethod).isNull(); + } + @SuppressWarnings("unchecked") private void assertError(Mono mono, final Class exceptionClass, final Consumer consumer) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java index c214b4bed171..ec34f70cebf2 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -66,33 +66,27 @@ protected RestTemplate getRestTemplate() { } - ResponseEntity performGet(String url, MediaType out, Class type) throws Exception { + ResponseEntity performGet(String url, MediaType out, Class type) { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(out)); return getRestTemplate().exchange(prepareGet(url, headers), type); } - ResponseEntity performGet(String url, HttpHeaders headers, Class type) throws Exception { + ResponseEntity performGet(String url, HttpHeaders headers, Class type) { return getRestTemplate().exchange(prepareGet(url, headers), type); } - ResponseEntity performGet(String url, MediaType out, ParameterizedTypeReference type) - throws Exception { - + ResponseEntity performGet(String url, MediaType out, ParameterizedTypeReference type) { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(out)); return this.restTemplate.exchange(prepareGet(url, headers), type); } - ResponseEntity performOptions(String url, HttpHeaders headers, Class type) - throws Exception { - + ResponseEntity performOptions(String url, HttpHeaders headers, Class type) { return getRestTemplate().exchange(prepareOptions(url, headers), type); } - ResponseEntity performPost(String url, MediaType in, Object body, MediaType out, Class type) - throws Exception { - + ResponseEntity performPost(String url, MediaType in, Object body, MediaType out, Class type) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(in); if (out != null) { @@ -101,15 +95,11 @@ ResponseEntity performPost(String url, MediaType in, Object body, MediaTy return getRestTemplate().exchange(preparePost(url, headers, body), type); } - ResponseEntity performPost(String url, HttpHeaders headers, Object body, - Class type) throws Exception { - + ResponseEntity performPost(String url, HttpHeaders headers, Object body, Class type) { return getRestTemplate().exchange(preparePost(url, headers, body), type); } - ResponseEntity performPost(String url, MediaType in, Object body, MediaType out, - ParameterizedTypeReference type) throws Exception { - + ResponseEntity performPost(String url, MediaType in, Object body, MediaType out, ParameterizedTypeReference type) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(in); if (out != null) { @@ -118,15 +108,15 @@ ResponseEntity performPost(String url, MediaType in, Object body, MediaTy return getRestTemplate().exchange(preparePost(url, headers, body), type); } - private RequestEntity prepareGet(String url, HttpHeaders headers) throws Exception { - URI uri = new URI("http://localhost:" + this.port + url); + private RequestEntity prepareGet(String url, HttpHeaders headers) { + URI uri = URI.create("http://localhost:" + this.port + url); RequestEntity.HeadersBuilder builder = RequestEntity.get(uri); addHeaders(builder, headers); return builder.build(); } - private RequestEntity prepareOptions(String url, HttpHeaders headers) throws Exception { - URI uri = new URI("http://localhost:" + this.port + url); + private RequestEntity prepareOptions(String url, HttpHeaders headers) { + URI uri = URI.create("http://localhost:" + this.port + url); RequestEntity.HeadersBuilder builder = RequestEntity.options(uri); addHeaders(builder, headers); return builder.build(); @@ -140,8 +130,8 @@ private void addHeaders(RequestEntity.HeadersBuilder builder, HttpHeaders hea } } - private RequestEntity preparePost(String url, HttpHeaders headers, Object body) throws Exception { - URI uri = new URI("http://localhost:" + this.port + url); + private RequestEntity preparePost(String url, HttpHeaders headers, Object body) { + URI uri = URI.create("http://localhost:" + this.port + url); RequestEntity.BodyBuilder builder = RequestEntity.post(uri); addHeaders(builder, headers); return builder.body(body); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerAdviceTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerAdviceTests.java index d506d2af4b69..1e3ec7f6a2b4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerAdviceTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerAdviceTests.java @@ -107,7 +107,7 @@ public void modelAttributeAdvice() throws Exception { Model model = handle(adapter, controller, "handle").getModel(); - assertThat(model.asMap().size()).isEqualTo(2); + assertThat(model.asMap()).hasSize(2); assertThat(model.asMap().get("attr1")).isEqualTo("lAttr1"); assertThat(model.asMap().get("attr2")).isEqualTo("gAttr2"); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerInputIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerInputIntegrationTests.java index 013218698c69..3b646ed4fbdd 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerInputIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerInputIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -45,10 +45,7 @@ class ControllerInputIntegrationTests extends AbstractRequestMappingIntegrationT @Override protected ApplicationContext initApplicationContext() { - AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext(); - wac.register(WebConfig.class, TestRestController.class); - wac.refresh(); - return wac; + return new AnnotationConfigApplicationContext(WebConfig.class, TestRestController.class); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContextTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContextTests.java index d695a3f750c6..5233a4a52ec6 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContextTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContextTests.java @@ -23,7 +23,7 @@ import org.junit.jupiter.api.Test; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.convert.ConversionService; import org.springframework.format.support.DefaultFormattingConversionService; @@ -131,7 +131,7 @@ private BindingContext createBindingContext(String methodName, Class... param SyncInvocableHandlerMethod handlerMethod = new SyncInvocableHandlerMethod(handler, method); handlerMethod.setArgumentResolvers(new ArrayList<>(this.argumentResolvers)); - handlerMethod.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer()); + handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer()); return new InitBinderBindingContext(this.bindingInitializer, Collections.singletonList(handlerMethod)); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelInitializerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelInitializerTests.java index ecae3618254a..9c1891c58071 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelInitializerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -112,7 +112,7 @@ public void modelAttributeMethods() { this.modelInitializer.initModel(handlerMethod, context, this.exchange).block(TIMEOUT); Map model = context.getModel().asMap(); - assertThat(model.size()).isEqualTo(5); + assertThat(model).hasSize(5); Object value = model.get("bean"); assertThat(((TestBean) value).getName()).isEqualTo("Bean"); @@ -141,10 +141,10 @@ public void saveModelAttributeToSession() { WebSession session = this.exchange.getSession().block(Duration.ZERO); assertThat(session).isNotNull(); - assertThat(session.getAttributes().size()).isEqualTo(0); + assertThat(session.getAttributes()).isEmpty(); context.saveModel(); - assertThat(session.getAttributes().size()).isEqualTo(1); + assertThat(session.getAttributes()).hasSize(1); assertThat(((TestBean) session.getRequiredAttribute("bean")).getName()).isEqualTo("Bean"); } @@ -164,7 +164,7 @@ public void retrieveModelAttributeFromSession() { this.modelInitializer.initModel(handlerMethod, context, this.exchange).block(TIMEOUT); context.saveModel(); - assertThat(session.getAttributes().size()).isEqualTo(1); + assertThat(session.getAttributes()).hasSize(1); assertThat(((TestBean) session.getRequiredAttribute("bean")).getName()).isEqualTo("Session Bean"); } @@ -198,7 +198,7 @@ public void clearModelAttributeFromSession() { context.getSessionStatus().setComplete(); context.saveModel(); - assertThat(session.getAttributes().size()).isEqualTo(0); + assertThat(session.getAttributes()).isEmpty(); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartWebClientIntegrationTests.java similarity index 96% rename from spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java rename to spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartWebClientIntegrationTests.java index 098a16ab7f74..f8f9328a7314 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartWebClientIntegrationTests.java @@ -65,7 +65,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeFalse; -class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTests { +class MultipartWebClientIntegrationTests extends AbstractHttpHandlerIntegrationTests { private WebClient webClient; @@ -270,7 +270,7 @@ void requestPart(@RequestPart FormFieldPart fieldPart, @PostMapping("/requestBodyMap") Mono requestBodyMap(@RequestBody Mono> partsMono) { - return partsMono.map(MultipartIntegrationTests::partMapDescription); + return partsMono.map(MultipartWebClientIntegrationTests::partMapDescription); } @PostMapping("/requestBodyFlux") @@ -345,16 +345,16 @@ private static String partMapDescription(MultiValueMap partsMap) { } private static Mono partFluxDescription(Flux partsFlux) { - return partsFlux.collectList().map(MultipartIntegrationTests::partListDescription); + return partsFlux.collectList().map(MultipartWebClientIntegrationTests::partListDescription); } private static String partListDescription(List parts) { - return parts.stream().map(MultipartIntegrationTests::partDescription) + return parts.stream().map(MultipartWebClientIntegrationTests::partDescription) .collect(Collectors.joining(",", "[", "]")); } private static String partDescription(Part part) { - return part instanceof FilePart ? part.name() + ":" + ((FilePart) part).filename() : part.name(); + return part instanceof FilePart filePart ? part.name() + ":" + filePart.filename() : part.name(); } static class FormBean { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyMethodArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyMethodArgumentResolverTests.java index 49f78fa70e5a..9b91d7835cef 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyMethodArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -41,12 +41,14 @@ import org.springframework.web.reactive.BindingContext; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; +import org.springframework.web.server.UnsupportedMediaTypeStatusException; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; import org.springframework.web.testfixture.method.ResolvableMethod; import org.springframework.web.testfixture.server.MockServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.springframework.web.testfixture.method.MvcAnnotationPredicates.requestBody; /** @@ -214,6 +216,17 @@ public void emptyBodyWithCompletableFuture() { }); } + @Test // gh-29565 + public void invalidContentType() { + MethodParameter parameter = this.testMethod.annot(requestBody()).arg(String.class); + + ServerWebExchange exchange = MockServerWebExchange.from( + MockServerHttpRequest.post("/path").header("Content-Type", "invalid").build()); + + assertThatThrownBy(() -> this.resolver.readBody(parameter, true, new BindingContext(), exchange)) + .isInstanceOf(UnsupportedMediaTypeStatusException.class); + } + @SuppressWarnings("unchecked") private T resolveValue(MethodParameter param, String body) { ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/path").body(body)); @@ -221,7 +234,8 @@ private T resolveValue(MethodParameter param, String body) { Object value = result.block(Duration.ofSeconds(5)); assertThat(value).isNotNull(); - assertThat(param.getParameterType().isAssignableFrom(value.getClass())).as("Unexpected return value type: " + value).isTrue(); + assertThat(param.getParameterType().isAssignableFrom(value.getClass())) + .as("Unexpected return value type: " + value).isTrue(); //no inspection unchecked return (T) value; @@ -234,7 +248,8 @@ private T resolveValueWithEmptyBody(MethodParameter param) { Object value = result.block(Duration.ofSeconds(5)); if (value != null) { - assertThat(param.getParameterType().isAssignableFrom(value.getClass())).as("Unexpected parameter type: " + value).isTrue(); + assertThat(param.getParameterType().isAssignableFrom(value.getClass())) + .as("Unexpected parameter type: " + value).isTrue(); } //no inspection unchecked diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java index a090f3c07f34..e7df37b4b916 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingExceptionHandlingIntegrationTests.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.Map; +import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -31,12 +32,15 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpServer; +import org.springframework.web.testfixture.http.server.reactive.bootstrap.ReactorHttpServer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -47,7 +51,7 @@ * @author Rossen Stoyanchev * @author Juergen Hoeller */ -class RequestMappingExceptionHandlingIntegrationTests extends AbstractRequestMappingIntegrationTests { +public class RequestMappingExceptionHandlingIntegrationTests extends AbstractRequestMappingIntegrationTests { @Override protected ApplicationContext initApplicationContext() { @@ -61,38 +65,38 @@ protected ApplicationContext initApplicationContext() { @ParameterizedHttpServerTest void thrownException(HttpServer httpServer) throws Exception { startServer(httpServer); - doTest("/thrown-exception", "Recovered from error: State"); } @ParameterizedHttpServerTest void thrownExceptionWithCause(HttpServer httpServer) throws Exception { startServer(httpServer); - doTest("/thrown-exception-with-cause", "Recovered from error: State"); } @ParameterizedHttpServerTest void thrownExceptionWithCauseToHandle(HttpServer httpServer) throws Exception { startServer(httpServer); - doTest("/thrown-exception-with-cause-to-handle", "Recovered from error: IO"); } @ParameterizedHttpServerTest void errorBeforeFirstItem(HttpServer httpServer) throws Exception { startServer(httpServer); - doTest("/mono-error", "Recovered from error: Argument"); } + private void doTest(String url, String expected) throws Exception { + assertThat(performGet(url, new HttpHeaders(), String.class).getBody()).isEqualTo(expected); + } + @ParameterizedHttpServerTest // SPR-16051 void exceptionAfterSeveralItems(HttpServer httpServer) throws Exception { startServer(httpServer); - assertThatExceptionOfType(Throwable.class).isThrownBy(() -> - performGet("/SPR-16051", new HttpHeaders(), String.class).getBody()) - .withMessageStartingWith("Error while extracting response"); + assertThatExceptionOfType(Throwable.class) + .isThrownBy(() -> performGet("/SPR-16051", new HttpHeaders(), String.class)) + .withMessageStartingWith("Error while extracting response"); } @ParameterizedHttpServerTest // SPR-16318 @@ -101,20 +105,50 @@ void exceptionFromMethodWithProducesCondition(HttpServer httpServer) throws Exce HttpHeaders headers = new HttpHeaders(); headers.add("Accept", "text/plain, application/problem+json"); - assertThatExceptionOfType(HttpStatusCodeException.class).isThrownBy(() -> - performGet("/SPR-16318", headers, String.class).getBody()) - .satisfies(ex -> { - assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); - assertThat(ex.getResponseHeaders().getContentType().toString()).isEqualTo("application/problem+json"); - assertThat(ex.getResponseBodyAsString()).isEqualTo("{\"reason\":\"error\"}"); - }); + assertThatExceptionOfType(HttpStatusCodeException.class) + .isThrownBy(() -> performGet("/SPR-16318", headers, String.class)) + .satisfies(ex -> { + assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + assertThat(ex.getResponseHeaders().getContentType().toString()).isEqualTo("application/problem+json"); + assertThat(ex.getResponseBodyAsString()).isEqualTo("{\"reason\":\"error\"}"); + }); } - private void doTest(String url, String expected) throws Exception { - assertThat(performGet(url, new HttpHeaders(), String.class).getBody()).isEqualTo(expected); + @Test + public void globalExceptionHandlerWithHandlerNotFound() throws Exception { + startServer(new ReactorHttpServer()); + + assertThatExceptionOfType(HttpStatusCodeException.class) + .isThrownBy(() -> performGet("/no-such-handler", new HttpHeaders(), String.class)) + .satisfies(ex -> { + assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(ex.getResponseBodyAsString()).isEqualTo("" + + "{\"type\":\"about:blank\"," + + "\"title\":\"Not Found\"," + + "\"status\":404," + + "\"instance\":\"/no-such-handler\"}"); + }); + } + + @Test + public void globalExceptionHandlerWithMissingRequestParameter() throws Exception { + startServer(new ReactorHttpServer()); + + assertThatExceptionOfType(HttpStatusCodeException.class) + .isThrownBy(() -> performGet("/missing-request-parameter", new HttpHeaders(), String.class)) + .satisfies(ex -> { + assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThat(ex.getResponseBodyAsString()).isEqualTo("{" + + "\"type\":\"about:blank\"," + + "\"title\":\"Bad Request\"," + + "\"status\":400," + + "\"detail\":\"Required query parameter 'q' is not present.\"," + + "\"instance\":\"/missing-request-parameter\"}"); + }); } + @Configuration @EnableWebFlux @ComponentScan(resourcePattern = "**/RequestMappingExceptionHandlingIntegrationTests$*.class") @@ -147,6 +181,11 @@ public Publisher handleWithError() { return Mono.error(new IllegalArgumentException("Argument")); } + @GetMapping(path = "/missing-request-parameter") + public String handleWithMissingParameter(@RequestParam String q) { + return "Success, q:" + q; + } + @GetMapping("/SPR-16051") public Flux errors() { return Flux.range(1, 10000) @@ -185,6 +224,11 @@ public ResponseEntity> handle(Spr16318Exception ex) { } + @ControllerAdvice + private static class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + } + + @SuppressWarnings("serial") private static class Spr16318Exception extends Exception { } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMappingTests.java index 293ce04780e4..386a206d9306 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMappingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -168,11 +168,11 @@ private RequestMappingInfo assertComposedAnnotationMapping(String methodName, St assertThat(info).isNotNull(); Set paths = info.getPatternsCondition().getPatterns(); - assertThat(paths.size()).isEqualTo(1); + assertThat(paths).hasSize(1); assertThat(paths.iterator().next().getPatternString()).isEqualTo(path); Set methods = info.getMethodsCondition().getMethods(); - assertThat(methods.size()).isEqualTo(1); + assertThat(methods).hasSize(1); assertThat(methods.iterator().next()).isEqualTo(requestMethod); return info; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java index 3816c50a639d..be11bdf2619a 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java index bd25e99a2813..2c5424c5e901 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java @@ -262,7 +262,7 @@ public void resource(HttpServer httpServer) throws Exception { assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.hasBody()).isTrue(); assertThat(response.getHeaders().getContentLength()).isEqualTo(951); - assertThat(response.getBody().length).isEqualTo(951); + assertThat(response.getBody()).hasSize(951); assertThat(response.getHeaders().getContentType()).isEqualTo(new MediaType("image", "png")); } @@ -358,7 +358,7 @@ public void personCreateWithPublisherJson(HttpServer httpServer) throws Exceptio asList(new Person("Robert"), new Person("Marie")), null, Void.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(2); + assertThat(getApplicationContext().getBean(PersonCreateController.class).persons).hasSize(2); } @ParameterizedHttpServerTest @@ -369,7 +369,7 @@ public void personCreateWithPublisherXml(HttpServer httpServer) throws Exception ResponseEntity response = performPost("/person-create/publisher", APPLICATION_XML, people, null, Void.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(2); + assertThat(getApplicationContext().getBean(PersonCreateController.class).persons).hasSize(2); } @ParameterizedHttpServerTest @@ -380,7 +380,7 @@ public void personCreateWithMono(HttpServer httpServer) throws Exception { "/person-create/mono", JSON, new Person("Robert"), null, Void.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(1); + assertThat(getApplicationContext().getBean(PersonCreateController.class).persons).hasSize(1); } @ParameterizedHttpServerTest @@ -391,7 +391,7 @@ public void personCreateWithSingle(HttpServer httpServer) throws Exception { "/person-create/single", JSON, new Person("Robert"), null, Void.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(1); + assertThat(getApplicationContext().getBean(PersonCreateController.class).persons).hasSize(1); } @ParameterizedHttpServerTest @@ -402,7 +402,7 @@ public void personCreateWithFluxJson(HttpServer httpServer) throws Exception { asList(new Person("Robert"), new Person("Marie")), null, Void.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(2); + assertThat(getApplicationContext().getBean(PersonCreateController.class).persons).hasSize(2); } @ParameterizedHttpServerTest @@ -413,7 +413,7 @@ public void personCreateWithFluxXml(HttpServer httpServer) throws Exception { ResponseEntity response = performPost("/person-create/flux", APPLICATION_XML, people, null, Void.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(2); + assertThat(getApplicationContext().getBean(PersonCreateController.class).persons).hasSize(2); } @ParameterizedHttpServerTest @@ -424,7 +424,7 @@ public void personCreateWithObservableJson(HttpServer httpServer) throws Excepti asList(new Person("Robert"), new Person("Marie")), null, Void.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(2); + assertThat(getApplicationContext().getBean(PersonCreateController.class).persons).hasSize(2); } @ParameterizedHttpServerTest @@ -435,7 +435,7 @@ public void personCreateWithObservableXml(HttpServer httpServer) throws Exceptio ResponseEntity response = performPost("/person-create/observable", APPLICATION_XML, people, null, Void.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(2); + assertThat(getApplicationContext().getBean(PersonCreateController.class).persons).hasSize(2); } @ParameterizedHttpServerTest @@ -446,7 +446,7 @@ public void personCreateWithFlowableJson(HttpServer httpServer) throws Exception asList(new Person("Robert"), new Person("Marie")), null, Void.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(2); + assertThat(getApplicationContext().getBean(PersonCreateController.class).persons).hasSize(2); } @ParameterizedHttpServerTest @@ -457,7 +457,7 @@ public void personCreateWithFlowableXml(HttpServer httpServer) throws Exception ResponseEntity response = performPost("/person-create/flowable", APPLICATION_XML, people, null, Void.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(getApplicationContext().getBean(PersonCreateController.class).persons.size()).isEqualTo(2); + assertThat(getApplicationContext().getBean(PersonCreateController.class).persons).hasSize(2); } @ParameterizedHttpServerTest // gh-23791 diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingViewResolutionIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingViewResolutionIntegrationTests.java index 3d0f350e408b..5fda49e6fc7f 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingViewResolutionIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingViewResolutionIntegrationTests.java @@ -53,10 +53,7 @@ class RequestMappingViewResolutionIntegrationTests extends AbstractRequestMappin @Override protected ApplicationContext initApplicationContext() { - AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext(); - wac.register(WebConfig.class); - wac.refresh(); - return wac; + return new AnnotationConfigApplicationContext(WebConfig.class); } @@ -72,7 +69,7 @@ void html(HttpServer httpServer) throws Exception { void etagCheckWithNotModifiedResponse(HttpServer httpServer) throws Exception { startServer(httpServer); - URI uri = new URI("http://localhost:" + this.port + "/html"); + URI uri = URI.create("http://localhost:" + this.port + "/html"); RequestEntity request = RequestEntity.get(uri).ifNoneMatch("\"deadb33f8badf00d\"").build(); ResponseEntity response = getRestTemplate().exchange(request, String.class); @@ -92,7 +89,7 @@ protected void prepareConnection(HttpURLConnection conn, String method) throws I } }; - URI uri = new URI("http://localhost:" + this.port + "/redirect"); + URI uri = URI.create("http://localhost:" + this.port + "/redirect"); RequestEntity request = RequestEntity.get(uri).accept(MediaType.ALL).build(); @SuppressWarnings("resource") ResponseEntity response = new RestTemplate(factory).exchange(request, Void.class); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java index 777bfd7f931f..ab10e4ce6a6e 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityExceptionHandlerTests.java @@ -172,7 +172,7 @@ void customExceptionToProblemDetailViaMessageSource() { .acceptLanguageAsLocales(locale).build()); ResponseEntity responseEntity = - this.exceptionHandler.handleException(new IllegalStateException(), exchange).block(); + this.exceptionHandler.handleException(new IllegalStateException("test"), exchange).block(); ProblemDetail body = (ProblemDetail) responseEntity.getBody(); assertThat(body.getDetail()).isEqualTo("Invalid state: A"); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java index 0eac1a523039..c2c7d919f604 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java @@ -20,7 +20,6 @@ import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; @@ -32,7 +31,6 @@ import com.fasterxml.jackson.databind.SerializationFeature; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Single; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -54,7 +52,6 @@ import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.util.ObjectUtils; import org.springframework.web.ErrorResponse; import org.springframework.web.ErrorResponseException; import org.springframework.web.reactive.HandlerResult; @@ -81,40 +78,28 @@ * * @author Rossen Stoyanchev */ -public class ResponseEntityResultHandlerTests { +class ResponseEntityResultHandlerTests { private static final String NEWLINE_SYSTEM_PROPERTY = System.lineSeparator(); + private final ResponseEntityResultHandler resultHandler = createHandler(); - private ResponseEntityResultHandler resultHandler; - - @BeforeEach - public void setup() throws Exception { - this.resultHandler = createHandler(); - } - - private ResponseEntityResultHandler createHandler(HttpMessageWriter... writers) { - List> writerList; - if (ObjectUtils.isEmpty(writers)) { - writerList = new ArrayList<>(); - writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); - writerList.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly())); - writerList.add(new ResourceHttpMessageWriter()); - writerList.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder())); - writerList.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder())); - writerList.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes())); - } - else { - writerList = Arrays.asList(writers); - } + private static ResponseEntityResultHandler createHandler() { + List> writerList = List.of( + new EncoderHttpMessageWriter<>(new ByteBufferEncoder()), + new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()), + new ResourceHttpMessageWriter(), + new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()), + new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()), + new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes())); RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build(); return new ResponseEntityResultHandler(writerList, resolver); } @Test - public void supports() throws Exception { + void supports() { Object value = null; MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); @@ -145,7 +130,7 @@ public void supports() throws Exception { } @Test - public void doesNotSupport() throws Exception { + void doesNotSupport() { Object value = null; MethodParameter returnType = on(TestController.class).resolveReturnType(String.class); @@ -160,12 +145,12 @@ public void doesNotSupport() throws Exception { } @Test - public void defaultOrder() throws Exception { + void defaultOrder() { assertThat(this.resultHandler.getOrder()).isEqualTo(0); } @Test - public void responseEntityStatusCode() throws Exception { + void responseEntityStatusCode() { ResponseEntity value = ResponseEntity.noContent().build(); MethodParameter returnType = on(TestController.class).resolveReturnType(entity(Void.class)); HandlerResult result = handlerResult(value, returnType); @@ -173,12 +158,12 @@ public void responseEntityStatusCode() throws Exception { this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); - assertThat(exchange.getResponse().getHeaders().size()).isEqualTo(0); + assertThat(exchange.getResponse().getHeaders()).isEmpty(); assertResponseBodyIsEmpty(exchange); } @Test - public void httpHeaders() throws Exception { + void httpHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setAllow(new LinkedHashSet<>(Arrays.asList(HttpMethod.GET, HttpMethod.POST, HttpMethod.OPTIONS))); MethodParameter returnType = on(TestController.class).resolveReturnType(entity(Void.class)); @@ -187,14 +172,14 @@ public void httpHeaders() throws Exception { this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(exchange.getResponse().getHeaders().size()).isEqualTo(1); + assertThat(exchange.getResponse().getHeaders()).hasSize(1); assertThat(exchange.getResponse().getHeaders().getFirst("Allow")).isEqualTo("GET,POST,OPTIONS"); assertResponseBodyIsEmpty(exchange); } @Test - public void responseEntityHeaders() throws Exception { - URI location = new URI("/path"); + void responseEntityHeaders() { + URI location = URI.create("/path"); ResponseEntity value = ResponseEntity.created(location).build(); MethodParameter returnType = on(TestController.class).resolveReturnType(entity(Void.class)); HandlerResult result = handlerResult(value, returnType); @@ -202,13 +187,13 @@ public void responseEntityHeaders() throws Exception { this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.CREATED); - assertThat(exchange.getResponse().getHeaders().size()).isEqualTo(1); + assertThat(exchange.getResponse().getHeaders()).hasSize(1); assertThat(exchange.getResponse().getHeaders().getLocation()).isEqualTo(location); assertResponseBodyIsEmpty(exchange); } @Test - public void handleResponseEntityWithNullBody() { + void handleResponseEntityWithNullBody() { Object returnValue = Mono.just(notFound().build()); MethodParameter type = on(TestController.class).resolveReturnType(Mono.class, entity(String.class)); HandlerResult result = handlerResult(returnValue, type); @@ -220,7 +205,7 @@ public void handleResponseEntityWithNullBody() { } @Test - public void handleReturnTypes() { + void handleReturnTypes() { Object returnValue = ResponseEntity.ok("abc"); MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); testHandle(returnValue, returnType); @@ -242,7 +227,7 @@ public void handleReturnTypes() { } @Test - public void handleErrorResponse() { + void handleErrorResponse() { ErrorResponseException ex = new ErrorResponseException(HttpStatus.BAD_REQUEST); ex.getHeaders().add("foo", "bar"); MethodParameter returnType = on(TestController.class).resolveReturnType(ErrorResponse.class); @@ -263,7 +248,7 @@ public void handleErrorResponse() { } @Test - public void handleProblemDetail() { + void handleProblemDetail() { ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST); MethodParameter returnType = on(TestController.class).resolveReturnType(ProblemDetail.class); HandlerResult result = handlerResult(problemDetail, returnType); @@ -272,7 +257,7 @@ public void handleProblemDetail() { this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - assertThat(exchange.getResponse().getHeaders().size()).isEqualTo(2); + assertThat(exchange.getResponse().getHeaders()).hasSize(2); assertThat(exchange.getResponse().getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON); assertResponseBody(exchange, "{\"type\":\"about:blank\"," + @@ -282,7 +267,7 @@ public void handleProblemDetail() { } @Test - public void handleReturnValueLastModified() throws Exception { + void handleReturnValueLastModified() { Instant currentTime = Instant.now().truncatedTo(ChronoUnit.SECONDS); Instant oneMinAgo = currentTime.minusSeconds(60); long timestamp = currentTime.toEpochMilli(); @@ -297,7 +282,7 @@ public void handleReturnValueLastModified() throws Exception { } @Test - public void handleReturnValueEtag() throws Exception { + void handleReturnValueEtag() { String etagValue = "\"deadb33f8badf00d\""; MockServerWebExchange exchange = MockServerWebExchange.from(get("/path").ifNoneMatch(etagValue)); @@ -310,7 +295,7 @@ public void handleReturnValueEtag() throws Exception { } @Test // SPR-14559 - public void handleReturnValueEtagInvalidIfNoneMatch() throws Exception { + void handleReturnValueEtagInvalidIfNoneMatch() { MockServerWebExchange exchange = MockServerWebExchange.from(get("/path").ifNoneMatch("unquoted")); ResponseEntity entity = ResponseEntity.ok().eTag("\"deadb33f8badf00d\"").body("body"); @@ -323,7 +308,7 @@ public void handleReturnValueEtagInvalidIfNoneMatch() throws Exception { } @Test - public void handleReturnValueETagAndLastModified() throws Exception { + void handleReturnValueETagAndLastModified() { String eTag = "\"deadb33f8badf00d\""; Instant currentTime = Instant.now().truncatedTo(ChronoUnit.SECONDS); @@ -343,7 +328,7 @@ public void handleReturnValueETagAndLastModified() throws Exception { } @Test - public void handleReturnValueChangedETagAndLastModified() throws Exception { + void handleReturnValueChangedETagAndLastModified() { String etag = "\"deadb33f8badf00d\""; String newEtag = "\"changed-etag-value\""; @@ -364,7 +349,7 @@ public void handleReturnValueChangedETagAndLastModified() throws Exception { } @Test // SPR-14877 - public void handleMonoWithWildcardBodyType() throws Exception { + void handleMonoWithWildcardBodyType() { MockServerWebExchange exchange = MockServerWebExchange.from(get("/path")); exchange.getAttributes().put(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(APPLICATION_JSON)); @@ -378,7 +363,7 @@ public void handleMonoWithWildcardBodyType() throws Exception { } @Test // SPR-14877 - public void handleMonoWithWildcardBodyTypeAndNullBody() throws Exception { + void handleMonoWithWildcardBodyTypeAndNullBody() { MockServerWebExchange exchange = MockServerWebExchange.from(get("/path")); exchange.getAttributes().put(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(APPLICATION_JSON)); @@ -392,7 +377,7 @@ public void handleMonoWithWildcardBodyTypeAndNullBody() throws Exception { } @Test // SPR-17082 - public void handleWithPresetContentType() { + void handleWithPresetContentType() { ResponseEntity value = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).build(); MethodParameter returnType = on(TestController.class).resolveReturnType(entity(Void.class)); HandlerResult result = handlerResult(value, returnType); @@ -401,13 +386,13 @@ public void handleWithPresetContentType() { this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(exchange.getResponse().getHeaders().size()).isEqualTo(1); + assertThat(exchange.getResponse().getHeaders()).hasSize(1); assertThat(exchange.getResponse().getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON); assertResponseBodyIsEmpty(exchange); } @Test // gh-23205 - public void handleWithPresetContentTypeShouldFailWithServerError() { + void handleWithPresetContentTypeShouldFailWithServerError() { ResponseEntity value = ResponseEntity.ok().contentType(MediaType.APPLICATION_XML).body(""); MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); HandlerResult result = handlerResult(value, returnType); @@ -426,7 +411,7 @@ public void handleWithPresetContentTypeShouldFailWithServerError() { } @Test // gh-23287 - public void handleWithProducibleContentTypeShouldFailWithServerError() { + void handleWithProducibleContentTypeShouldFailWithServerError() { ResponseEntity value = ResponseEntity.ok().body(""); MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); HandlerResult result = handlerResult(value, returnType); @@ -448,7 +433,7 @@ public void handleWithProducibleContentTypeShouldFailWithServerError() { } @Test // gh-26212 - public void handleWithObjectMapperByTypeRegistration() { + void handleWithObjectMapperByTypeRegistration() { MediaType halFormsMediaType = MediaType.parseMediaType("application/prs.hal-forms+json"); MediaType halMediaType = MediaType.parseMediaType("application/hal+json"); @@ -479,7 +464,7 @@ public void handleWithObjectMapperByTypeRegistration() { } @Test // gh-24539 - public void malformedAcceptHeader() { + void malformedAcceptHeader() { ResponseEntity value = ResponseEntity.badRequest().body("Foo"); MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); HandlerResult result = handlerResult(value, returnType); @@ -525,7 +510,7 @@ private void assertResponseBodyIsEmpty(MockServerWebExchange exchange) { } private void assertConditionalResponse(MockServerWebExchange exchange, HttpStatus status, - String body, String etag, Instant lastModified) throws Exception { + String body, String etag, Instant lastModified) { assertThat(exchange.getResponse().getStatusCode()).isEqualTo(status); if (body != null) { @@ -535,11 +520,11 @@ private void assertConditionalResponse(MockServerWebExchange exchange, HttpStatu assertResponseBodyIsEmpty(exchange); } if (etag != null) { - assertThat(exchange.getResponse().getHeaders().get(HttpHeaders.ETAG).size()).isEqualTo(1); + assertThat(exchange.getResponse().getHeaders().get(HttpHeaders.ETAG)).hasSize(1); assertThat(exchange.getResponse().getHeaders().getETag()).isEqualTo(etag); } if (lastModified.isAfter(Instant.EPOCH)) { - assertThat(exchange.getResponse().getHeaders().get(HttpHeaders.LAST_MODIFIED).size()).isEqualTo(1); + assertThat(exchange.getResponse().getHeaders().get(HttpHeaders.LAST_MODIFIED)).hasSize(1); assertThat(exchange.getResponse().getHeaders().getLastModified()).isEqualTo(lastModified.toEpochMilli()); } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/DefaultRenderingBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/DefaultRenderingBuilderTests.java index 62b195bef46c..3cf45ab109cb 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/DefaultRenderingBuilderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/DefaultRenderingBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -40,7 +40,7 @@ public void defaultValues() { assertThat(rendering.view()).isEqualTo("abc"); assertThat(rendering.modelAttributes()).isEqualTo(Collections.emptyMap()); assertThat(rendering.status()).isNull(); - assertThat(rendering.headers().size()).isEqualTo(0); + assertThat(rendering.headers()).isEmpty(); } @Test @@ -95,7 +95,7 @@ public void model() throws Exception { public void header() throws Exception { Rendering rendering = Rendering.view("foo").header("foo", "bar").build(); - assertThat(rendering.headers().size()).isEqualTo(1); + assertThat(rendering.headers()).hasSize(1); assertThat(rendering.headers().get("foo")).isEqualTo(Collections.singletonList("bar")); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/KotlinScriptTemplateTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/KotlinScriptTemplateTests.java index 560f11fabb77..5181b9944032 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/KotlinScriptTemplateTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/KotlinScriptTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractWebSocketIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractReactiveWebSocketIntegrationTests.java similarity index 94% rename from spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractWebSocketIntegrationTests.java rename to spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractReactiveWebSocketIntegrationTests.java index 71a081c0aeff..601642c3c5f4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractWebSocketIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractReactiveWebSocketIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -45,7 +45,6 @@ import org.springframework.http.server.reactive.HttpHandler; import org.springframework.web.filter.reactive.ServerWebExchangeContextFilter; import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.reactive.socket.client.JettyWebSocketClient; import org.springframework.web.reactive.socket.client.ReactorNetty2WebSocketClient; import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; import org.springframework.web.reactive.socket.client.TomcatWebSocketClient; @@ -70,7 +69,7 @@ import org.springframework.web.testfixture.http.server.reactive.bootstrap.UndertowHttpServer; /** - * Base class for WebSocket integration tests. Subclasses must implement + * Base class for reactive WebSocket integration tests. Subclasses must implement * {@link #getWebConfigClass()} to return Spring config class with (server-side) * handler mappings to {@code WebSocketHandler}'s. * @@ -78,7 +77,7 @@ * @author Sam Brannen */ @SuppressWarnings({"unused", "WeakerAccess"}) -abstract class AbstractWebSocketIntegrationTests { +abstract class AbstractReactiveWebSocketIntegrationTests { private static final File TMP_DIR = new File(System.getProperty("java.io.tmpdir")); @@ -91,9 +90,10 @@ abstract class AbstractWebSocketIntegrationTests { static Stream arguments() throws IOException { + @SuppressWarnings("removal") WebSocketClient[] clients = new WebSocketClient[] { new TomcatWebSocketClient(), - new JettyWebSocketClient(), + new org.springframework.web.reactive.socket.client.JettyWebSocketClient(), new ReactorNettyWebSocketClient(), new ReactorNetty2WebSocketClient(), new UndertowWebSocketClient(Xnio.getInstance().createWorker(OptionMap.EMPTY)) @@ -142,15 +142,15 @@ protected void startServer(WebSocketClient client, HttpServer server, Class s // Set dynamically chosen port this.port = this.server.getPort(); - if (this.client instanceof Lifecycle) { - ((Lifecycle) this.client).start(); + if (this.client instanceof Lifecycle lifecycle) { + lifecycle.start(); } } @AfterEach void stopServer() { - if (this.client instanceof Lifecycle) { - ((Lifecycle) this.client).stop(); + if (this.client instanceof Lifecycle lifecycle) { + lifecycle.stop(); } this.server.stop(); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/socket/WebSocketIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/socket/WebSocketIntegrationTests.java index ec81cab03eef..d739a3af1c70 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/socket/WebSocketIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/socket/WebSocketIntegrationTests.java @@ -50,7 +50,7 @@ * @author Sam Brannen * @author Brian Clozel */ -class WebSocketIntegrationTests extends AbstractWebSocketIntegrationTests { +class WebSocketIntegrationTests extends AbstractReactiveWebSocketIntegrationTests { private static final Log logger = LogFactory.getLog(WebSocketIntegrationTests.class); diff --git a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/KotlinInvocableHandlerMethodTests.kt b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/KotlinInvocableHandlerMethodTests.kt index 133046955275..7975a7dd853e 100644 --- a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/KotlinInvocableHandlerMethodTests.kt +++ b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/KotlinInvocableHandlerMethodTests.kt @@ -34,6 +34,7 @@ import org.springframework.web.reactive.result.method.annotation.ContinuationHan import reactor.core.publisher.Mono import reactor.test.StepVerifier import java.lang.reflect.Method +import java.time.Duration import kotlin.reflect.jvm.javaMethod class KotlinInvocableHandlerMethodTests { @@ -89,11 +90,9 @@ class KotlinInvocableHandlerMethodTests { val response = this.exchange.response this.resolvers.add(stubResolver(response)) val method = CoroutinesController::response.javaMethod!! - val result = invoke(CoroutinesController(), method) + val result = invokeForResult(CoroutinesController(), method, response) - StepVerifier.create(result) - .consumeNextWith { StepVerifier.create(it.returnValue as Mono<*>).verifyComplete() } - .verifyComplete() + assertThat(result).`as`("Expected no result (i.e. fully handled)").isNull() assertThat(this.exchange.response.headers.getFirst("foo")).isEqualTo("bar") } @@ -105,6 +104,10 @@ class KotlinInvocableHandlerMethodTests { assertHandlerResultValue(result, "success:foo") } + private fun invokeForResult(handler: Any, method: Method, vararg providedArgs: Any): HandlerResult? { + return invoke(handler, method, *providedArgs).block(Duration.ofSeconds(5)) + } + private fun invoke(handler: Any, method: Method, vararg providedArgs: Any?): Mono { val invocable = InvocableHandlerMethod(handler, method) invocable.setArgumentResolvers(this.resolvers) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java index af706c2e1243..25cf390494f2 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.concurrent.Callable; import java.util.stream.Collectors; @@ -167,6 +168,12 @@ public abstract class FrameworkServlet extends HttpServletBean implements Applic */ private static final String INIT_PARAM_DELIMITERS = ",; \t\n"; + /** + * HTTP methods supported by {@link jakarta.servlet.http.HttpServlet}. + */ + private static final Set HTTP_SERVLET_METHODS = Set.of("DELETE", "HEAD", "GET", "OPTIONS", "POST", "PUT", + "TRACE"); + /** ServletContext attribute to find the WebApplicationContext in. */ @Nullable @@ -866,18 +873,18 @@ public void destroy() { /** - * Override the parent class implementation in order to intercept PATCH requests. + * Override the parent class implementation in order to intercept requests + * using PATCH or non-standard HTTP methods (WebDAV). */ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod()); - if (HttpMethod.PATCH.equals(httpMethod)) { - processRequest(request, response); + if (HTTP_SERVLET_METHODS.contains(request.getMethod())) { + super.service(request, response); } else { - super.service(request, response); + processRequest(request, response); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java index e7d45c4e2d40..99362952fe4b 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java @@ -44,7 +44,7 @@ public class NoHandlerFoundException extends ServletException implements ErrorRe private final String requestURL; - private final HttpHeaders headers; + private final HttpHeaders requestHeaders; private final ProblemDetail body; @@ -59,7 +59,7 @@ public NoHandlerFoundException(String httpMethod, String requestURL, HttpHeaders super("No endpoint " + httpMethod + " " + requestURL + "."); this.httpMethod = httpMethod; this.requestURL = requestURL; - this.headers = headers; + this.requestHeaders = headers; this.body = ProblemDetail.forStatusAndDetail(getStatusCode(), getMessage()); } @@ -76,8 +76,23 @@ public String getRequestURL() { return this.requestURL; } + /** + * Return headers to use for the response. + *

    Note: As of 6.0 this method overlaps with + * {@link ErrorResponse#getHeaders()} and therefore no longer returns request + * headers. Use {@link #getRequestHeaders()} instead for request headers. + */ + @Override public HttpHeaders getHeaders() { - return this.headers; + return ErrorResponse.super.getHeaders(); + } + + /** + * Return the headers of the request. + * @since 6.0.3 + */ + public HttpHeaders getRequestHeaders() { + return this.requestHeaders; } @Override diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java index 8ee86bae790a..34aefb1d4d03 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java @@ -55,7 +55,6 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; -import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -569,7 +568,6 @@ private ManagedList getMessageConverters(Element element, @Nullable Object so messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source)); messageConverters.add(createConverterDefinition(ResourceRegionHttpMessageConverter.class, source)); - messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source)); messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source)); if (romePresent) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java index 70b15a2f7397..e6ac91ad6001 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -232,8 +232,8 @@ private static void registerLocaleResolver(ParserContext context, @Nullable Obje } /** - * Registers an {@link FixedThemeResolver} under a well-known name - * unless already registered. + * Registers an {@link org.springframework.web.servlet.theme.FixedThemeResolver} + * under a well-known name unless already registered. */ @SuppressWarnings("deprecation") private static void registerThemeResolver(ParserContext context, @Nullable Object source) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistry.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistry.java index 07e43dc244db..5e5d607007f1 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistry.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index 9a3f5da09dea..198f4492ed74 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -58,7 +58,6 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; -import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.lang.Nullable; import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; @@ -880,13 +879,6 @@ protected final void addDefaultHttpMessageConverters(List()); - } - catch (Throwable ex) { - // Ignore when no TransformerFactory implementation is available... - } - messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultRenderingResponseBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultRenderingResponseBuilder.java index 2f610350db8e..45f92fbc1a9e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultRenderingResponseBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultRenderingResponseBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -98,7 +98,7 @@ public RenderingResponse.Builder cookies(Consumer> @Override public RenderingResponse.Builder modelAttribute(Object attribute) { Assert.notNull(attribute, "Attribute must not be null"); - if (attribute instanceof Collection && ((Collection) attribute).isEmpty()) { + if (attribute instanceof Collection collection && collection.isEmpty()) { return this; } return modelAttribute(Conventions.getVariableName(attribute), attribute); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java index 96e0a94e716f..a2223c29ea56 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java @@ -95,7 +95,7 @@ class DefaultServerRequest implements ServerRequest { public DefaultServerRequest(HttpServletRequest servletRequest, List> messageConverters) { this.serverHttpRequest = new ServletServerHttpRequest(servletRequest); - this.messageConverters = Collections.unmodifiableList(new ArrayList<>(messageConverters)); + this.messageConverters = List.copyOf(messageConverters); this.headers = new DefaultRequestHeaders(this.serverHttpRequest.getHeaders()); this.params = CollectionUtils.toMultiValueMap(new ServletParametersMap(servletRequest)); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ServerResponse.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ServerResponse.java index 026e0814cf1b..0b6beb803a3d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ServerResponse.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ServerResponse.java @@ -47,6 +47,7 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; +import org.springframework.web.ErrorResponse; import org.springframework.web.servlet.ModelAndView; /** @@ -106,6 +107,18 @@ static BodyBuilder from(ServerResponse other) { return new DefaultServerResponseBuilder(other); } + /** + * Create a {@code ServerResponse} from the given {@link ErrorResponse}. + * @param response the {@link ErrorResponse} to initialize from + * @return the built response + * @since 6.0 + */ + static ServerResponse from(ErrorResponse response) { + return status(response.getStatusCode()) + .headers(headers -> headers.putAll(response.getHeaders())) + .body(response.getBody()); + } + /** * Create a builder with the given HTTP status. * @param status the response status diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java index 5f349f8b4a41..f67ea7e2e692 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java @@ -29,7 +29,6 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; -import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.web.filter.ServerHttpObservationFilter; @@ -189,12 +188,6 @@ private void initMessageConverters() { List> messageConverters = new ArrayList<>(4); messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(new StringHttpMessageConverter()); - try { - messageConverters.add(new SourceHttpMessageConverter<>()); - } - catch (Error err) { - // Ignore when no TransformerFactory implementation is available - } messageConverters.add(new AllEncompassingFormHttpMessageConverter()); this.messageConverters = messageConverters; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index 0dc6875f018b..85d61f2ce6aa 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -147,9 +147,8 @@ public HandlerMethodMappingNamingStrategy getNamingStrategy() { public Map getHandlerMethods() { this.mappingRegistry.acquireReadLock(); try { - return Collections.unmodifiableMap( - this.mappingRegistry.getRegistrations().entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().handlerMethod))); + return this.mappingRegistry.getRegistrations().entrySet().stream() + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, entry -> entry.getValue().handlerMethod)); } finally { this.mappingRegistry.releaseReadLock(); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java index 6c9dce05553b..75bb7a2c3a6f 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java @@ -374,7 +374,7 @@ protected void exposeUriTemplateVariables(Map uriTemplateVariabl @Override @Nullable public RequestMatchResult match(HttpServletRequest request, String pattern) { - Assert.isNull(getPatternParser(), "This HandlerMapping uses PathPatterns."); + Assert.state(getPatternParser() == null, "This HandlerMapping uses PathPatterns."); String lookupPath = UrlPathHelper.getResolvedLookupPath(request); if (getPathMatcher().match(pattern, lookupPath)) { return new RequestMatchResult(pattern, lookupPath, getPathMatcher()); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java index a10055bfa84f..9df482be905e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -158,7 +158,7 @@ private T doWithMatchingMapping( HttpServletRequest request, boolean ignoreException, BiFunction matchHandler) throws Exception { - Assert.notNull(this.handlerMappings, "Handler mappings not initialized"); + Assert.state(this.handlerMappings != null, "Handler mappings not initialized"); boolean parseRequestPath = !this.pathPatternHandlerMappings.isEmpty(); RequestPath previousPath = null; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/PathPatternMatchableHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/PathPatternMatchableHandlerMapping.java index 89ca01893f69..3057331dfb2c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/PathPatternMatchableHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/PathPatternMatchableHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -59,7 +59,7 @@ public PathPatternMatchableHandlerMapping(MatchableHandlerMapping delegate) { @Override public RequestMatchResult match(HttpServletRequest request, String pattern) { PathPattern pathPattern = this.pathPatternCache.computeIfAbsent(pattern, value -> { - Assert.isTrue(this.pathPatternCache.size() < MAX_PATTERNS, "Max size for pattern cache exceeded."); + Assert.state(this.pathPatternCache.size() < MAX_PATTERNS, "Max size for pattern cache exceeded."); return this.parser.parse(pattern); }); PathContainer path = ServletRequestPathUtils.getParsedRequestPath(request).pathWithinApplication(); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java index d5f4b09e8dff..da02aa2b9dfe 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -59,7 +59,7 @@ public class RequestMatchResult { */ public RequestMatchResult(PathPattern pathPattern, PathContainer lookupPath) { Assert.notNull(pathPattern, "PathPattern is required"); - Assert.notNull(pathPattern, "PathContainer is required"); + Assert.notNull(lookupPath, "PathContainer is required"); this.pattern = null; this.lookupPath = null; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java index 362f98314855..ec92c8abd26d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java @@ -246,6 +246,10 @@ private Map> extractMatrixVariables( protected HandlerMethod handleNoMatch( Set infos, String lookupPath, HttpServletRequest request) throws ServletException { + if (CollectionUtils.isEmpty(infos)) { + return null; + } + PartialMatchHelper helper = new PartialMatchHelper(infos, request); if (helper.isEmpty()) { return null; @@ -293,11 +297,11 @@ protected HandlerMethod handleNoMatch( /** * Aggregate all partial matches and expose methods checking across them. */ - private static class PartialMatchHelper { + private static final class PartialMatchHelper { private final List partialMatches = new ArrayList<>(); - public PartialMatchHelper(Set infos, HttpServletRequest request) { + PartialMatchHelper(Set infos, HttpServletRequest request) { for (RequestMappingInfo info : infos) { if (info.getActivePatternsCondition().getMatchingCondition(request) != null) { this.partialMatches.add(new PartialMatch(info, request)); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java index d7c2767fc255..0588f57fe5a8 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java @@ -37,7 +37,6 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; -import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.lang.Nullable; import org.springframework.ui.ModelMap; import org.springframework.web.accept.ContentNegotiationManager; @@ -260,12 +259,6 @@ private void initMessageConverters() { } this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(new StringHttpMessageConverter()); - try { - this.messageConverters.add(new SourceHttpMessageConverter<>()); - } - catch (Error err) { - // Ignore when no TransformerFactory implementation is available - } this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java index e0f4fb5a8c5f..df38a974bab4 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java @@ -448,7 +448,7 @@ public static MethodArgumentBuilder fromMappingName(String mappingName) { */ public static MethodArgumentBuilder fromMappingName(@Nullable UriComponentsBuilder builder, String name) { WebApplicationContext wac = getWebApplicationContext(); - Assert.notNull(wac, "No WebApplicationContext"); + Assert.state(wac != null, "No WebApplicationContext"); Map map = wac.getBeansOfType(RequestMappingInfoHandlerMapping.class); List handlerMethods = null; for (RequestMappingInfoHandlerMapping mapping : map.values()) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index 74f02955c8ee..b9429cc841db 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -46,7 +46,6 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; -import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.lang.Nullable; import org.springframework.ui.ModelMap; import org.springframework.util.CollectionUtils; @@ -568,12 +567,6 @@ private void initMessageConverters() { } this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(new StringHttpMessageConverter()); - try { - this.messageConverters.add(new SourceHttpMessageConverter<>()); - } - catch (Error err) { - // Ignore when no TransformerFactory implementation is available - } this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index 338ec94fe1d3..411f96f8aa9e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -337,8 +337,8 @@ String getPathPrefix(Class handlerType) { @Nullable private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); - RequestCondition condition = (element instanceof Class ? - getCustomTypeCondition((Class) element) : getCustomMethodCondition((Method) element)); + RequestCondition condition = (element instanceof Class clazz ? + getCustomTypeCondition(clazz) : getCustomMethodCondition((Method) element)); return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); } @@ -454,7 +454,7 @@ private void updateConsumesCondition(RequestMappingInfo info, Method method) { @Override public RequestMatchResult match(HttpServletRequest request, String pattern) { - Assert.isNull(getPatternParser(), "This HandlerMapping requires a PathPattern"); + Assert.state(getPatternParser() == null, "This HandlerMapping uses PathPatterns."); RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build(); RequestMappingInfo match = info.getMatchingCondition(request); return (match != null && match.getPatternsCondition() != null ? diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java index a30433a34941..eb47bb6e0481 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java @@ -61,8 +61,8 @@ * for global exception handling in an application. Subclasses can override * individual methods that handle a specific exception, override * {@link #handleExceptionInternal} to override common handling of all exceptions, - * or {@link #createResponseEntity} to intercept the final step of creating the - * {@link ResponseEntity} from the selected HTTP status code, headers, and body. + * or override {@link #createResponseEntity} to intercept the final step of creating + * the {@link ResponseEntity} from the selected HTTP status code, headers, and body. * * @author Rossen Stoyanchev * @since 3.2 @@ -96,9 +96,18 @@ public void setMessageSource(MessageSource messageSource) { this.messageSource = messageSource; } + /** + * Get the {@link MessageSource} that this exception handler uses. + * @since 6.0.3 + */ + @Nullable + protected MessageSource getMessageSource() { + return this.messageSource; + } + /** - * Handle all exceptions raised within Spring MVC handling of the request . + * Handle all exceptions raised within Spring MVC handling of the request. * @param ex the exception to handle * @param request the current request */ @@ -508,10 +517,14 @@ protected ProblemDetail createProblemDetail( Exception ex, HttpStatusCode status, String defaultDetail, @Nullable String detailMessageCode, @Nullable Object[] detailMessageArguments, WebRequest request) { - ErrorResponse errorResponse = ErrorResponse.createFor( - ex, status, null, defaultDetail, detailMessageCode, detailMessageArguments); - - return errorResponse.updateAndGetBody(this.messageSource, LocaleContextHolder.getLocale()); + ErrorResponse.Builder builder = ErrorResponse.builder(ex, status, defaultDetail); + if (detailMessageCode != null) { + builder.detailMessageCode(detailMessageCode); + } + if (detailMessageArguments != null) { + builder.detailMessageArguments(detailMessageArguments); + } + return builder.build().updateAndGetBody(this.messageSource, LocaleContextHolder.getLocale()); } /** diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBody.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBody.java index 3c6fe1575ce8..34e78e1b2aee 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBody.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBody.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 8d94d796f59f..ab632ddfb6c5 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -625,8 +625,8 @@ protected Resource getResource(HttpServletRequest request) throws IOException { return null; } - Assert.notNull(this.resolverChain, "ResourceResolverChain not initialized."); - Assert.notNull(this.transformerChain, "ResourceTransformerChain not initialized."); + Assert.state(this.resolverChain != null, "ResourceResolverChain not initialized."); + Assert.state(this.transformerChain != null, "ResourceTransformerChain not initialized."); Resource resource = this.resolverChain.resolveResource(request, path, getLocations()); if (resource != null) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java index 1b55b5bb0b75..e42d9d708532 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java index 353c9d4a5805..44559ce60bc6 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -31,6 +31,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; import org.springframework.web.context.AbstractContextLoaderInitializer; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; @@ -77,13 +78,13 @@ public void onStartup(ServletContext servletContext) throws ServletException { */ protected void registerDispatcherServlet(ServletContext servletContext) { String servletName = getServletName(); - Assert.hasLength(servletName, "getServletName() must not return null or empty"); + Assert.state(StringUtils.hasLength(servletName), "getServletName() must not return null or empty"); WebApplicationContext servletAppContext = createServletApplicationContext(); - Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null"); + Assert.state(servletAppContext != null, "createServletApplicationContext() must not return null"); FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); - Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null"); + Assert.state(dispatcherServlet != null, "createDispatcherServlet(WebApplicationContext) must not return null"); dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/BindStatus.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/BindStatus.java index f8debe853cdf..f84b96c21c5d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/BindStatus.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/BindStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -128,8 +128,8 @@ else if (this.expression.endsWith("*")) { this.objectErrors = this.errors.getFieldErrors(this.expression); this.value = this.errors.getFieldValue(this.expression); this.valueType = this.errors.getFieldType(this.expression); - if (this.errors instanceof BindingResult) { - this.bindingResult = (BindingResult) this.errors; + if (this.errors instanceof BindingResult br) { + this.bindingResult = br; this.actualValue = this.bindingResult.getRawFieldValue(this.expression); this.editor = this.bindingResult.findEditor(this.expression, null); } @@ -163,8 +163,8 @@ else if (this.expression.endsWith("*")) { this.errorMessages = new String[0]; } - if (htmlEscape && this.value instanceof String) { - this.value = HtmlUtils.htmlEscape((String) this.value); + if (htmlEscape && this.value instanceof String text) { + this.value = HtmlUtils.htmlEscape(text); } } @@ -239,8 +239,8 @@ public Object getActualValue() { * will get HTML-escaped. */ public String getDisplayValue() { - if (this.value instanceof String) { - return (String) this.value; + if (this.value instanceof String displayValue) { + return displayValue; } if (this.value != null) { return (this.htmlEscape ? HtmlUtils.htmlEscape(this.value.toString()) : this.value.toString()); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/ParamAware.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/ParamAware.java index 82bad9f662f1..0dbd4a00f934 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/ParamAware.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/ParamAware.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementBodyTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementBodyTag.java index ed521e83971b..a35a6286d188 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementBodyTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementBodyTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ViewResolverComposite.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ViewResolverComposite.java index 5421c4b90022..95f66107cce9 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ViewResolverComposite.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ViewResolverComposite.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. @@ -78,8 +78,8 @@ public int getOrder() { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { for (ViewResolver viewResolver : this.viewResolvers) { - if (viewResolver instanceof ApplicationContextAware) { - ((ApplicationContextAware)viewResolver).setApplicationContext(applicationContext); + if (viewResolver instanceof ApplicationContextAware applicationContextAware) { + applicationContextAware.setApplicationContext(applicationContext); } } } @@ -87,8 +87,8 @@ public void setApplicationContext(ApplicationContext applicationContext) throws @Override public void setServletContext(ServletContext servletContext) { for (ViewResolver viewResolver : this.viewResolvers) { - if (viewResolver instanceof ServletContextAware) { - ((ServletContextAware)viewResolver).setServletContext(servletContext); + if (viewResolver instanceof ServletContextAware servletContextAware) { + servletContextAware.setServletContext(servletContext); } } } @@ -96,8 +96,8 @@ public void setServletContext(ServletContext servletContext) { @Override public void afterPropertiesSet() throws Exception { for (ViewResolver viewResolver : this.viewResolvers) { - if (viewResolver instanceof InitializingBean) { - ((InitializingBean) viewResolver).afterPropertiesSet(); + if (viewResolver instanceof InitializingBean initializingBean) { + initializingBean.afterPropertiesSet(); } } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurer.java index a00e2510bcf0..ecf91a940c66 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurer.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java index 176cf840a526..8d7be38b4941 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -40,8 +40,6 @@ * Abstract base class for Jackson based and content type independent * {@link AbstractView} implementations. * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Jeremy Grelle * @author Arjen Poutsma * @author Rossen Stoyanchev diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java index b9f239d9b8e3..ea7af86ad2e4 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java @@ -42,8 +42,6 @@ * *

    The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}. * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Jeremy Grelle * @author Arjen Poutsma * @author Rossen Stoyanchev diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java index 05b564078592..3c4554cb5b65 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -38,8 +38,6 @@ * *

    The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}. * - *

    Compatible with Jackson 2.9 to 2.12, as of Spring 5.3. - * * @author Sebastien Deleuze * @since 4.1 * @see org.springframework.web.servlet.view.json.MappingJackson2JsonView diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MarshallingView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MarshallingView.java index 36b7f04cbdf2..65c6820265d2 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MarshallingView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MarshallingView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -97,7 +97,7 @@ public void setModelKey(String modelKey) { @Override protected void initApplicationContext() { - Assert.notNull(this.marshaller, "Property 'marshaller' is required"); + Assert.state(this.marshaller != null, "Property 'marshaller' is required"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/context/support/ServletContextSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/context/support/ServletContextSupportTests.java index f669c8e72209..000828349f55 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/context/support/ServletContextSupportTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/context/support/ServletContextSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -159,7 +159,7 @@ public Set getResourcePaths(String path) { for (Resource resource : found) { foundPaths.add(((ServletContextResource) resource).getPath()); } - assertThat(foundPaths.size()).isEqualTo(2); + assertThat(foundPaths).hasSize(2); assertThat(foundPaths.contains("/WEB-INF/context1.xml")).isTrue(); assertThat(foundPaths.contains("/WEB-INF/context2.xml")).isTrue(); } @@ -192,7 +192,7 @@ public Set getResourcePaths(String path) { for (Resource resource : found) { foundPaths.add(((ServletContextResource) resource).getPath()); } - assertThat(foundPaths.size()).isEqualTo(2); + assertThat(foundPaths).hasSize(2); assertThat(foundPaths.contains("/WEB-INF/mydir1/context1.xml")).isTrue(); assertThat(foundPaths.contains("/WEB-INF/mydir2/context2.xml")).isTrue(); } @@ -232,7 +232,7 @@ public Set getResourcePaths(String path) { for (Resource resource : found) { foundPaths.add(((ServletContextResource) resource).getPath()); } - assertThat(foundPaths.size()).isEqualTo(3); + assertThat(foundPaths).hasSize(3); assertThat(foundPaths.contains("/WEB-INF/mydir1/context1.xml")).isTrue(); assertThat(foundPaths.contains("/WEB-INF/mydir2/context2.xml")).isTrue(); assertThat(foundPaths.contains("/WEB-INF/mydir2/mydir3/context3.xml")).isTrue(); @@ -261,7 +261,7 @@ public Set getResourcePaths(String path) { for (Resource resource : found) { foundPaths.add(((ServletContextResource) resource).getPath()); } - assertThat(foundPaths.size()).isEqualTo(2); + assertThat(foundPaths).hasSize(2); assertThat(foundPaths.contains("/WEB-INF/context1.xml")).isTrue(); assertThat(foundPaths.contains("/WEB-INF/context2.xml")).isTrue(); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java index 595046a04746..8937ea0e1e3e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java @@ -875,6 +875,14 @@ public void mixedInitializerClasses() throws Exception { assertThat(getServletContext().getAttribute("otherInitialized")).isEqualTo("true"); } + @Test + public void webDavMethod() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "PROPFIND", "/body.do"); + MockHttpServletResponse response = new MockHttpServletResponse(); + complexDispatcherServlet.service(request, response); + assertThat(response.getContentAsString()).isEqualTo("body"); + } + public static class ControllerFromParent implements Controller { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/FlashMapTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/FlashMapTests.java index 12d2ffde19b5..830f9f132afb 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/FlashMapTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/FlashMapTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -78,7 +78,7 @@ public void addTargetRequestParamNullValue() { flashMap.addTargetRequestParam("empty", " "); flashMap.addTargetRequestParam("null", null); - assertThat(flashMap.getTargetRequestParams().size()).isEqualTo(1); + assertThat(flashMap.getTargetRequestParams()).hasSize(1); assertThat(flashMap.getTargetRequestParams().getFirst("text")).isEqualTo("abc"); } @@ -92,8 +92,8 @@ public void addTargetRequestParamsNullValue() { FlashMap flashMap = new FlashMap(); flashMap.addTargetRequestParams(params); - assertThat(flashMap.getTargetRequestParams().size()).isEqualTo(1); - assertThat(flashMap.getTargetRequestParams().get("key").size()).isEqualTo(1); + assertThat(flashMap.getTargetRequestParams()).hasSize(1); + assertThat(flashMap.getTargetRequestParams().get("key")).hasSize(1); assertThat(flashMap.getTargetRequestParams().getFirst("key")).isEqualTo("abc"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/SimpleWebApplicationContext.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/SimpleWebApplicationContext.java index 6531b7736e5b..0de974e4a2b8 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/SimpleWebApplicationContext.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/SimpleWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java index 6c79844d34d5..d46a84fa6ac9 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java @@ -123,7 +123,7 @@ private void testArgumentResolvers(Object bean) { assertThat(value instanceof List).isTrue(); @SuppressWarnings("unchecked") List resolvers = (List) value; - assertThat(resolvers.size()).isEqualTo(3); + assertThat(resolvers).hasSize(3); assertThat(resolvers.get(0) instanceof ServletWebArgumentResolverAdapter).isTrue(); assertThat(resolvers.get(1) instanceof TestHandlerMethodArgumentResolver).isTrue(); assertThat(resolvers.get(2) instanceof TestHandlerMethodArgumentResolver).isTrue(); @@ -144,7 +144,7 @@ private void testReturnValueHandlers(Object bean) { assertThat(value instanceof List).isTrue(); @SuppressWarnings("unchecked") List handlers = (List) value; - assertThat(handlers.size()).isEqualTo(2); + assertThat(handlers).hasSize(2); assertThat(handlers.get(0).getClass()).isEqualTo(TestHandlerMethodReturnValueHandler.class); assertThat(handlers.get(1).getClass()).isEqualTo(TestHandlerMethodReturnValueHandler.class); assertThat(handlers.get(1)).isNotSameAs(handlers.get(0)); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index c52eea704079..753d50a609b4 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -244,7 +244,7 @@ public void testDefaultConfig() throws Exception { MockHttpServletResponse response = new MockHttpServletResponse(); HandlerExecutionChain chain = mapping.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(1); + assertThat(chain.getInterceptorList()).hasSize(1); assertThat(chain.getInterceptorList().get(0) instanceof ConversionServiceExposingInterceptor).isTrue(); ConversionServiceExposingInterceptor interceptor = (ConversionServiceExposingInterceptor) chain.getInterceptorList().get(0); interceptor.preHandle(request, response, handlerMethod); @@ -264,7 +264,7 @@ public void testDefaultConfig() throws Exception { String name = "mvcHandlerMappingIntrospector"; HandlerMappingIntrospector introspector = this.appContext.getBean(name, HandlerMappingIntrospector.class); assertThat(introspector).isNotNull(); - assertThat(introspector.getHandlerMappings().size()).isEqualTo(2); + assertThat(introspector.getHandlerMappings()).hasSize(2); assertThat(introspector.getHandlerMappings().get(0)).isSameAs(mapping); assertThat(introspector.getHandlerMappings().get(1).getClass()).isEqualTo(BeanNameUrlHandlerMapping.class); } @@ -301,7 +301,7 @@ public void testCustomConversionService() throws Exception { MockHttpServletResponse response = new MockHttpServletResponse(); HandlerExecutionChain chain = mapping.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(1); + assertThat(chain.getInterceptorList()).hasSize(1); assertThat(chain.getInterceptorList().get(0) instanceof ConversionServiceExposingInterceptor).isTrue(); ConversionServiceExposingInterceptor interceptor = (ConversionServiceExposingInterceptor) chain.getInterceptorList().get(0); interceptor.preHandle(request, response, handler); @@ -353,7 +353,7 @@ public void testInterceptors() throws Exception { request.addParameter("theme", "green"); HandlerExecutionChain chain = mapping.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(4); + assertThat(chain.getInterceptorList()).hasSize(4); assertThat(chain.getInterceptorList().get(0) instanceof ConversionServiceExposingInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(1) instanceof LocaleChangeInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(2) instanceof ThemeChangeInterceptor).isTrue(); @@ -361,15 +361,15 @@ public void testInterceptors() throws Exception { request.setRequestURI("/admin/users"); chain = mapping.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(2); + assertThat(chain.getInterceptorList()).hasSize(2); request.setRequestURI("/logged/accounts/12345"); chain = mapping.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(3); + assertThat(chain.getInterceptorList()).hasSize(3); request.setRequestURI("/foo/logged"); chain = mapping.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(3); + assertThat(chain.getInterceptorList()).hasSize(3); } @Test @@ -469,7 +469,7 @@ public void testResourcesWithResolversTransformers() throws Exception { PathResourceResolver pathResolver = (PathResourceResolver) resolvers.get(3); Map locationCharsets = pathResolver.getLocationCharsets(); - assertThat(locationCharsets.size()).isEqualTo(1); + assertThat(locationCharsets).hasSize(1); assertThat(locationCharsets.values().iterator().next()).isEqualTo(StandardCharsets.ISO_8859_1); List transformers = handler.getResourceTransformers(); @@ -578,7 +578,7 @@ public void testBeanDecoration() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); HandlerExecutionChain chain = mapping.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(3); + assertThat(chain.getInterceptorList()).hasSize(3); assertThat(chain.getInterceptorList().get(0) instanceof ConversionServiceExposingInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(1) instanceof LocaleChangeInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(2) instanceof ThemeChangeInterceptor).isTrue(); @@ -604,7 +604,7 @@ public void testViewControllers() throws Exception { request.setMethod("GET"); HandlerExecutionChain chain = mapping.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(3); + assertThat(chain.getInterceptorList()).hasSize(3); assertThat(chain.getInterceptorList().get(0) instanceof ConversionServiceExposingInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(1) instanceof LocaleChangeInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(2) instanceof ThemeChangeInterceptor).isTrue(); @@ -617,7 +617,7 @@ public void testViewControllers() throws Exception { request = new MockHttpServletRequest("GET", "/foo"); chain = mapping2.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(4); + assertThat(chain.getInterceptorList()).hasSize(4); assertThat(chain.getInterceptorList().get(1) instanceof ConversionServiceExposingInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(2) instanceof LocaleChangeInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(3) instanceof ThemeChangeInterceptor).isTrue(); @@ -628,7 +628,7 @@ public void testViewControllers() throws Exception { request.setContextPath("/myapp"); request.setServletPath("/app"); chain = mapping2.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(4); + assertThat(chain.getInterceptorList()).hasSize(4); assertThat(chain.getInterceptorList().get(1) instanceof ConversionServiceExposingInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(2) instanceof LocaleChangeInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(3) instanceof ThemeChangeInterceptor).isTrue(); @@ -639,7 +639,7 @@ public void testViewControllers() throws Exception { request.setContextPath("/myapp"); request.setServletPath("/app"); chain = mapping2.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(4); + assertThat(chain.getInterceptorList()).hasSize(4); assertThat(chain.getInterceptorList().get(1) instanceof ConversionServiceExposingInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(2) instanceof LocaleChangeInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(3) instanceof ThemeChangeInterceptor).isTrue(); @@ -683,7 +683,7 @@ public void testViewControllersOnWebSphere() throws Exception { request.setServletPath("/app/"); request.setAttribute("com.ibm.websphere.servlet.uri_non_decoded", "/myapp/app/bar"); HandlerExecutionChain chain = mapping2.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(4); + assertThat(chain.getInterceptorList()).hasSize(4); assertThat(chain.getInterceptorList().get(1) instanceof ConversionServiceExposingInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(2) instanceof LocaleChangeInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(3) instanceof ThemeChangeInterceptor).isTrue(); @@ -695,7 +695,7 @@ public void testViewControllersOnWebSphere() throws Exception { request.setServletPath("/app/"); request.setHttpServletMapping(new MockHttpServletMapping("", "", "", MappingMatch.PATH)); chain = mapping2.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(4); + assertThat(chain.getInterceptorList()).hasSize(4); assertThat(chain.getInterceptorList().get(1) instanceof ConversionServiceExposingInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(2) instanceof LocaleChangeInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(3) instanceof ThemeChangeInterceptor).isTrue(); @@ -706,7 +706,7 @@ public void testViewControllersOnWebSphere() throws Exception { request.setContextPath("/myapp"); request.setServletPath("/"); chain = mapping2.getHandler(request); - assertThat(chain.getInterceptorList().size()).isEqualTo(4); + assertThat(chain.getInterceptorList()).hasSize(4); assertThat(chain.getInterceptorList().get(1) instanceof ConversionServiceExposingInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(2) instanceof LocaleChangeInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(3) instanceof ThemeChangeInterceptor).isTrue(); @@ -775,11 +775,11 @@ public void testAsyncSupportOptions() throws Exception { CallableProcessingInterceptor[] callableInterceptors = (CallableProcessingInterceptor[]) fieldAccessor.getPropertyValue("callableInterceptors"); - assertThat(callableInterceptors.length).isEqualTo(1); + assertThat(callableInterceptors).hasSize(1); DeferredResultProcessingInterceptor[] deferredResultInterceptors = (DeferredResultProcessingInterceptor[]) fieldAccessor.getPropertyValue("deferredResultInterceptors"); - assertThat(deferredResultInterceptors.length).isEqualTo(1); + assertThat(deferredResultInterceptors).hasSize(1); } @Test @@ -853,14 +853,14 @@ public void testViewResolutionWithContentNegotiation() throws Exception { ViewResolverComposite compositeResolver = this.appContext.getBean(ViewResolverComposite.class); assertThat(compositeResolver).isNotNull(); - assertThat(compositeResolver.getViewResolvers().size()).isEqualTo(1); + assertThat(compositeResolver.getViewResolvers()).hasSize(1); assertThat(compositeResolver.getOrder()).isEqualTo(Ordered.HIGHEST_PRECEDENCE); List resolvers = compositeResolver.getViewResolvers(); assertThat(resolvers.get(0).getClass()).isEqualTo(ContentNegotiatingViewResolver.class); ContentNegotiatingViewResolver cnvr = (ContentNegotiatingViewResolver) resolvers.get(0); - assertThat(cnvr.getViewResolvers().size()).isEqualTo(5); - assertThat(cnvr.getDefaultViews().size()).isEqualTo(1); + assertThat(cnvr.getViewResolvers()).hasSize(5); + assertThat(cnvr.getDefaultViews()).hasSize(1); assertThat(cnvr.isUseNotAcceptableStatusCode()).isTrue(); String beanName = "contentNegotiationManager"; @@ -907,7 +907,7 @@ public void testCorsMinimal() throws Exception { loadBeanDefinitions("mvc-config-cors-minimal.xml"); String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class); - assertThat(beanNames.length).isEqualTo(2); + assertThat(beanNames).hasSize(2); for (String beanName : beanNames) { AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName); assertThat(handlerMapping).isNotNull(); @@ -915,7 +915,7 @@ public void testCorsMinimal() throws Exception { Map configs = ((UrlBasedCorsConfigurationSource) accessor .getPropertyValue("corsConfigurationSource")).getCorsConfigurations(); assertThat(configs).isNotNull(); - assertThat(configs.size()).isEqualTo(1); + assertThat(configs).hasSize(1); CorsConfiguration config = configs.get("/**"); assertThat(config).isNotNull(); assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[]{"*"}); @@ -932,7 +932,7 @@ public void testCors() { loadBeanDefinitions("mvc-config-cors.xml"); String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class); - assertThat(beanNames.length).isEqualTo(2); + assertThat(beanNames).hasSize(2); for (String beanName : beanNames) { AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName); assertThat(handlerMapping).isNotNull(); @@ -940,7 +940,7 @@ public void testCors() { Map configs = ((UrlBasedCorsConfigurationSource) accessor .getPropertyValue("corsConfigurationSource")).getCorsConfigurations(); assertThat(configs).isNotNull(); - assertThat(configs.size()).isEqualTo(2); + assertThat(configs).hasSize(2); CorsConfiguration config = configs.get("/api/**"); assertThat(config).isNotNull(); assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[]{"https://domain1.com", "https://domain2.com"}); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/CorsRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/CorsRegistryTests.java index 9b64d9c26df4..9b267eb814a7 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/CorsRegistryTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/CorsRegistryTests.java @@ -50,7 +50,7 @@ public void noMapping() { public void multipleMappings() { this.registry.addMapping("/foo"); this.registry.addMapping("/bar"); - assertThat(this.registry.getCorsConfigurations().size()).isEqualTo(2); + assertThat(this.registry.getCorsConfigurations()).hasSize(2); } @Test @@ -59,7 +59,7 @@ public void customizedMapping() { .allowedMethods("DELETE").allowCredentials(false).allowedHeaders("header1", "header2") .exposedHeaders("header3", "header4").maxAge(3600); Map configs = this.registry.getCorsConfigurations(); - assertThat(configs.size()).isEqualTo(1); + assertThat(configs).hasSize(1); CorsConfiguration config = configs.get("/foo"); assertThat(config.getAllowedOrigins()).isEqualTo(Arrays.asList("https://domain2.com", "https://domain2.com")); assertThat(config.getAllowedMethods()).isEqualTo(Collections.singletonList("DELETE")); @@ -88,7 +88,7 @@ void combine() { this.registry.addMapping("/api/**").combine(otherConfig); Map configs = this.registry.getCorsConfigurations(); - assertThat(configs.size()).isEqualTo(1); + assertThat(configs).hasSize(1); CorsConfiguration config = configs.get("/api/**"); assertThat(config.getAllowedOrigins()).isEqualTo(Collections.singletonList("http://localhost:3000")); assertThat(config.getAllowedMethods()).isEqualTo(Collections.singletonList("*")); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationTests.java index 9460e848ffe5..5ac3850750a0 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationTests.java @@ -112,8 +112,8 @@ public void requestMappingHandlerAdapter() { assertThat(initializer.getConversionService()).isSameAs(conversionService.getValue()); boolean condition = initializer.getValidator() instanceof LocalValidatorFactoryBean; assertThat(condition).isTrue(); - assertThat(resolvers.getValue().size()).isEqualTo(0); - assertThat(handlers.getValue().size()).isEqualTo(0); + assertThat(resolvers.getValue()).isEmpty(); + assertThat(handlers.getValue()).isEmpty(); assertThat(adapter.getMessageConverters()).isEqualTo(converters.getValue()); assertThat(asyncConfigurer).isNotNull(); } @@ -174,7 +174,7 @@ public void handlerExceptionResolver() { verify(webMvcConfigurer).configureContentNegotiation(contentNegotiationConfigurer.capture()); verify(webMvcConfigurer).configureHandlerExceptionResolvers(exceptionResolvers.capture()); - assertThat(exceptionResolvers.getValue().size()).isEqualTo(3); + assertThat(exceptionResolvers.getValue()).hasSize(3); boolean condition2 = exceptionResolvers.getValue().get(0) instanceof ExceptionHandlerExceptionResolver; assertThat(condition2).isTrue(); boolean condition1 = exceptionResolvers.getValue().get(1) instanceof ResponseStatusExceptionResolver; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java index 0f641bfeab08..355e3814c4b9 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -105,7 +105,7 @@ public void addWebRequestInterceptor() throws Exception { this.registry.addWebRequestInterceptor(this.webInterceptor1); List interceptors = getInterceptorsForPath(null); - assertThat(interceptors.size()).isEqualTo(1); + assertThat(interceptors).hasSize(1); verifyWebInterceptor(interceptors.get(0), this.webInterceptor1); } @@ -115,7 +115,7 @@ public void addWebRequestInterceptors() throws Exception { this.registry.addWebRequestInterceptor(this.webInterceptor2); List interceptors = getInterceptorsForPath(null); - assertThat(interceptors.size()).isEqualTo(2); + assertThat(interceptors).hasSize(2); verifyWebInterceptor(interceptors.get(0), this.webInterceptor1); verifyWebInterceptor(interceptors.get(1), this.webInterceptor2); } @@ -135,11 +135,11 @@ public void addWebRequestInterceptorsWithUrlPatterns() throws Exception { this.registry.addWebRequestInterceptor(this.webInterceptor2).addPathPatterns("/path2"); List interceptors = getInterceptorsForPath("/path1"); - assertThat(interceptors.size()).isEqualTo(1); + assertThat(interceptors).hasSize(1); verifyWebInterceptor(interceptors.get(0), this.webInterceptor1); interceptors = getInterceptorsForPath("/path2"); - assertThat(interceptors.size()).isEqualTo(1); + assertThat(interceptors).hasSize(1); verifyWebInterceptor(interceptors.get(0), this.webInterceptor2); } @@ -159,7 +159,7 @@ public void orderedInterceptors() { this.registry.addInterceptor(this.interceptor2).order(Ordered.HIGHEST_PRECEDENCE); List interceptors = this.registry.getInterceptors(); - assertThat(interceptors.size()).isEqualTo(2); + assertThat(interceptors).hasSize(2); assertThat(interceptors.get(0)).isSameAs(this.interceptor2); assertThat(interceptors.get(1)).isSameAs(this.interceptor1); @@ -171,7 +171,7 @@ public void nonOrderedInterceptors() { this.registry.addInterceptor(this.interceptor2).order(0); List interceptors = this.registry.getInterceptors(); - assertThat(interceptors.size()).isEqualTo(2); + assertThat(interceptors).hasSize(2); assertThat(interceptors.get(0)).isSameAs(this.interceptor1); assertThat(interceptors.get(1)).isSameAs(this.interceptor2); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java index 7cbedbfd00cd..359ac12efa18 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -231,7 +231,7 @@ public void urlResourceWithCharset() { List resolvers = handler.getResourceResolvers(); PathResourceResolver resolver = (PathResourceResolver) resolvers.get(resolvers.size()-1); Map locationCharsets = resolver.getLocationCharsets(); - assertThat(locationCharsets.size()).isEqualTo(1); + assertThat(locationCharsets).hasSize(1); assertThat(locationCharsets.values().iterator().next()).isEqualTo(StandardCharsets.ISO_8859_1); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java index 4986338605cd..2853d626c65d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -86,7 +86,7 @@ public void hasRegistrationsWhenContentNegotiationEnabled() { @Test public void noResolvers() { assertThat(this.registry.getViewResolvers()).isNotNull(); - assertThat(this.registry.getViewResolvers().size()).isEqualTo(0); + assertThat(this.registry.getViewResolvers()).isEmpty(); assertThat(this.registry.hasRegistrations()).isFalse(); } @@ -100,7 +100,7 @@ public void customViewResolver() { @Test public void beanName() { this.registry.beanName(); - assertThat(this.registry.getViewResolvers().size()).isEqualTo(1); + assertThat(this.registry.getViewResolvers()).hasSize(1); assertThat(registry.getViewResolvers().get(0).getClass()).isEqualTo(BeanNameViewResolver.class); } @@ -123,7 +123,7 @@ public void jspMultipleResolvers() { this.registry.jsp().viewNames("view1", "view2"); this.registry.jsp().viewNames("view3", "view4"); assertThat(this.registry.getViewResolvers()).isNotNull(); - assertThat(this.registry.getViewResolvers().size()).isEqualTo(2); + assertThat(this.registry.getViewResolvers()).hasSize(2); assertThat(this.registry.getViewResolvers().get(0).getClass()).isEqualTo(InternalResourceViewResolver.class); assertThat(this.registry.getViewResolvers().get(1).getClass()).isEqualTo(InternalResourceViewResolver.class); } @@ -199,7 +199,7 @@ public void contentNegotiationAddsDefaultViewRegistrations() { @SuppressWarnings("unchecked") private T checkAndGetResolver(Class resolverType) { assertThat(this.registry.getViewResolvers()).isNotNull(); - assertThat(this.registry.getViewResolvers().size()).isEqualTo(1); + assertThat(this.registry.getViewResolvers()).hasSize(1); assertThat(this.registry.getViewResolvers().get(0).getClass()).isEqualTo(resolverType); return (T) registry.getViewResolvers().get(0); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java index 2232f77a25aa..b03c84fb6398 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java @@ -137,14 +137,14 @@ public void handlerMappings() throws Exception { assertThat(chain).isNotNull(); HandlerInterceptor[] interceptors = chain.getInterceptors(); assertThat(interceptors).isNotNull(); - assertThat(interceptors.length).isEqualTo(4); + assertThat(interceptors).hasSize(4); assertThat(interceptors[0].getClass().getSimpleName()).isEqualTo("CorsInterceptor"); assertThat(interceptors[1].getClass()).isEqualTo(LocaleChangeInterceptor.class); assertThat(interceptors[2].getClass()).isEqualTo(ConversionServiceExposingInterceptor.class); assertThat(interceptors[3].getClass()).isEqualTo(ResourceUrlProviderExposingInterceptor.class); Map map = rmHandlerMapping.getHandlerMethods(); - assertThat(map.size()).isEqualTo(2); + assertThat(map).hasSize(2); RequestMappingInfo info = map.entrySet().stream() .filter(entry -> entry.getValue().getBeanType().equals(UserController.class)) .findFirst() @@ -210,7 +210,7 @@ public void requestMappingHandlerAdapter() { // Message converters List> converters = adapter.getMessageConverters(); - assertThat(converters.size()).isEqualTo(2); + assertThat(converters).hasSize(2); assertThat(converters.get(0).getClass()).isEqualTo(StringHttpMessageConverter.class); assertThat(converters.get(1).getClass()).isEqualTo(MappingJackson2HttpMessageConverter.class); ObjectMapper objectMapper = ((MappingJackson2HttpMessageConverter) converters.get(1)).getObjectMapper(); @@ -223,11 +223,11 @@ public void requestMappingHandlerAdapter() { // Custom argument resolvers and return value handlers List argResolvers = (List) fieldAccessor.getPropertyValue("customArgumentResolvers"); - assertThat(argResolvers.size()).isEqualTo(1); + assertThat(argResolvers).hasSize(1); List handlers = (List) fieldAccessor.getPropertyValue("customReturnValueHandlers"); - assertThat(handlers.size()).isEqualTo(1); + assertThat(handlers).hasSize(1); // Async support options assertThat(fieldAccessor.getPropertyValue("taskExecutor").getClass()).isEqualTo(ConcurrentTaskExecutor.class); @@ -235,11 +235,11 @@ public void requestMappingHandlerAdapter() { CallableProcessingInterceptor[] callableInterceptors = (CallableProcessingInterceptor[]) fieldAccessor.getPropertyValue("callableInterceptors"); - assertThat(callableInterceptors.length).isEqualTo(1); + assertThat(callableInterceptors).hasSize(1); DeferredResultProcessingInterceptor[] deferredResultInterceptors = (DeferredResultProcessingInterceptor[]) fieldAccessor.getPropertyValue("deferredResultInterceptors"); - assertThat(deferredResultInterceptors.length).isEqualTo(1); + assertThat(deferredResultInterceptors).hasSize(1); assertThat(fieldAccessor.getPropertyValue("ignoreDefaultModelOnRedirect")).asInstanceOf(BOOLEAN).isTrue(); } @@ -299,7 +299,7 @@ public void exceptionResolvers() throws Exception { List resolvers = ((HandlerExceptionResolverComposite) this.config.handlerExceptionResolver(null)).getExceptionResolvers(); - assertThat(resolvers.size()).isEqualTo(2); + assertThat(resolvers).hasSize(2); assertThat(resolvers.get(0).getClass()).isEqualTo(ResponseStatusExceptionResolver.class); assertThat(resolvers.get(1).getClass()).isEqualTo(SimpleMappingExceptionResolver.class); } @@ -313,19 +313,19 @@ public void viewResolvers() throws Exception { List viewResolvers = viewResolver.getViewResolvers(); DirectFieldAccessor accessor = new DirectFieldAccessor(viewResolvers.get(0)); - assertThat(viewResolvers.size()).isEqualTo(1); + assertThat(viewResolvers).hasSize(1); assertThat(viewResolvers.get(0).getClass()).isEqualTo(ContentNegotiatingViewResolver.class); assertThat((boolean) (Boolean) accessor.getPropertyValue("useNotAcceptableStatusCode")).isFalse(); assertThat(accessor.getPropertyValue("contentNegotiationManager")).isNotNull(); List defaultViews = (List) accessor.getPropertyValue("defaultViews"); assertThat(defaultViews).isNotNull(); - assertThat(defaultViews.size()).isEqualTo(1); + assertThat(defaultViews).hasSize(1); assertThat(defaultViews.get(0).getClass()).isEqualTo(MappingJackson2JsonView.class); viewResolvers = (List) accessor.getPropertyValue("viewResolvers"); assertThat(viewResolvers).isNotNull(); - assertThat(viewResolvers.size()).isEqualTo(1); + assertThat(viewResolvers).hasSize(1); assertThat(viewResolvers.get(0).getClass()).isEqualTo(InternalResourceViewResolver.class); accessor = new DirectFieldAccessor(viewResolvers.get(0)); assertThat(accessor.getPropertyValue("prefix")).isEqualTo("/"); @@ -335,7 +335,7 @@ public void viewResolvers() throws Exception { @Test public void crossOrigin() { Map configs = this.config.getCorsConfigurations(); - assertThat(configs.size()).isEqualTo(1); + assertThat(configs).hasSize(1); assertThat(configs.get("/resources/**").getAllowedOrigins().get(0)).isEqualTo("*"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index 2d7996b5aeda..c8c7efd52cf2 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -163,7 +163,7 @@ public void beanNameHandlerMapping() throws Exception { assertThat(chain).isNotNull(); HandlerInterceptor[] interceptors = chain.getInterceptors(); assertThat(interceptors).isNotNull(); - assertThat(interceptors.length).isEqualTo(3); + assertThat(interceptors).hasSize(3); assertThat(interceptors[1].getClass()).isEqualTo(ConversionServiceExposingInterceptor.class); assertThat(interceptors[2].getClass()).isEqualTo(ResourceUrlProviderExposingInterceptor.class); } @@ -173,7 +173,7 @@ public void requestMappingHandlerAdapter() { ApplicationContext context = initContext(WebConfig.class); RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); List> converters = adapter.getMessageConverters(); - assertThat(converters).hasSizeGreaterThanOrEqualTo(15); + assertThat(converters).hasSizeGreaterThanOrEqualTo(14); converters.stream() .filter(converter -> converter instanceof AbstractJackson2HttpMessageConverter) .forEach(converter -> { @@ -203,7 +203,7 @@ public void requestMappingHandlerAdapter() { DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter); @SuppressWarnings("unchecked") List bodyAdvice = (List) fieldAccessor.getPropertyValue("requestResponseBodyAdvice"); - assertThat(bodyAdvice.size()).isEqualTo(2); + assertThat(bodyAdvice).hasSize(2); assertThat(bodyAdvice.get(0).getClass()).isEqualTo(JsonViewRequestBodyAdvice.class); assertThat(bodyAdvice.get(1).getClass()).isEqualTo(JsonViewResponseBodyAdvice.class); } @@ -237,7 +237,7 @@ public void handlerExceptionResolver() { DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(eher); List interceptors = (List) fieldAccessor.getPropertyValue("responseBodyAdvice"); - assertThat(interceptors.size()).isEqualTo(1); + assertThat(interceptors).hasSize(1); assertThat(interceptors.get(0).getClass()).isEqualTo(JsonViewResponseBodyAdvice.class); LocaleContextHolder.setLocale(Locale.ENGLISH); @@ -260,21 +260,21 @@ public void customArgumentResolvers() { HandlerExceptionResolverComposite composite = context.getBean(HandlerExceptionResolverComposite.class); assertThat(adapter).isNotNull(); - assertThat(adapter.getCustomArgumentResolvers().size()).isEqualTo(1); + assertThat(adapter.getCustomArgumentResolvers()).hasSize(1); assertThat(adapter.getCustomArgumentResolvers().get(0).getClass()).isEqualTo(TestArgumentResolver.class); - assertThat(adapter.getCustomReturnValueHandlers().size()).isEqualTo(1); + assertThat(adapter.getCustomReturnValueHandlers()).hasSize(1); assertThat(adapter.getCustomReturnValueHandlers().get(0).getClass()).isEqualTo(TestReturnValueHandler.class); assertThat(composite).isNotNull(); - assertThat(composite.getExceptionResolvers().size()).isEqualTo(3); + assertThat(composite.getExceptionResolvers()).hasSize(3); assertThat(composite.getExceptionResolvers().get(0).getClass()).isEqualTo(ExceptionHandlerExceptionResolver.class); ExceptionHandlerExceptionResolver resolver = (ExceptionHandlerExceptionResolver) composite.getExceptionResolvers().get(0); - assertThat(resolver.getCustomArgumentResolvers().size()).isEqualTo(1); + assertThat(resolver.getCustomArgumentResolvers()).hasSize(1); assertThat(resolver.getCustomArgumentResolvers().get(0).getClass()).isEqualTo(TestArgumentResolver.class); - assertThat(resolver.getCustomReturnValueHandlers().size()).isEqualTo(1); + assertThat(resolver.getCustomReturnValueHandlers()).hasSize(1); assertThat(resolver.getCustomReturnValueHandlers().get(0).getClass()).isEqualTo(TestReturnValueHandler.class); } @@ -285,7 +285,7 @@ public void mvcViewResolver() { ViewResolverComposite resolver = context.getBean("mvcViewResolver", ViewResolverComposite.class); assertThat(resolver).isNotNull(); - assertThat(resolver.getViewResolvers().size()).isEqualTo(1); + assertThat(resolver.getViewResolvers()).hasSize(1); assertThat(resolver.getViewResolvers().get(0).getClass()).isEqualTo(InternalResourceViewResolver.class); assertThat(resolver.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE); } @@ -296,7 +296,7 @@ public void mvcViewResolverWithExistingResolver() throws Exception { ViewResolverComposite resolver = context.getBean("mvcViewResolver", ViewResolverComposite.class); assertThat(resolver).isNotNull(); - assertThat(resolver.getViewResolvers().size()).isEqualTo(0); + assertThat(resolver.getViewResolvers()).isEmpty(); assertThat(resolver.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE); assertThat(resolver.resolveViewName("anyViewName", Locale.ENGLISH)).isNull(); } @@ -307,7 +307,7 @@ public void mvcViewResolverWithOrderSet() { ViewResolverComposite resolver = context.getBean("mvcViewResolver", ViewResolverComposite.class); assertThat(resolver).isNotNull(); - assertThat(resolver.getViewResolvers().size()).isEqualTo(1); + assertThat(resolver.getViewResolvers()).hasSize(1); assertThat(resolver.getViewResolvers().get(0).getClass()).isEqualTo(InternalResourceViewResolver.class); assertThat(resolver.getOrder()).isEqualTo(123); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java index 45659cec6246..476ac65e50bc 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -140,7 +140,7 @@ public void cookies() throws Exception { MockHttpServletResponse response = new MockHttpServletResponse(); ModelAndView mav = result.writeTo(request, response, EMPTY_CONTEXT); assertThat(mav).isNotNull(); - assertThat(response.getCookies().length).isEqualTo(1); + assertThat(response.getCookies()).hasSize(1); assertThat(response.getCookies()[0].getName()).isEqualTo("name"); assertThat(response.getCookies()[0].getValue()).isEqualTo("value"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java index d8aa06113e36..08890f6670bd 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -283,7 +283,7 @@ void bodyParameterizedTypeReference() throws Exception { Collections.singletonList(new MappingJackson2HttpMessageConverter())); List result = request.body(new ParameterizedTypeReference>() {}); - assertThat(result.size()).isEqualTo(2); + assertThat(result).hasSize(2); assertThat(result.get(0)).isEqualTo("foo"); assertThat(result.get(1)).isEqualTo("bar"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java index 90aab2cff2f7..96c76e6530dc 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/ResourceHandlerFunctionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -162,7 +162,7 @@ public void head() throws IOException, ServletException { assertThat(servletResponse.getStatus()).isEqualTo(200); byte[] actualBytes = servletResponse.getContentAsByteArray(); - assertThat(actualBytes.length).isEqualTo(0); + assertThat(actualBytes).isEmpty(); assertThat(servletResponse.getContentType()).isEqualTo(MediaType.TEXT_PLAIN_VALUE); assertThat(servletResponse.getContentLength()).isEqualTo(this.resource.contentLength()); } @@ -185,7 +185,7 @@ public void options() throws ServletException, IOException { String[] methods = StringUtils.tokenizeToStringArray(allowHeader, ","); assertThat(methods).containsExactlyInAnyOrder("GET","HEAD","OPTIONS"); byte[] actualBytes = servletResponse.getContentAsByteArray(); - assertThat(actualBytes.length).isEqualTo(0); + assertThat(actualBytes).isEmpty(); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/support/RouterFunctionMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/support/RouterFunctionMappingTests.java index ead0a1e538bc..df5dfbcef914 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/support/RouterFunctionMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/support/RouterFunctionMappingTests.java @@ -26,7 +26,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.observation.ServerRequestObservationContext; +import org.springframework.http.server.observation.ServerRequestObservationContext; import org.springframework.web.filter.ServerHttpObservationFilter; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerMapping; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java index ccda745f7935..95ab45ba4657 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -165,14 +165,14 @@ public void detectHandlerMethodsInAncestorContexts() { mapping1.setApplicationContext(new StaticApplicationContext(cxt)); mapping1.afterPropertiesSet(); - assertThat(mapping1.getHandlerMethods().size()).isEqualTo(0); + assertThat(mapping1.getHandlerMethods()).isEmpty(); AbstractHandlerMethodMapping mapping2 = new MyHandlerMethodMapping(); mapping2.setDetectHandlerMethodsInAncestorContexts(true); mapping2.setApplicationContext(new StaticApplicationContext(cxt)); mapping2.afterPropertiesSet(); - assertThat(mapping2.getHandlerMethods().size()).isEqualTo(2); + assertThat(mapping2.getHandlerMethods()).hasSize(2); } @Test @@ -186,7 +186,7 @@ public void registerMapping() { List directUrlMatches = this.mapping.getMappingRegistry().getMappingsByDirectPath(key1); assertThat(directUrlMatches).isNotNull(); - assertThat(directUrlMatches.size()).isEqualTo(1); + assertThat(directUrlMatches).hasSize(1); assertThat(directUrlMatches.get(0)).isEqualTo(key1); // Mapping name lookup @@ -197,13 +197,13 @@ public void registerMapping() { String name1 = this.method1.getName(); List handlerMethods = this.mapping.getMappingRegistry().getHandlerMethodsByMappingName(name1); assertThat(handlerMethods).isNotNull(); - assertThat(handlerMethods.size()).isEqualTo(1); + assertThat(handlerMethods).hasSize(1); assertThat(handlerMethods.get(0)).isEqualTo(handlerMethod1); String name2 = this.method2.getName(); handlerMethods = this.mapping.getMappingRegistry().getHandlerMethodsByMappingName(name2); assertThat(handlerMethods).isNotNull(); - assertThat(handlerMethods.size()).isEqualTo(1); + assertThat(handlerMethods).hasSize(1); assertThat(handlerMethods.get(0)).isEqualTo(handlerMethod2); } @@ -225,7 +225,7 @@ public void registerMappingWithSameMethodAndTwoHandlerInstances() { List directUrlMatches = this.mapping.getMappingRegistry().getMappingsByDirectPath(key1); assertThat(directUrlMatches).isNotNull(); - assertThat(directUrlMatches.size()).isEqualTo(1); + assertThat(directUrlMatches).hasSize(1); assertThat(directUrlMatches.get(0)).isEqualTo(key1); // Mapping name lookup @@ -233,7 +233,7 @@ public void registerMappingWithSameMethodAndTwoHandlerInstances() { String name = this.method1.getName(); List handlerMethods = this.mapping.getMappingRegistry().getHandlerMethodsByMappingName(name); assertThat(handlerMethods).isNotNull(); - assertThat(handlerMethods.size()).isEqualTo(2); + assertThat(handlerMethods).hasSize(2); assertThat(handlerMethods.get(0)).isEqualTo(handlerMethod1); assertThat(handlerMethods.get(1)).isEqualTo(handlerMethod2); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/ParameterizableViewControllerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/ParameterizableViewControllerTests.java index 4ed63c4fc37b..376a0ca3d161 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/ParameterizableViewControllerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/ParameterizableViewControllerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -66,7 +66,7 @@ public void handleRequestWithoutViewName() throws Exception { public void handleRequestWithFlashAttributes() throws Exception { this.request.setAttribute(DispatcherServlet.INPUT_FLASH_MAP_ATTRIBUTE, new ModelMap("name", "value")); ModelAndView mav = this.controller.handleRequest(this.request, new MockHttpServletResponse()); - assertThat(mav.getModel().size()).isEqualTo(1); + assertThat(mav.getModel()).hasSize(1); assertThat(mav.getModel().get("name")).isEqualTo("value"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/UrlFilenameViewControllerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/UrlFilenameViewControllerTests.java index a43e86e9ab58..b61271525a1c 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/UrlFilenameViewControllerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/UrlFilenameViewControllerTests.java @@ -192,7 +192,7 @@ void withFlashAttributes(Function requestFactory request.setAttribute(DispatcherServlet.INPUT_FLASH_MAP_ATTRIBUTE, new ModelMap("name", "value")); ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse()); assertThat(mv.getViewName()).isEqualTo("index"); - assertThat(mv.getModel().size()).isEqualTo(1); + assertThat(mv.getModel()).hasSize(1); assertThat(mv.getModel().get("name")).isEqualTo("value"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java index eee6f72b9a70..4c2c0b2f381f 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -111,7 +111,7 @@ public void notAnnotated() { } @Test // SPR-12903 - public void nestedException() throws Exception { + public void nestedException() { Exception cause = new StatusCodeAndReasonMessageException(); TypeMismatchException ex = new TypeMismatchException("value", ITestBean.class, cause); ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex); @@ -119,14 +119,14 @@ public void nestedException() throws Exception { } @Test - public void responseStatusException() throws Exception { + public void responseStatusException() { ResponseStatusException ex = new ResponseStatusException(HttpStatus.BAD_REQUEST); ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex); assertResolved(mav, 400, null); } @Test // SPR-15524 - public void responseStatusExceptionWithReason() throws Exception { + public void responseStatusExceptionWithReason() { ResponseStatusException ex = new ResponseStatusException(HttpStatus.BAD_REQUEST, "The reason"); ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex); assertResolved(mav, 400, "The reason"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/HeadersRequestConditionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/HeadersRequestConditionTests.java index 56d466915c3c..c55d34e0ab67 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/HeadersRequestConditionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/HeadersRequestConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -161,7 +161,7 @@ public void combine() { HeadersRequestCondition result = condition1.combine(condition2); Collection conditions = result.getContent(); - assertThat(conditions.size()).isEqualTo(2); + assertThat(conditions).hasSize(2); } @Test diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/ParamsRequestConditionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/ParamsRequestConditionTests.java index 6b1a4d364d02..c586b68ba3d1 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/ParamsRequestConditionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/ParamsRequestConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -130,7 +130,7 @@ public void combine() { ParamsRequestCondition result = condition1.combine(condition2); Collection conditions = result.getContent(); - assertThat(conditions.size()).isEqualTo(2); + assertThat(conditions).hasSize(2); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestConditionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestConditionTests.java index 7cb38ac95a1b..637f423fac0a 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestConditionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/condition/RequestMethodsRequestConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -123,7 +123,7 @@ public void combine() { RequestMethodsRequestCondition condition2 = new RequestMethodsRequestCondition(POST); RequestMethodsRequestCondition result = condition1.combine(condition2); - assertThat(result.getContent().size()).isEqualTo(2); + assertThat(result.getContent()).hasSize(2); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java index 5dcf68759d3f..b77fe1fe76b2 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -33,8 +34,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.http.observation.ServerRequestObservationContext; import org.springframework.http.server.RequestPath; +import org.springframework.http.server.observation.ServerRequestObservationContext; import org.springframework.lang.Nullable; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; @@ -364,12 +365,12 @@ void handleMatchMatrixVariables(TestRequestMappingInfoHandlerMapping mapping) { assertThat(matrixVariables).isNotNull(); if (mapping.getPatternParser() != null) { - assertThat(matrixVariables.size()).isEqualTo(1); + assertThat(matrixVariables).hasSize(1); assertThat(matrixVariables.getFirst("b")).isEqualTo("c"); assertThat(uriVariables.get("foo")).isEqualTo("a=42"); } else { - assertThat(matrixVariables.size()).isEqualTo(2); + assertThat(matrixVariables).hasSize(2); assertThat(matrixVariables.getFirst("a")).isEqualTo("42"); assertThat(matrixVariables.getFirst("b")).isEqualTo("c"); assertThat(uriVariables.get("foo")).isEqualTo("a=42"); @@ -397,6 +398,18 @@ void handleMatchMatrixVariablesDecoding(TestRequestMappingInfoHandlerMapping map assertThat(uriVariables.get("cars")).isEqualTo("cars"); } + @PathPatternsParameterizedTest // gh-29611 + void handleNoMatchWithoutPartialMatches(TestRequestMappingInfoHandlerMapping mapping) throws ServletException { + String path = "/non-existent"; + MockHttpServletRequest request = new MockHttpServletRequest("GET", path); + + HandlerMethod handlerMethod = mapping.handleNoMatch(new HashSet<>(), path, request); + assertThat(handlerMethod).isNull(); + + handlerMethod = mapping.handleNoMatch(null, path, request); + assertThat(handlerMethod).isNull(); + } + private HandlerMethod getHandler( TestRequestMappingInfoHandlerMapping mapping, MockHttpServletRequest request) throws Exception { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java index 0e1cb644cbc2..87843e1efb0b 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java @@ -59,7 +59,7 @@ void createEmpty(RequestMappingInfo.Builder infoBuilder) { // gh-22543 RequestMappingInfo info = infoBuilder.build(); assertThat(info.getPatternValues()).isEqualTo(Collections.singleton("")); - assertThat(info.getMethodsCondition().getMethods().size()).isEqualTo(0); + assertThat(info.getMethodsCondition().getMethods()).isEmpty(); assertThat(info.getParamsCondition()).isNotNull(); assertThat(info.getHeadersCondition()).isNotNull(); assertThat(info.getConsumesCondition().isEmpty()).isTrue(); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java index 42b86b9b9097..ea0b51434309 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java @@ -228,7 +228,7 @@ void resolveExceptionModelAtArgument() throws Exception { ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex); assertThat(mav).isNotNull(); - assertThat(mav.getModelMap().size()).isEqualTo(1); + assertThat(mav.getModelMap()).hasSize(1); assertThat(mav.getModelMap().get("exceptionClassName")).isEqualTo("IllegalArgumentException"); } @@ -438,8 +438,8 @@ void resolveExceptionViaMappedHandler() throws Exception { private void assertMethodProcessorCount(int resolverCount, int handlerCount) { - assertThat(this.resolver.getArgumentResolvers().getResolvers().size()).isEqualTo(resolverCount); - assertThat(this.resolver.getReturnValueHandlers().getHandlers().size()).isEqualTo(handlerCount); + assertThat(this.resolver.getArgumentResolvers().getResolvers()).hasSize(resolverCount); + assertThat(this.resolver.getReturnValueHandlers().getHandlers()).hasSize(handlerCount); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java index 11b54eb8285a..455bd0c894a8 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java @@ -832,14 +832,14 @@ private void assertConditionalResponse(HttpStatus status, String body, String et assertResponseBody(body); } else { - assertThat(servletResponse.getContentAsByteArray().length).isEqualTo(0); + assertThat(servletResponse.getContentAsByteArray()).isEmpty(); } if (etag != null) { - assertThat(servletResponse.getHeaderValues(HttpHeaders.ETAG).size()).isEqualTo(1); + assertThat(servletResponse.getHeaderValues(HttpHeaders.ETAG)).hasSize(1); assertThat(servletResponse.getHeader(HttpHeaders.ETAG)).isEqualTo(etag); } if (lastModified != -1) { - assertThat(servletResponse.getHeaderValues(HttpHeaders.LAST_MODIFIED).size()).isEqualTo(1); + assertThat(servletResponse.getHeaderValues(HttpHeaders.LAST_MODIFIED)).hasSize(1); assertThat((servletResponse.getDateHeader(HttpHeaders.LAST_MODIFIED) / 1000)).isEqualTo((lastModified / 1000)); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java index 27a93e1e5000..72c0bb09096b 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java @@ -152,7 +152,7 @@ public void handleRedirectWithIgnoreDefaultModel() throws Exception { ModelMap model = mavContainer.getModel(); assertThat(mavContainer.getView()).isSameAs(redirectView); - assertThat(model.size()).isEqualTo(1); + assertThat(model).hasSize(1); assertThat(model.get("name")).isEqualTo("value"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolverTests.java index c2753eeb5f25..978bf77f245e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -98,7 +98,7 @@ public void resolveArgument() throws Exception { @SuppressWarnings("unchecked") Map pathVars = (Map) request.getAttribute(View.PATH_VARIABLES); assertThat(pathVars).isNotNull(); - assertThat(pathVars.size()).isEqualTo(1); + assertThat(pathVars).hasSize(1); assertThat(pathVars.get("name")).isEqualTo("value"); } @@ -114,7 +114,7 @@ public void resolveArgumentNotRequired() throws Exception { @SuppressWarnings("unchecked") Map pathVars = (Map) request.getAttribute(View.PATH_VARIABLES); assertThat(pathVars).isNotNull(); - assertThat(pathVars.size()).isEqualTo(1); + assertThat(pathVars).hasSize(1); assertThat(pathVars.get("name")).isEqualTo("value"); } @@ -136,7 +136,7 @@ public void resolveArgumentWrappedAsOptional() throws Exception { @SuppressWarnings("unchecked") Map pathVars = (Map) request.getAttribute(View.PATH_VARIABLES); assertThat(pathVars).isNotNull(); - assertThat(pathVars.size()).isEqualTo(1); + assertThat(pathVars).hasSize(1); assertThat(pathVars.get("name")).isEqualTo(Optional.of("value")); } @@ -155,7 +155,7 @@ public void resolveArgumentWithExistingPathVars() throws Exception { @SuppressWarnings("unchecked") Map pathVars = (Map) request.getAttribute(View.PATH_VARIABLES); assertThat(pathVars).isNotNull(); - assertThat(pathVars.size()).isEqualTo(2); + assertThat(pathVars).hasSize(2); assertThat(pathVars.get("name")).isEqualTo("value"); assertThat(pathVars.get("oldName")).isEqualTo("oldValue"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java index 99f94f087d94..e5e7855a5445 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -17,7 +17,6 @@ package org.springframework.web.servlet.mvc.method.annotation; import java.awt.Color; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -102,19 +101,17 @@ * @see HandlerMethodAnnotationDetectionTests * @see ServletAnnotationControllerHandlerMethodTests */ -public class RequestMappingHandlerAdapterIntegrationTests { +class RequestMappingHandlerAdapterIntegrationTests { private final Object handler = new Handler(); + private final MockHttpServletRequest request = new MockHttpServletRequest(); + private final MockHttpServletResponse response = new MockHttpServletResponse(); private RequestMappingHandlerAdapter handlerAdapter; - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - @BeforeEach - public void setup() throws Exception { + void setup() throws Exception { ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer(); bindingInitializer.setValidator(new StubValidator()); @@ -132,9 +129,6 @@ public void setup() throws Exception { handlerAdapter.setBeanFactory(context.getBeanFactory()); handlerAdapter.afterPropertiesSet(); - request = new MockHttpServletRequest(); - response = new MockHttpServletResponse(); - request.setMethod("POST"); // Expose request to the current thread (for SpEL expressions) @@ -142,13 +136,13 @@ public void setup() throws Exception { } @AfterEach - public void teardown() { + void teardown() { RequestContextHolder.resetRequestAttributes(); } @Test - public void handle() throws Exception { + void handle() throws Exception { Class[] parameterTypes = new Class[] {int.class, String.class, String.class, String.class, Map.class, Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class, Color.class, HttpServletRequest.class, HttpServletResponse.class, TestBean.class, TestBean.class, @@ -226,11 +220,11 @@ public void handle() throws Exception { assertThat(model.get("sessionAttribute")).isSameAs(sessionAttribute); assertThat(model.get("requestAttribute")).isSameAs(requestAttribute); - assertThat(model.get("url")).isEqualTo(new URI("http://localhost/contextPath/main/path")); + assertThat(model.get("url")).isEqualTo(URI.create("http://localhost/contextPath/main/path")); } @Test - public void handleInInterface() throws Exception { + void handleInInterface() throws Exception { Class[] parameterTypes = new Class[] {int.class, String.class, String.class, String.class, Map.class, Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class, Color.class, HttpServletRequest.class, HttpServletResponse.class, TestBean.class, TestBean.class, @@ -307,11 +301,11 @@ public void handleInInterface() throws Exception { assertThat(model.get("sessionAttribute")).isSameAs(sessionAttribute); assertThat(model.get("requestAttribute")).isSameAs(requestAttribute); - assertThat(model.get("url")).isEqualTo(new URI("http://localhost/contextPath/main/path")); + assertThat(model.get("url")).isEqualTo(URI.create("http://localhost/contextPath/main/path")); } @Test - public void handleRequestBody() throws Exception { + void handleRequestBody() throws Exception { Class[] parameterTypes = new Class[] {byte[].class}; request.setMethod("POST"); @@ -328,7 +322,7 @@ public void handleRequestBody() throws Exception { } @Test - public void handleAndValidateRequestBody() throws Exception { + void handleAndValidateRequestBody() throws Exception { Class[] parameterTypes = new Class[] {TestBean.class, Errors.class}; request.addHeader("Content-Type", "text/plain; charset=utf-8"); @@ -344,7 +338,7 @@ public void handleAndValidateRequestBody() throws Exception { } @Test - public void handleHttpEntity() throws Exception { + void handleHttpEntity() throws Exception { Class[] parameterTypes = new Class[] {HttpEntity.class}; request.addHeader("Content-Type", "text/plain; charset=utf-8"); @@ -364,7 +358,7 @@ public void handleHttpEntity() throws Exception { // SPR-13867 @Test - public void handleHttpEntityWithCacheControl() throws Exception { + void handleHttpEntityWithCacheControl() throws Exception { Class[] parameterTypes = new Class[] {HttpEntity.class}; request.addHeader("Content-Type", "text/plain; charset=utf-8"); request.setContent("Hello Server".getBytes("UTF-8")); @@ -379,7 +373,7 @@ public void handleHttpEntityWithCacheControl() throws Exception { } @Test - public void handleRequestPart() throws Exception { + void handleRequestPart() throws Exception { MockMultipartHttpServletRequest multipartRequest = new MockMultipartHttpServletRequest(); multipartRequest.addFile(new MockMultipartFile("requestPart", "", "text/plain", "content".getBytes("UTF-8"))); @@ -391,7 +385,7 @@ public void handleRequestPart() throws Exception { } @Test - public void handleAndValidateRequestPart() throws Exception { + void handleAndValidateRequestPart() throws Exception { MockMultipartHttpServletRequest multipartRequest = new MockMultipartHttpServletRequest(); multipartRequest.addFile(new MockMultipartFile("requestPart", "", "text/plain", "content".getBytes("UTF-8"))); @@ -403,7 +397,7 @@ public void handleAndValidateRequestPart() throws Exception { } @Test - public void handleAndCompleteSession() throws Exception { + void handleAndCompleteSession() throws Exception { HandlerMethod handlerMethod = handlerMethod("handleAndCompleteSession", SessionStatus.class); handlerAdapter.handle(request, response, handlerMethod); @@ -654,8 +648,8 @@ public Object resolveArgument( } } - @Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE }) + @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) - @Documented - public @interface AuthenticationPrincipal {} + @interface AuthenticationPrincipal {} + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java index 811922635a0b..58003b875537 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java @@ -277,9 +277,9 @@ private HandlerMethod handlerMethod(Object handler, String methodName, Class. } private void assertMethodProcessorCount(int resolverCount, int initBinderResolverCount, int handlerCount) { - assertThat(this.handlerAdapter.getArgumentResolvers().size()).isEqualTo(resolverCount); - assertThat(this.handlerAdapter.getInitBinderArgumentResolvers().size()).isEqualTo(initBinderResolverCount); - assertThat(this.handlerAdapter.getReturnValueHandlers().size()).isEqualTo(handlerCount); + assertThat(this.handlerAdapter.getArgumentResolvers()).hasSize(resolverCount); + assertThat(this.handlerAdapter.getInitBinderArgumentResolvers()).hasSize(initBinderResolverCount); + assertThat(this.handlerAdapter.getReturnValueHandlers()).hasSize(handlerCount); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java index 42408234575e..1f9537ec4c63 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -299,11 +299,11 @@ private RequestMappingInfo assertComposedAnnotationMapping( assertThat(info).isNotNull(); Set paths = info.getPatternValues(); - assertThat(paths.size()).isEqualTo(1); + assertThat(paths).hasSize(1); assertThat(paths.iterator().next()).isEqualTo(path); Set methods = info.getMethodsCondition().getMethods(); - assertThat(methods.size()).isEqualTo(1); + assertThat(methods).hasSize(1); assertThat(methods.iterator().next()).isEqualTo(requestMethod); return info; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java index dd45fd080139..c54514bd66c0 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -75,7 +75,7 @@ * @author Brian Clozel * @author Sam Brannen */ -public class RequestPartIntegrationTests { +class RequestPartIntegrationTests { private RestTemplate restTemplate; @@ -85,7 +85,7 @@ public class RequestPartIntegrationTests { @BeforeAll - public static void startServer() throws Exception { + static void startServer() throws Exception { // Let server pick its own random, available port. server = new Server(0); @@ -106,14 +106,14 @@ public static void startServer() throws Exception { } @AfterAll - public static void stopServer() throws Exception { + static void stopServer() throws Exception { if (server != null) { server.stop(); } } @BeforeEach - public void setup() { + void setup() { ByteArrayHttpMessageConverter emptyBodyConverter = new ByteArrayHttpMessageConverter(); emptyBodyConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON)); @@ -132,13 +132,13 @@ public void setup() { @Test - public void standardMultipartResolver() throws Exception { + void standardMultipartResolver() throws Exception { testCreate(baseUrl + "/standard-resolver/test", "Jason"); testCreate(baseUrl + "/standard-resolver/test", "Arjen"); } @Test // SPR-13319 - public void standardMultipartResolverWithEncodedFileName() throws Exception { + void standardMultipartResolverWithEncodedFileName() throws Exception { String boundaryText = MimeTypeUtils.generateMultipartBoundaryString(); Map params = Collections.singletonMap("boundary", boundaryText); @@ -152,7 +152,7 @@ public void standardMultipartResolverWithEncodedFileName() throws Exception { "--" + boundaryText + "--"; RequestEntity requestEntity = - RequestEntity.post(new URI(baseUrl + "/standard-resolver/spr13319")) + RequestEntity.post(URI.create(baseUrl + "/standard-resolver/spr13319")) .contentType(new MediaType(MediaType.MULTIPART_FORM_DATA, params)) .body(content.getBytes(StandardCharsets.US_ASCII)); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java index d282b429962f..2c075f1b632c 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolverTests.java @@ -32,7 +32,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.http.HttpInputMessage; @@ -137,7 +137,7 @@ public InputStream getInputStream() throws IOException { Method method = ReflectionUtils.findMethod(getClass(), "handle", (Class[]) null); paramRequestPart = new SynthesizingMethodParameter(method, 0); - paramRequestPart.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); + paramRequestPart.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); paramNamedRequestPart = new SynthesizingMethodParameter(method, 1); paramValidRequestPart = new SynthesizingMethodParameter(method, 2); paramMultipartFile = new SynthesizingMethodParameter(method, 3); @@ -145,20 +145,20 @@ public InputStream getInputStream() throws IOException { paramMultipartFileArray = new SynthesizingMethodParameter(method, 5); paramInt = new SynthesizingMethodParameter(method, 6); paramMultipartFileNotAnnot = new SynthesizingMethodParameter(method, 7); - paramMultipartFileNotAnnot.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); + paramMultipartFileNotAnnot.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); paramPart = new SynthesizingMethodParameter(method, 8); - paramPart.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); + paramPart.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); paramPartList = new SynthesizingMethodParameter(method, 9); paramPartArray = new SynthesizingMethodParameter(method, 10); paramRequestParamAnnot = new SynthesizingMethodParameter(method, 11); optionalMultipartFile = new SynthesizingMethodParameter(method, 12); - optionalMultipartFile.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); + optionalMultipartFile.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); optionalMultipartFileList = new SynthesizingMethodParameter(method, 13); - optionalMultipartFileList.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); + optionalMultipartFileList.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); optionalPart = new SynthesizingMethodParameter(method, 14); - optionalPart.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); + optionalPart.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); optionalPartList = new SynthesizingMethodParameter(method, 15); - optionalPartList.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); + optionalPartList.initParameterNameDiscovery(new DefaultParameterNameDiscoverer()); optionalRequestPart = new SynthesizingMethodParameter(method, 16); } @@ -203,7 +203,7 @@ public void resolveMultipartFileArray() throws Exception { assertThat(actual).isNotNull(); assertThat(actual instanceof MultipartFile[]).isTrue(); MultipartFile[] parts = (MultipartFile[]) actual; - assertThat(parts.length).isEqualTo(2); + assertThat(parts).hasSize(2); assertThat(multipartFile1).isEqualTo(parts[0]); assertThat(multipartFile2).isEqualTo(parts[1]); } @@ -269,7 +269,7 @@ public void resolvePartArrayArgument() throws Exception { Object result = resolver.resolveArgument(paramPartArray, null, webRequest, null); assertThat(result instanceof Part[]).isTrue(); Part[] parts = (Part[]) result; - assertThat(parts.length).isEqualTo(2); + assertThat(parts).hasSize(2); assertThat(part1).isEqualTo(parts[0]); assertThat(part2).isEqualTo(parts[1]); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java index ad8e1187eb73..12529ef528ad 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java @@ -253,7 +253,13 @@ public void bindException() { @Test public void noHandlerFoundException() { - testException(new NoHandlerFoundException("GET", "/resource", HttpHeaders.EMPTY)); + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); // gh-29626 + + ResponseEntity responseEntity = + testException(new NoHandlerFoundException("GET", "/resource", requestHeaders)); + + assertThat(responseEntity.getHeaders()).isEmpty(); } @Test diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java index 6866eab072fd..a02189602766 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java @@ -27,7 +27,6 @@ import java.lang.annotation.Target; import java.lang.reflect.Method; import java.net.URI; -import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.security.Principal; import java.text.SimpleDateFormat; @@ -170,7 +169,7 @@ * @author Juergen Hoeller * @author Sam Brannen */ -public class ServletAnnotationControllerHandlerMethodTests extends AbstractServletHandlerMethodTests { +class ServletAnnotationControllerHandlerMethodTests extends AbstractServletHandlerMethodTests { static Stream pathPatternsArguments() { return Stream.of(true, false); @@ -2731,7 +2730,7 @@ public void initBinder(WebDataBinder binder, String date, @RequestParam("date") vf.afterPropertiesSet(); binder.setValidator(vf); assertThat(date).isEqualTo("2007-10-02"); - assertThat(date2.length).isEqualTo(1); + assertThat(date2).hasSize(1); assertThat(date2[0]).isEqualTo("2007-10-02"); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); @@ -3654,14 +3653,14 @@ public void templatePath(Writer writer) throws IOException { static class ResponseEntityController { @PostMapping("/foo") - public ResponseEntity foo(HttpEntity requestEntity) throws Exception { + public ResponseEntity foo(HttpEntity requestEntity) { assertThat(requestEntity).isNotNull(); assertThat(requestEntity.getHeaders().getFirst("MyRequestHeader")).isEqualTo("MyValue"); - String body = new String(requestEntity.getBody(), "UTF-8"); + String body = new String(requestEntity.getBody(), StandardCharsets.UTF_8); assertThat(body).isEqualTo("Hello World"); - URI location = new URI("/foo"); + URI location = URI.create("/foo"); return ResponseEntity.created(location).header("MyResponseHeader", "MyValue").body(body); } @@ -3868,9 +3867,9 @@ static class HttpHeadersResponseController { @RequestMapping(value = "/", method = RequestMethod.POST) @ResponseStatus(HttpStatus.CREATED) - public HttpHeaders create() throws URISyntaxException { + public HttpHeaders create() { HttpHeaders headers = new HttpHeaders(); - headers.setLocation(new URI("/test/items/123")); + headers.setLocation(URI.create("/test/items/123")); return headers; } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitterTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitterTests.java index 47844e7e00ec..fd4dc22b4386 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitterTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/SseEmitterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -126,7 +126,7 @@ private static class TestHandler implements ResponseBodyEmitter.Handler { public void assertSentObjectCount(int size) { - assertThat(this.objects.size()).isEqualTo(size); + assertThat(this.objects).hasSize(size); } public void assertObject(int index, Object object) { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java index 9339486bd374..11197dee86be 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/AnnotationConfigDispatcherServletInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -85,7 +85,7 @@ public void setUp() throws Exception { public void register() throws ServletException { initializer.onStartup(servletContext); - assertThat(servlets.size()).isEqualTo(1); + assertThat(servlets).hasSize(1); assertThat(servlets.get(SERVLET_NAME)).isNotNull(); DispatcherServlet servlet = (DispatcherServlet) servlets.get(SERVLET_NAME); @@ -96,7 +96,7 @@ public void register() throws ServletException { boolean condition = wac.getBean("bean") instanceof MyBean; assertThat(condition).isTrue(); - assertThat(servletRegistrations.size()).isEqualTo(1); + assertThat(servletRegistrations).hasSize(1); assertThat(servletRegistrations.get(SERVLET_NAME)).isNotNull(); MockServletRegistration servletRegistration = servletRegistrations.get(SERVLET_NAME); @@ -106,7 +106,7 @@ public void register() throws ServletException { assertThat(servletRegistration.getRunAsRole()).isEqualTo(ROLE_NAME); assertThat(servletRegistration.isAsyncSupported()).isTrue(); - assertThat(filterRegistrations.size()).isEqualTo(4); + assertThat(filterRegistrations).hasSize(4); assertThat(filterRegistrations.get("hiddenHttpMethodFilter")).isNotNull(); assertThat(filterRegistrations.get("delegatingFilterProxy")).isNotNull(); assertThat(filterRegistrations.get("delegatingFilterProxy#0")).isNotNull(); @@ -179,7 +179,7 @@ protected Filter[] getServletFilters() { initializer.onStartup(servletContext); - assertThat(filterRegistrations.size()).isEqualTo(0); + assertThat(filterRegistrations).isEmpty(); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java index 15bf12019e83..a527d1db4b44 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/DispatcherServletInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -59,7 +59,7 @@ public class DispatcherServletInitializerTests { public void register() throws ServletException { initializer.onStartup(servletContext); - assertThat(servlets.size()).isEqualTo(1); + assertThat(servlets).hasSize(1); assertThat(servlets.get(SERVLET_NAME)).isNotNull(); DispatcherServlet servlet = (DispatcherServlet) servlets.get(SERVLET_NAME); @@ -70,7 +70,7 @@ public void register() throws ServletException { boolean condition = servletContext.getBean("bean") instanceof MyBean; assertThat(condition).isTrue(); - assertThat(registrations.size()).isEqualTo(1); + assertThat(registrations).hasSize(1); assertThat(registrations.get(SERVLET_NAME)).isNotNull(); MockServletRegistration registration = registrations.get(SERVLET_NAME); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/AbstractTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/AbstractTagTests.java index 28c9a03708a2..3d44ecf01530 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/AbstractTagTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/AbstractTagTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/UrlTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/UrlTagTests.java index fd55353f41d6..e23cd6f2ac9b 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/UrlTagTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/UrlTagTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -351,7 +351,7 @@ public void replaceUriTemplateParamsNoParams() throws JspException { String uri = tag.replaceUriTemplateParams("url/path", params, usedParams); assertThat(uri).isEqualTo("url/path"); - assertThat(usedParams.size()).isEqualTo(0); + assertThat(usedParams).isEmpty(); } @Test @@ -361,7 +361,7 @@ public void replaceUriTemplateParamsTemplateWithoutParamMatch() throws JspExcept String uri = tag.replaceUriTemplateParams("url/{path}", params, usedParams); assertThat(uri).isEqualTo("url/{path}"); - assertThat(usedParams.size()).isEqualTo(0); + assertThat(usedParams).isEmpty(); } @Test @@ -376,7 +376,7 @@ public void replaceUriTemplateParamsTemplateWithParamMatch() throws JspException String uri = tag.replaceUriTemplateParams("url/{name}", params, usedParams); assertThat(uri).isEqualTo("url/value"); - assertThat(usedParams.size()).isEqualTo(1); + assertThat(usedParams).hasSize(1); assertThat(usedParams.contains("name")).isTrue(); } @@ -392,7 +392,7 @@ public void replaceUriTemplateParamsTemplateWithParamMatchNamePreEncoding() thro String uri = tag.replaceUriTemplateParams("url/{n me}", params, usedParams); assertThat(uri).isEqualTo("url/value"); - assertThat(usedParams.size()).isEqualTo(1); + assertThat(usedParams).hasSize(1); assertThat(usedParams.contains("n me")).isTrue(); } @@ -410,7 +410,7 @@ public void replaceUriTemplateParamsTemplateWithParamMatchValueEncoded() throws usedParams); assertThat(uri).isEqualTo("url/v%20lue"); - assertThat(usedParams.size()).isEqualTo(1); + assertThat(usedParams).hasSize(1); assertThat(usedParams.contains("name")).isTrue(); } @@ -427,7 +427,7 @@ public void replaceUriTemplateParamsTemplateWithPathSegment() throws JspExceptio String uri = tag.replaceUriTemplateParams("url/{/name}", params, usedParams); assertThat(uri).isEqualTo("url/my%2FId"); - assertThat(usedParams.size()).isEqualTo(1); + assertThat(usedParams).hasSize(1); assertThat(usedParams.contains("name")).isTrue(); } @@ -443,7 +443,7 @@ public void replaceUriTemplateParamsTemplateWithPath() throws JspException { String uri = tag.replaceUriTemplateParams("url/{name}", params, usedParams); assertThat(uri).isEqualTo("url/my/Id"); - assertThat(usedParams.size()).isEqualTo(1); + assertThat(usedParams).hasSize(1); assertThat(usedParams.contains("name")).isTrue(); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/CheckboxesTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/CheckboxesTagTests.java index 170d1cf36e40..3c875681eb92 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/CheckboxesTagTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/CheckboxesTagTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -359,7 +359,7 @@ public void withMultiValueWithEditor() throws Exception { int result = this.tag.doStartTag(); assertThat(result).isEqualTo(Tag.SKIP_BODY); - assertThat(editor.allProcessedValues.size()).isEqualTo(3); + assertThat(editor.allProcessedValues).hasSize(3); String output = getOutput(); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/InputTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/InputTagTests.java index 39da1b05c3f9..6a88081f8e32 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/InputTagTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/InputTagTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -55,7 +55,7 @@ protected TestBean createTestBean() { // set up test data this.rob = new TestBean(); this.rob.setName("Rob"); - this.rob.setMyFloat(Float.valueOf(12.34f)); + this.rob.setMyFloat(12.34f); TestBean sally = new TestBean(); sally.setName("Sally"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/OptionsTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/OptionsTagTests.java index a8e87811b901..b8cab1c58318 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/OptionsTagTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/OptionsTagTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -261,7 +261,7 @@ void withoutItemsEnumParent() throws Exception { Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Node value1 = rootElement.selectSingleNode("option[@value = 'VALUE_1']"); Node value2 = rootElement.selectSingleNode("option[@value = 'VALUE_2']"); assertThat(value1.getText()).isEqualTo("TestEnum: VALUE_1"); @@ -291,7 +291,7 @@ void withoutItemsEnumParentWithExplicitLabelsAndValues() throws Exception { Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Node value1 = rootElement.selectSingleNode("option[@value = 'Value: VALUE_1']"); Node value2 = rootElement.selectSingleNode("option[@value = 'Value: VALUE_2']"); assertThat(value1.getText()).isEqualTo("Label: VALUE_1"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonsTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonsTagTests.java index 49d2daf22297..26c05af6cb8d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonsTagTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonsTagTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -310,7 +310,7 @@ public void withMultiValueWithEditor() throws Exception { int result = this.tag.doStartTag(); assertThat(result).isEqualTo(Tag.SKIP_BODY); - assertThat(editor.allProcessedValues.size()).isEqualTo(3); + assertThat(editor.allProcessedValues).hasSize(3); String output = getOutput(); @@ -492,7 +492,7 @@ public void withoutItemsEnumBindTarget() throws Exception { Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Node value1 = rootElement.selectSingleNode("//input[@value = 'VALUE_1']"); Node value2 = rootElement.selectSingleNode("//input[@value = 'VALUE_2']"); assertThat(rootElement.selectSingleNode("//label[@for = '" + value1.valueOf("@id") + "']").getText()).isEqualTo("TestEnum: VALUE_1"); @@ -517,7 +517,7 @@ public void withoutItemsEnumBindTargetWithExplicitLabelsAndValues() throws Excep Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Node value1 = rootElement.selectSingleNode("//input[@value = 'Value: VALUE_1']"); Node value2 = rootElement.selectSingleNode("//input[@value = 'Value: VALUE_2']"); assertThat(rootElement.selectSingleNode("//label[@for = '" + value1.valueOf("@id") + "']").getText()).isEqualTo("Label: VALUE_1"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/SelectTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/SelectTagTests.java index d8a69d4d87c1..1f53566fcde1 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/SelectTagTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/SelectTagTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -401,7 +401,7 @@ public void withIntegerArray() throws Exception { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -474,7 +474,7 @@ public void withMultiList() throws Exception { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -523,7 +523,7 @@ public Country parse(String text, Locale locale) throws ParseException { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(1); + assertThat(rootElement.elements()).hasSize(1); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -571,7 +571,7 @@ public Country parse(String text, Locale locale) throws ParseException { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -617,7 +617,7 @@ public String getAsText() { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -652,7 +652,7 @@ public void withMultiMap() throws Exception { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -728,7 +728,7 @@ public String getAsText() { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -771,7 +771,7 @@ public void multipleForCollection() throws Exception { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -800,7 +800,7 @@ public void multipleWithStringValue() throws Exception { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -829,7 +829,7 @@ public void multipleExplicitlyTrue() throws Exception { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -858,7 +858,7 @@ public void multipleExplicitlyFalse() throws Exception { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(1); + assertThat(rootElement.elements()).hasSize(1); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -884,7 +884,7 @@ public void multipleWithBooleanTrue() throws Exception { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(2); + assertThat(rootElement.elements()).hasSize(2); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); @@ -913,7 +913,7 @@ public void multipleWithBooleanFalse() throws Exception { SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(output)); Element rootElement = document.getRootElement(); - assertThat(rootElement.elements().size()).isEqualTo(1); + assertThat(rootElement.elements()).hasSize(1); Element selectElement = rootElement.element("select"); assertThat(selectElement.getName()).isEqualTo("select"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/theme/ThemeResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/theme/ThemeResolverTests.java index 209fc9696749..89dc1c69c86e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/theme/ThemeResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/theme/ThemeResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/BaseViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/BaseViewTests.java index 29c69d17568a..c92ea46ffbdb 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/BaseViewTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/BaseViewTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -126,7 +126,7 @@ public void pathVarsOverrideStaticAttributes() throws Exception { checkContainsAll(pathVars, tv.model); - assertThat(tv.model.size()).isEqualTo(3); + assertThat(tv.model).hasSize(3); assertThat(tv.model.get("something")).isEqualTo("else"); assertThat(tv.initialized).isTrue(); } @@ -154,7 +154,7 @@ public void dynamicModelOverridesStaticAttributesIfCollision() throws Exception // Check it contains all checkContainsAll(model, tv.model); - assertThat(tv.model.size()).isEqualTo(3); + assertThat(tv.model).hasSize(3); assertThat(tv.model.get("something")).isEqualTo("else"); assertThat(tv.initialized).isTrue(); } @@ -182,7 +182,7 @@ public void dynamicModelOverridesPathVariables() throws Exception { tv.render(model, request, response); checkContainsAll(model, tv.model); - assertThat(tv.model.size()).isEqualTo(3); + assertThat(tv.model).hasSize(3); assertThat(tv.model.get("something")).isEqualTo("else"); assertThat(tv.initialized).isTrue(); } @@ -191,7 +191,7 @@ public void dynamicModelOverridesPathVariables() throws Exception { public void ignoresNullAttributes() { AbstractView v = new ConcreteView(); v.setAttributes(null); - assertThat(v.getStaticAttributes().size()).isEqualTo(0); + assertThat(v.getStaticAttributes()).isEmpty(); } /** @@ -201,14 +201,14 @@ public void ignoresNullAttributes() { public void attributeCSVParsingIgnoresNull() { AbstractView v = new ConcreteView(); v.setAttributesCSV(null); - assertThat(v.getStaticAttributes().size()).isEqualTo(0); + assertThat(v.getStaticAttributes()).isEmpty(); } @Test public void attributeCSVParsingIgnoresEmptyString() { AbstractView v = new ConcreteView(); v.setAttributesCSV(""); - assertThat(v.getStaticAttributes().size()).isEqualTo(0); + assertThat(v.getStaticAttributes()).isEmpty(); } /** @@ -259,7 +259,7 @@ public void attributeCSVParsingInvalid() { public void attributeCSVParsingIgnoresTrailingComma() { AbstractView v = new ConcreteView(); v.setAttributesCSV("foo=[de],"); - assertThat(v.getStaticAttributes().size()).isEqualTo(1); + assertThat(v.getStaticAttributes()).hasSize(1); } /** diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/ViewResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/ViewResolverTests.java index 506541bce39e..0154ed7fd3fb 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/ViewResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/ViewResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/KotlinScriptTemplateTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/KotlinScriptTemplateTests.java index 4cc53b606262..680e1881080a 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/KotlinScriptTemplateTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/KotlinScriptTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java index 93dd65275384..63a403e46cf1 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -62,7 +62,9 @@ public abstract class AbstractWebSocketSession implements NativeWebSocketSess */ public AbstractWebSocketSession(@Nullable Map attributes) { if (attributes != null) { - this.attributes.putAll(attributes); + attributes.entrySet().stream() + .filter(entry -> (entry.getKey() != null && entry.getValue() != null)) + .forEach(entry -> this.attributes.put(entry.getKey(), entry.getValue())); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/standard/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/standard/package-info.java index 26d1c4b67bf2..fac5f85a06bb 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/standard/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/standard/package-info.java @@ -1,5 +1,5 @@ /** - * Adapter classes for the standard Java WebSocket API. + * Adapter classes for the standard Jakarta WebSocket API. */ @NonNullApi @NonNullFields diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java index 96d0854c3adf..c893ba9baeaa 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java @@ -45,7 +45,8 @@ /** * Initiates WebSocket requests to a WebSocket server programmatically - * through the Jetty WebSocket API. + * through the Jetty WebSocket API. Only supported on Jetty 11, superseded by + * {@link org.springframework.web.socket.client.standard.StandardWebSocketClient}. * *

    As of 4.1 this class implements {@link Lifecycle} rather than * {@link org.springframework.context.SmartLifecycle}. Use @@ -55,7 +56,10 @@ * @author Rossen Stoyanchev * @author Juergen Hoeller * @since 4.0 + * @deprecated as of 6.0.3, in favor of + * {@link org.springframework.web.socket.client.standard.StandardWebSocketClient} */ +@Deprecated(since = "6.0.3", forRemoval = true) public class JettyWebSocketClient extends AbstractWebSocketClient implements Lifecycle { private final org.eclipse.jetty.websocket.client.WebSocketClient client; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java index f22c4e842b1b..801a28943997 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java @@ -51,7 +51,7 @@ import org.springframework.web.socket.client.AbstractWebSocketClient; /** - * A WebSocketClient based on standard Java WebSocket API. + * A WebSocketClient based on the standard Jakarta WebSocket API. * * @author Rossen Stoyanchev * @since 4.0 @@ -88,7 +88,7 @@ public StandardWebSocketClient(WebSocketContainer webSocketContainer) { /** - * The standard Java WebSocket API allows passing "user properties" to the + * The standard Jakarta WebSocket API allows passing "user properties" to the * server via {@link ClientEndpointConfig#getUserProperties() userProperties}. * Use this property to configure one or more properties to be passed on * every handshake. diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/package-info.java index 6bfe258f8b9c..501c3d4879d9 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/package-info.java @@ -1,5 +1,5 @@ /** - * Client-side classes for use with standard Java WebSocket endpoints. + * Client-side classes for use with standard Jakarta WebSocket endpoints. */ @NonNullApi @NonNullFields diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/handler/WebSocketHandlerDecoratorFactory.java b/spring-websocket/src/main/java/org/springframework/web/socket/handler/WebSocketHandlerDecoratorFactory.java index 8a3cc640f301..0be8400d82c2 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/handler/WebSocketHandlerDecoratorFactory.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/handler/WebSocketHandlerDecoratorFactory.java @@ -24,7 +24,7 @@ *

    Decoration should be done through sub-classing * {@link org.springframework.web.socket.handler.WebSocketHandlerDecorator * WebSocketHandlerDecorator} to allow any code to traverse decorators and/or - * unwrap the original handler when necessary . + * unwrap the original handler when necessary. * * @author Rossen Stoyanchev * @since 4.1.2 @@ -35,7 +35,7 @@ public interface WebSocketHandlerDecoratorFactory { * Decorate the given WebSocketHandler. * @param handler the handler to be decorated. * @return the same handler or the handler wrapped with a subclass of - * {@code WebSocketHandlerDecorator}. + * {@code WebSocketHandlerDecorator} */ WebSocketHandler decorate(WebSocketHandler handler); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionDisconnectEvent.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionDisconnectEvent.java index 4a5241a9db6c..7cd5b93dd222 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionDisconnectEvent.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SessionDisconnectEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SubProtocolWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SubProtocolWebSocketHandler.java index d629a9a92c64..0a546d80b08b 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SubProtocolWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SubProtocolWebSocketHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -267,7 +267,7 @@ public Stats getStats() { @Override public final void start() { - Assert.isTrue(this.defaultProtocolHandler != null || !this.protocolHandlers.isEmpty(), "No handlers"); + Assert.state(this.defaultProtocolHandler != null || !this.protocolHandlers.isEmpty(), "No handlers"); synchronized (this.lifecycleMonitor) { this.clientOutboundChannel.subscribe(this); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/RequestUpgradeStrategy.java index c88596017676..288193de2efa 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/RequestUpgradeStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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. @@ -31,6 +31,7 @@ * * @author Rossen Stoyanchev * @since 4.0 + * @see org.springframework.web.socket.server.standard.StandardWebSocketUpgradeStrategy */ public interface RequestUpgradeStrategy { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/jetty/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/jetty/JettyRequestUpgradeStrategy.java index 34e6b89f01a5..57387844add7 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/jetty/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/jetty/JettyRequestUpgradeStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -49,7 +49,7 @@ */ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { - private static final String[] SUPPORTED_VERSIONS = new String[] { String.valueOf(13) }; + private static final String[] SUPPORTED_VERSIONS = new String[] {"13"}; @Override diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java index a02b18b82f7a..0dea79819f86 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java @@ -58,8 +58,6 @@ * A base class for {@code RequestUpgradeStrategy} implementations on top of * JSR-356 based servers which include Tyrus as their WebSocket engine. * - *

    Works with Tyrus 1.11 (WebLogic 12.2.1) and Tyrus 1.12 (GlassFish 4.1.1). - * * @author Rossen Stoyanchev * @author Brian Clozel * @author Juergen Hoeller @@ -68,6 +66,10 @@ */ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy { + private static final String[] SUPPORTED_VERSIONS = + StringUtils.tokenizeToStringArray(Version.getSupportedWireProtocolVersions(), ","); + + private static final Random random = new Random(); private static final Constructor constructor; @@ -111,7 +113,7 @@ private static Constructor getEndpointConstructor() { @Override public String[] getSupportedVersions() { - return StringUtils.tokenizeToStringArray(Version.getSupportedWireProtocolVersions(), ","); + return SUPPORTED_VERSIONS; } @Override diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServerEndpointExporter.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServerEndpointExporter.java index ab0ebbbd2f4d..5c6b381eef0b 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServerEndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServerEndpointExporter.java @@ -37,7 +37,7 @@ /** * Detects beans of type {@link jakarta.websocket.server.ServerEndpointConfig} and registers - * with the standard Java WebSocket runtime. Also detects beans annotated with + * with the standard Jakarta WebSocket runtime. Also detects beans annotated with * {@link ServerEndpoint} and registers them as well. Although not required, it is likely * annotated endpoints should have their {@code configurator} property set to * {@link SpringConfigurator}. diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServerEndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServerEndpointRegistration.java index bc42f3dc883d..382a31fdf47f 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServerEndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/ServerEndpointRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -38,7 +38,7 @@ /** * An implementation of {@link jakarta.websocket.server.ServerEndpointConfig} for use in * Spring-based applications. A {@link ServerEndpointRegistration} bean is detected by - * {@link ServerEndpointExporter} and registered with a Java WebSocket runtime at startup. + * {@link ServerEndpointExporter} and registered with a Jakarta WebSocket runtime at startup. * *

    Class constructors accept a singleton {@link jakarta.websocket.Endpoint} instance * or an Endpoint specified by type {@link Class}. When specified by type, the endpoint diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/StandardWebSocketUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/StandardWebSocketUpgradeStrategy.java index 0ac70abe408c..9cb810a0ce46 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/StandardWebSocketUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/StandardWebSocketUpgradeStrategy.java @@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.websocket.Endpoint; import jakarta.websocket.Extension; +import jakarta.websocket.server.ServerEndpointConfig; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; @@ -33,24 +34,32 @@ /** * A WebSocket {@code RequestUpgradeStrategy} for the Jakarta WebSocket API 2.1+. * + *

    This strategy serves as a fallback if no specific server has been detected. + * It can also be used with Jakarta EE 10 level servers such as Tomcat 10.1 and + * Undertow 2.3 directly, relying on their built-in Jakarta WebSocket 2.1 support. + * *

    To modify properties of the underlying {@link jakarta.websocket.server.ServerContainer} * you can use {@link ServletServerContainerFactoryBean} in XML configuration or, * when using Java configuration, access the container instance through the * "jakarta.websocket.server.ServerContainer" ServletContext attribute. * * @author Juergen Hoeller + * @author Rossen Stoyanchev * @since 6.0 * @see jakarta.websocket.server.ServerContainer#upgradeHttpToWebSocket */ public class StandardWebSocketUpgradeStrategy extends AbstractStandardUpgradeStrategy { + private static final String[] SUPPORTED_VERSIONS = new String[] {"13"}; + + @Override public String[] getSupportedVersions() { - return new String[] {"13"}; + return SUPPORTED_VERSIONS; } @Override - public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, + protected void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, @Nullable String selectedProtocol, List selectedExtensions, Endpoint endpoint) throws HandshakeFailureException { @@ -66,7 +75,7 @@ public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse respon endpointConfig.setExtensions(selectedExtensions); try { - getContainer(servletRequest).upgradeHttpToWebSocket(servletRequest, servletResponse, endpointConfig, pathParams); + upgradeHttpToWebSocket(servletRequest, servletResponse, endpointConfig, pathParams); } catch (Exception ex) { throw new HandshakeFailureException( @@ -74,4 +83,10 @@ public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse respon } } + protected void upgradeHttpToWebSocket(HttpServletRequest request, HttpServletResponse response, + ServerEndpointConfig endpointConfig, Map pathParams) throws Exception { + + getContainer(request).upgradeHttpToWebSocket(request, response, endpointConfig, pathParams); + } + } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java index 2344cf660c38..714918c07a16 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java @@ -16,24 +16,16 @@ package org.springframework.web.socket.server.standard; -import java.util.Collections; -import java.util.List; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.websocket.Endpoint; -import jakarta.websocket.Extension; +import jakarta.websocket.server.ServerEndpointConfig; import org.apache.tomcat.websocket.server.WsServerContainer; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.lang.Nullable; -import org.springframework.web.socket.server.HandshakeFailureException; - /** - * A WebSocket {@code RequestUpgradeStrategy} for Apache Tomcat. Compatible with - * Tomcat 10 and higher. + * A WebSocket {@code RequestUpgradeStrategy} for Apache Tomcat. Compatible with Tomcat 10 + * and higher, in particular with Tomcat 10.0 (not based on Jakarta WebSocket 2.1 yet). * *

    To modify properties of the underlying {@link jakarta.websocket.server.ServerContainer} * you can use {@link ServletServerContainerFactoryBean} in XML configuration or, @@ -41,44 +33,18 @@ * "jakarta.websocket.server.ServerContainer" ServletContext attribute. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 4.0 - * @see WsServerContainer#upgradeHttpToWebSocket + * @see org.apache.tomcat.websocket.server.WsServerContainer#upgradeHttpToWebSocket */ -public class TomcatRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy { +public class TomcatRequestUpgradeStrategy extends StandardWebSocketUpgradeStrategy { @Override - public String[] getSupportedVersions() { - return new String[] {"13"}; - } - - @Override - public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - @Nullable String selectedProtocol, List selectedExtensions, Endpoint endpoint) - throws HandshakeFailureException { - - HttpServletRequest servletRequest = getHttpServletRequest(request); - HttpServletResponse servletResponse = getHttpServletResponse(response); + protected void upgradeHttpToWebSocket(HttpServletRequest request, HttpServletResponse response, + ServerEndpointConfig endpointConfig, Map pathParams) throws Exception { - StringBuffer requestUrl = servletRequest.getRequestURL(); - String path = servletRequest.getRequestURI(); // shouldn't matter - Map pathParams = Collections. emptyMap(); - - ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(path, endpoint); - endpointConfig.setSubprotocols(Collections.singletonList(selectedProtocol)); - endpointConfig.setExtensions(selectedExtensions); - - try { - getContainer(servletRequest).upgradeHttpToWebSocket(servletRequest, servletResponse, endpointConfig, pathParams); - } - catch (Exception ex) { - throw new HandshakeFailureException( - "Servlet request failed to upgrade to WebSocket: " + requestUrl, ex); - } - } - - @Override - public WsServerContainer getContainer(HttpServletRequest request) { - return (WsServerContainer) super.getContainer(request); + ((WsServerContainer) getContainer(request)).upgradeHttpToWebSocket( + request, response, endpointConfig, pathParams); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java index 4ee43f18e509..bb20bcafcaf5 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 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,79 +16,41 @@ package org.springframework.web.socket.server.standard; -import java.io.IOException; -import java.util.Collections; -import java.util.List; import java.util.Map; -import io.undertow.websockets.core.WebSocketVersion; import io.undertow.websockets.jsr.ServerWebSocketContainer; -import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.websocket.Endpoint; -import jakarta.websocket.Extension; - -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.lang.Nullable; -import org.springframework.web.socket.server.HandshakeFailureException; +import jakarta.websocket.server.ServerEndpointConfig; /** * A WebSocket {@code RequestUpgradeStrategy} for WildFly and its underlying * Undertow web server. Also compatible with embedded Undertow usage. * - *

    Requires Undertow 1.3.5+ as of Spring Framework 5.0. + *

    Designed for Undertow 2.2, also compatible with Undertow 2.3 + * (which implements Jakarta WebSocket 2.1 as well). * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 4.0.1 + * @see io.undertow.websockets.jsr.ServerWebSocketContainer#doUpgrade */ -public class UndertowRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy { +public class UndertowRequestUpgradeStrategy extends StandardWebSocketUpgradeStrategy { - private static final String[] VERSIONS = new String[] { - WebSocketVersion.V13.toHttpHeaderValue(), - WebSocketVersion.V08.toHttpHeaderValue(), - WebSocketVersion.V07.toHttpHeaderValue() - }; + private static final String[] SUPPORTED_VERSIONS = new String[] {"13", "8", "7"}; @Override public String[] getSupportedVersions() { - return VERSIONS; + return SUPPORTED_VERSIONS; } @Override - protected void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - @Nullable String selectedProtocol, List selectedExtensions, Endpoint endpoint) - throws HandshakeFailureException { - - HttpServletRequest servletRequest = getHttpServletRequest(request); - HttpServletResponse servletResponse = getHttpServletResponse(response); - - StringBuffer requestUrl = servletRequest.getRequestURL(); - String path = servletRequest.getRequestURI(); // shouldn't matter - Map pathParams = Collections.emptyMap(); - - ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(path, endpoint); - endpointConfig.setSubprotocols(Collections.singletonList(selectedProtocol)); - endpointConfig.setExtensions(selectedExtensions); + protected void upgradeHttpToWebSocket(HttpServletRequest request, HttpServletResponse response, + ServerEndpointConfig endpointConfig, Map pathParams) throws Exception { - try { - getContainer(servletRequest).doUpgrade(servletRequest, servletResponse, endpointConfig, pathParams); - } - catch (ServletException ex) { - throw new HandshakeFailureException( - "Servlet request failed to upgrade to WebSocket: " + requestUrl, ex); - } - catch (IOException ex) { - throw new HandshakeFailureException( - "Response update failed during upgrade to WebSocket: " + requestUrl, ex); - } - } - - @Override - public ServerWebSocketContainer getContainer(HttpServletRequest request) { - return (ServerWebSocketContainer) super.getContainer(request); + ((ServerWebSocketContainer) getContainer(request)).doUpgrade( + request, response, endpointConfig, pathParams); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebSphereRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebSphereRequestUpgradeStrategy.java index 24014d476775..47df6b7c8598 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebSphereRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebSphereRequestUpgradeStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -17,22 +17,13 @@ package org.springframework.web.socket.server.standard; import java.lang.reflect.Method; -import java.util.Collections; -import java.util.List; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.websocket.Endpoint; -import jakarta.websocket.Extension; import jakarta.websocket.server.ServerContainer; import jakarta.websocket.server.ServerEndpointConfig; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.lang.Nullable; -import org.springframework.web.socket.server.HandshakeFailureException; - /** * WebSphere support for upgrading an {@link HttpServletRequest} during a * WebSocket handshake. To modify properties of the underlying @@ -41,12 +32,11 @@ * Java configuration, access the container instance through the * "javax.websocket.server.ServerContainer" ServletContext attribute. * - *

    Tested with WAS Liberty beta (August 2015) for the upcoming 8.5.5.7 release. - * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 4.2.1 */ -public class WebSphereRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy { +public class WebSphereRequestUpgradeStrategy extends StandardWebSocketUpgradeStrategy { private static final Method upgradeMethod; @@ -64,34 +54,11 @@ public class WebSphereRequestUpgradeStrategy extends AbstractStandardUpgradeStra @Override - public String[] getSupportedVersions() { - return new String[] {"13"}; - } - - @Override - public void upgradeInternal(ServerHttpRequest httpRequest, ServerHttpResponse httpResponse, - @Nullable String selectedProtocol, List selectedExtensions, Endpoint endpoint) - throws HandshakeFailureException { - - HttpServletRequest request = getHttpServletRequest(httpRequest); - HttpServletResponse response = getHttpServletResponse(httpResponse); + protected void upgradeHttpToWebSocket(HttpServletRequest request, HttpServletResponse response, + ServerEndpointConfig endpointConfig, Map pathParams) throws Exception { - StringBuffer requestUrl = request.getRequestURL(); - String path = request.getRequestURI(); // shouldn't matter - Map pathParams = Collections. emptyMap(); - - ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(path, endpoint); - endpointConfig.setSubprotocols(Collections.singletonList(selectedProtocol)); - endpointConfig.setExtensions(selectedExtensions); - - try { - ServerContainer container = getContainer(request); - upgradeMethod.invoke(container, request, response, endpointConfig, pathParams); - } - catch (Exception ex) { - throw new HandshakeFailureException( - "Servlet request failed to upgrade to WebSocket for " + requestUrl, ex); - } + ServerContainer container = getContainer(request); + upgradeMethod.invoke(container, request, response, endpointConfig, pathParams); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java index ead6f06852e1..527054eb755f 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java @@ -37,7 +37,6 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.socket.SubProtocolCapable; import org.springframework.web.socket.WebSocketExtension; @@ -47,7 +46,13 @@ import org.springframework.web.socket.server.HandshakeFailureException; import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.server.RequestUpgradeStrategy; +import org.springframework.web.socket.server.jetty.JettyRequestUpgradeStrategy; +import org.springframework.web.socket.server.standard.GlassFishRequestUpgradeStrategy; import org.springframework.web.socket.server.standard.StandardWebSocketUpgradeStrategy; +import org.springframework.web.socket.server.standard.TomcatRequestUpgradeStrategy; +import org.springframework.web.socket.server.standard.UndertowRequestUpgradeStrategy; +import org.springframework.web.socket.server.standard.WebLogicRequestUpgradeStrategy; +import org.springframework.web.socket.server.standard.WebSphereRequestUpgradeStrategy; /** * A base class for {@link HandshakeHandler} implementations, independent of the Servlet API. @@ -129,42 +134,6 @@ protected AbstractHandshakeHandler(RequestUpgradeStrategy requestUpgradeStrategy } - private static RequestUpgradeStrategy initRequestUpgradeStrategy() { - String className; - if (tomcatWsPresent) { - className = "org.springframework.web.socket.server.standard.TomcatRequestUpgradeStrategy"; - } - else if (jettyWsPresent) { - className = "org.springframework.web.socket.server.jetty.JettyRequestUpgradeStrategy"; - } - else if (undertowWsPresent) { - className = "org.springframework.web.socket.server.standard.UndertowRequestUpgradeStrategy"; - } - else if (glassfishWsPresent) { - className = "org.springframework.web.socket.server.standard.GlassFishRequestUpgradeStrategy"; - } - else if (weblogicWsPresent) { - className = "org.springframework.web.socket.server.standard.WebLogicRequestUpgradeStrategy"; - } - else if (websphereWsPresent) { - className = "org.springframework.web.socket.server.standard.WebSphereRequestUpgradeStrategy"; - } - else { - // Let's assume Jakarta WebSocket API 2.1+ - return new StandardWebSocketUpgradeStrategy(); - } - - try { - Class clazz = ClassUtils.forName(className, AbstractHandshakeHandler.class.getClassLoader()); - return (RequestUpgradeStrategy) ReflectionUtils.accessibleConstructor(clazz).newInstance(); - } - catch (Exception ex) { - throw new IllegalStateException( - "Failed to instantiate RequestUpgradeStrategy: " + className, ex); - } - } - - /** * Return the {@link RequestUpgradeStrategy} for WebSocket requests. */ @@ -425,4 +394,45 @@ protected Principal determineUser( return request.getPrincipal(); } + + private static RequestUpgradeStrategy initRequestUpgradeStrategy() { + if (tomcatWsPresent) { + return new TomcatRequestUpgradeStrategy(); + } + else if (jettyWsPresent) { + return new JettyRequestUpgradeStrategy(); + } + else if (undertowWsPresent) { + return new UndertowRequestUpgradeStrategy(); + } + else if (glassfishWsPresent) { + return TyrusStrategyDelegate.forGlassFish(); + } + else if (weblogicWsPresent) { + return TyrusStrategyDelegate.forWebLogic(); + } + else if (websphereWsPresent) { + return new WebSphereRequestUpgradeStrategy(); + } + else { + // Let's assume Jakarta WebSocket API 2.1+ + return new StandardWebSocketUpgradeStrategy(); + } + } + + + /** + * Inner class to avoid a reachable dependency on Tyrus API. + */ + private static class TyrusStrategyDelegate { + + public static RequestUpgradeStrategy forGlassFish() { + return new GlassFishRequestUpgradeStrategy(); + } + + public static RequestUpgradeStrategy forWebLogic() { + return new WebLogicRequestUpgradeStrategy(); + } + } + } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/JettyXhrTransport.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/JettyXhrTransport.java index 8e95e6550a9e..d6a91be662dc 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/JettyXhrTransport.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/JettyXhrTransport.java @@ -20,6 +20,7 @@ import java.net.URI; import java.nio.ByteBuffer; import java.util.Enumeration; +import java.util.Iterator; import java.util.concurrent.CompletableFuture; import org.eclipse.jetty.client.HttpClient; @@ -171,9 +172,9 @@ private static void addHttpHeaders(Request request, HttpHeaders headers) { private static HttpHeaders toHttpHeaders(HttpFields httpFields) { HttpHeaders responseHeaders = new HttpHeaders(); - Enumeration names = httpFields.getFieldNames(); - while (names.hasMoreElements()) { - String name = names.nextElement(); + Iterator names = httpFields.getFieldNamesCollection().iterator(); + while (names.hasNext()) { + String name = names.next(); Enumeration values = httpFields.getValues(name); while (values.hasMoreElements()) { String value = values.nextElement(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/SockJsClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/SockJsClient.java index 0292e6e05563..39d51fc6dace 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/SockJsClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/SockJsClient.java @@ -19,7 +19,6 @@ import java.net.URI; import java.security.Principal; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -67,14 +66,7 @@ public class SockJsClient implements WebSocketClient, Lifecycle { private static final Log logger = LogFactory.getLog(SockJsClient.class); - private static final Set supportedProtocols = new HashSet<>(4); - - static { - supportedProtocols.add("ws"); - supportedProtocols.add("wss"); - supportedProtocols.add("http"); - supportedProtocols.add("https"); - } + private static final Set supportedProtocols = Set.of("ws", "wss", "http", "https"); private final List transports; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java index 9a5e28e7f4d6..d6699ccf94e4 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -29,7 +29,7 @@ import org.springframework.util.Assert; /** - * A Jackson 2.6+ codec for encoding and decoding SockJS messages. + * A Jackson 2.x codec for encoding and decoding SockJS messages. * *

    It customizes Jackson's default properties with the following ones: *

      diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java index 0cda2d6e6ade..6baae4e69706 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java @@ -58,7 +58,7 @@ * path resolution and handling of static SockJS requests (e.g. "/info", "/iframe.html", * etc). Sub-classes must handle session URLs (i.e. transport-specific requests). * - * By default, only same origin requests are allowed. Use {@link #setAllowedOrigins} + *

      By default, only same origin requests are allowed. Use {@link #setAllowedOrigins} * to specify a list of allowed origins (a list containing "*" will allow all origins). * * @author Rossen Stoyanchev @@ -620,6 +620,7 @@ private class IframeHandler implements SockJsRequestHandler { + SockJS iframe

    Pattern 1Pattern 2Result
    {@code null}{@code null}