@ -0,0 +1,10 @@
|
||||
root = true
|
||||
|
||||
[*.{adoc,bat,groovy,html,java,js,jsp,kt,kts,md,properties,py,rb,sh,sql,svg,txt,xml,xsd}]
|
||||
charset = utf-8
|
||||
|
||||
[*.{groovy,java,kt,kts,xml,xsd}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
continuation_indent_size = 8
|
||||
end_of_line = lf
|
@ -0,0 +1,16 @@
|
||||
# Normalize line endings to LF.
|
||||
* text eol=lf
|
||||
|
||||
# Ensure that line endings for multipart files in spring-web are not modified.
|
||||
*.multipart -text
|
||||
|
||||
# Ensure that line endings for DOS batch files are not modified.
|
||||
*.bat -text
|
||||
|
||||
# Ensure the following are treated as binary.
|
||||
*.gif binary
|
||||
*.jar binary
|
||||
*.jpeg binary
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.vsd binary
|
@ -0,0 +1,43 @@
|
||||
*.java.hsp
|
||||
*.sonarj
|
||||
*.sw*
|
||||
.DS_Store
|
||||
.settings
|
||||
.springBeans
|
||||
bin
|
||||
build.sh
|
||||
integration-repo
|
||||
ivy-cache
|
||||
jxl.log
|
||||
jmx.log
|
||||
derby.log
|
||||
spring-test/test-output/
|
||||
.gradle
|
||||
argfile*
|
||||
pom.xml
|
||||
activemq-data/
|
||||
|
||||
classes/
|
||||
/build
|
||||
buildSrc/build
|
||||
/spring-*/build
|
||||
/spring-core/kotlin-coroutines/build
|
||||
/framework-bom/build
|
||||
/integration-tests/build
|
||||
/src/asciidoc/build
|
||||
target/
|
||||
|
||||
# Eclipse artifacts, including WTP generated manifests
|
||||
.classpath
|
||||
.project
|
||||
spring-*/src/main/java/META-INF/MANIFEST.MF
|
||||
|
||||
# IDEA artifacts and output dirs
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea
|
||||
out
|
||||
test-output
|
||||
atlassian-ide-plugin.xml
|
||||
.gradletasknamecache
|
@ -0,0 +1,23 @@
|
||||
Juergen Hoeller <jhoeller@pivotal.io> jhoeller <jhoeller@vmware.com>
|
||||
<jhoeller@pivotal.io> <jhoeller@vmware.com>
|
||||
<jhoeller@pivotal.io> <jhoeller@gopivotal.com>
|
||||
<rstoyanchev@pivotal.io> <rstoyanchev@vmware.com>
|
||||
<rstoyanchev@pivotal.io> <rstoyanchev@gopivotal.com>
|
||||
<pwebb@pivotal.io> <pwebb@vmware.com>
|
||||
<pwebb@pivotal.io> <pwebb@gopivotal.com>
|
||||
<cbeams@pivotal.io> <cbeams@vmware.com>
|
||||
<cbeams@pivotal.io> <cbeams@gopivotal.com>
|
||||
<cbeams@pivotal.io> <cbeams@gmail.com>
|
||||
<apoutsma@pivotal.io> <apoutsma@vmware.com>
|
||||
<apoutsma@pivotal.io> <apoutsma@gopivotal.com>
|
||||
<apoutsma@pivotal.io> <poutsma@mac.com>
|
||||
<ogierke@pivotal.io> <ogierke@vmware.com>
|
||||
<ogierke@pivotal.io> <ogierke@gopivotal.com>
|
||||
<dsyer@pivotal.io> <dsyer@vmware.com>
|
||||
<dsyer@pivotal.io> <dsyer@gopivotal.com>
|
||||
<dsyer@pivotal.io> <david_syer@hotmail.com>
|
||||
<aclement@pivotal.io> <aclement@vmware.com>
|
||||
<aclement@pivotal.io> <aclement@gopivotal.com>
|
||||
<aclement@pivotal.io> <andrew.clement@gmail.com>
|
||||
<dmitry.katsubo@gmail.com> <dmitry.katsubo@gmai.com>
|
||||
Nick Williams <nicholas@nicholaswilliams.net> Nicholas Williams <nicholas@nicholaswilliams.net>
|
@ -0,0 +1,44 @@
|
||||
= Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open
|
||||
and welcoming community, we pledge to respect all people who contribute through reporting
|
||||
issues, posting feature requests, updating documentation, submitting pull requests or
|
||||
patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for
|
||||
everyone, regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments,
|
||||
commits, code, wiki edits, issues, and other contributions that are not aligned to this
|
||||
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
|
||||
that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing this project. Project
|
||||
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
|
||||
from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
|
||||
be reviewed and investigated and will result in a response that is deemed necessary and
|
||||
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
|
||||
with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
|
||||
https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
|
@ -0,0 +1,134 @@
|
||||
# Contributing to the Spring Framework
|
||||
|
||||
First off, thank you for taking the time to contribute! :+1: :tada:
|
||||
|
||||
### Table of Contents
|
||||
|
||||
* [Code of Conduct](#code-of-conduct)
|
||||
* [How to Contribute](#how-to-contribute)
|
||||
* [Discuss](#discuss)
|
||||
* [Create an Issue](#create-an-issue)
|
||||
* [Issue Lifecycle](#issue-lifecycle)
|
||||
* [Submit a Pull Request](#submit-a-pull-request)
|
||||
* [Build from Source](#build-from-source)
|
||||
* [Source Code Style](#source-code-style)
|
||||
* [Reference Docs](#reference-docs)
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc).
|
||||
By participating you are expected to uphold this code.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
### How to Contribute
|
||||
|
||||
#### Discuss
|
||||
|
||||
If you have a question, check Stack Overflow using
|
||||
[this list of tags](https://stackoverflow.com/questions/tagged/spring+or+spring-mvc+or+spring-aop+or+spring-jdbc+or+spring-transactions+or+spring-annotations+or+spring-jms+or+spring-el+or+spring-test+or+spring+or+spring-remoting+or+spring-orm+or+spring-jmx+or+spring-cache+or+spring-webflux?tab=Newest).
|
||||
Find an existing discussion, or start a new one if necessary.
|
||||
|
||||
If you believe there is an issue, search through
|
||||
[existing issues](https://github.com/spring-projects/spring-framework/issues) trying a
|
||||
few different ways to find discussions, past or current, that are related to the issue.
|
||||
Reading those discussions helps you to learn about the issue, and helps us to make a
|
||||
decision.
|
||||
|
||||
|
||||
#### Create an Issue
|
||||
|
||||
Reporting an issue or making a feature request is a great way to contribute. Your feedback
|
||||
and the conversations that result from it provide a continuous flow of ideas. However,
|
||||
before creating a ticket, please take the time to [discuss and research](#discuss) first.
|
||||
|
||||
If creating an issue after a discussion on Stack Overflow, please provide a description
|
||||
in the issue instead of simply referring to Stack Overflow. The issue tracker is an
|
||||
important place of record for design discussions and should be self-sufficient.
|
||||
|
||||
Once you're ready, create an issue on
|
||||
[GitHub](https://github.com/spring-projects/spring-framework/issues).
|
||||
|
||||
#### Issue Lifecycle
|
||||
|
||||
When an issue is first created, it is flagged `waiting-for-triage` waiting for a team
|
||||
member to triage it. Once the issue has been reviewed, the team may ask for further
|
||||
information if needed, and based on the findings, the issue is either assigned a target
|
||||
milestone or is closed with a specific status.
|
||||
|
||||
When a fix is ready, the issue is closed and may still be re-opened until the fix is
|
||||
released. After that the issue will typically no longer be reopened. In rare cases if the
|
||||
issue was not at all fixed, the issue may be re-opened. In most cases however any
|
||||
follow-up reports will need to be created as new issues with a fresh description.
|
||||
|
||||
#### Submit a Pull Request
|
||||
|
||||
1. If you have not previously done so, please sign the
|
||||
[Contributor License Agreement](https://cla.pivotal.io/sign/spring). You will be reminded
|
||||
automatically when you submit the PR.
|
||||
|
||||
1. Should you create an issue first? No, just create the pull request and use the
|
||||
description to provide context and motivation, as you would for an issue. If you want
|
||||
to start a discussion first or have already created an issue, once a pull request is
|
||||
created, we will close the issue as superseded by the pull request, and the discussion
|
||||
about the issue will continue under the pull request.
|
||||
|
||||
1. Always check out the `master` branch and submit pull requests against it
|
||||
(for target version see [settings.gradle](settings.gradle)).
|
||||
Backports to prior versions will be considered on a case-by-case basis and reflected as
|
||||
the fix version in the issue tracker.
|
||||
|
||||
1. Choose the granularity of your commits consciously and squash commits that represent
|
||||
multiple edits or corrections of the same logical change. See
|
||||
[Rewriting History section of Pro Git](https://git-scm.com/book/en/Git-Tools-Rewriting-History)
|
||||
for an overview of streamlining the commit history.
|
||||
|
||||
1. Format commit messages using 55 characters for the subject line, 72 characters per line
|
||||
for the description, followed by the issue fixed, e.g. `Closes gh-22276`. See the
|
||||
[Commit Guidelines section of Pro Git](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines)
|
||||
for best practices around commit messages, and use `git log` to see some examples.
|
||||
|
||||
1. If there is a prior issue, reference the GitHub issue number in the description of the
|
||||
pull request.
|
||||
|
||||
If accepted, your contribution may be heavily modified as needed prior to merging.
|
||||
You will likely retain author attribution for your Git commits granted that the bulk of
|
||||
your changes remain intact. You may also be asked to rework the submission.
|
||||
|
||||
If asked to make corrections, simply push the changes against the same branch, and your
|
||||
pull request will be updated. In other words, you do not need to create a new pull request
|
||||
when asked to make changes.
|
||||
|
||||
#### Participate in Reviews
|
||||
|
||||
Helping to review pull requests is another great way to contribute. Your feedback
|
||||
can help to shape the implementation of new features. When reviewing pull requests,
|
||||
however, please refrain from approving or rejecting a PR unless you are a core
|
||||
committer for the Spring Framework.
|
||||
|
||||
### Build from Source
|
||||
|
||||
See the [Build from Source](https://github.com/spring-projects/spring-framework/wiki/Build-from-Source)
|
||||
wiki page for instructions on how to check out, build, and import the Spring Framework
|
||||
source code into your IDE.
|
||||
|
||||
### Source Code Style
|
||||
|
||||
The wiki pages
|
||||
[Code Style](https://github.com/spring-projects/spring-framework/wiki/Code-Style) and
|
||||
[IntelliJ IDEA Editor Settings](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings)
|
||||
define the source file coding standards we use along with some IDEA editor settings we customize.
|
||||
|
||||
### Reference Docs
|
||||
|
||||
The reference documentation is in the [src/docs/asciidoc](src/docs/asciidoc) directory, in
|
||||
[Asciidoctor](https://asciidoctor.org/) format. For trivial changes, you may be able to browse,
|
||||
edit source files, and submit directly from GitHub.
|
||||
|
||||
When making changes locally, execute `./gradlew asciidoctor` and then browse the result under
|
||||
`build/asciidoc/html5/index.html`.
|
||||
|
||||
Asciidoctor also supports live editing. For more details read
|
||||
[Editing AsciiDoc with Live Preview](https://asciidoctor.org/docs/editing-asciidoc-with-live-preview/).
|
||||
Note that if you choose the
|
||||
[System Monitor](https://asciidoctor.org/docs/editing-asciidoc-with-live-preview/#using-a-system-monitor)
|
||||
option, you can find a Guardfile under `src/docs/asciidoc`.
|
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
@ -0,0 +1,34 @@
|
||||
# <img src="src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build")
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
Spring provides everything required beyond the Java programming language for creating enterprise applications for a wide range of scenarios and architectures. Please read the [Overview](https://docs.spring.io/spring/docs/current/spring-framework-reference/overview.html#spring-introduction) section as reference for a more complete introduction.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
## Access to Binaries
|
||||
|
||||
For access to artifacts or a distribution zip, see the [Spring Framework Artifacts](https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Artifacts) wiki page.
|
||||
|
||||
## Documentation
|
||||
|
||||
The Spring Framework maintains reference documentation ([published](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/) and [source](src/docs/asciidoc)), Github [wiki pages](https://github.com/spring-projects/spring-framework/wiki), and an
|
||||
[API reference](https://docs.spring.io/spring-framework/docs/current/javadoc-api/). There are also [guides and tutorials](https://spring.io/guides) across Spring projects.
|
||||
|
||||
## Micro-Benchmarks
|
||||
|
||||
See the [Micro-Benchmarks](https://github.com/spring-projects/spring-framework/wiki/Micro-Benchmarks) Wiki page.
|
||||
|
||||
## Build from Source
|
||||
|
||||
See the [Build from Source](https://github.com/spring-projects/spring-framework/wiki/Build-from-Source) Wiki page and the [CONTRIBUTING.md](CONTRIBUTING.md) file.
|
||||
|
||||
## Stay in Touch
|
||||
|
||||
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on Twitter. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [news feed](https://spring.io/blog/category/news).
|
||||
|
||||
## License
|
||||
|
||||
The Spring Framework is released under version 2.0 of the [Apache License](https://www.apache.org/licenses/LICENSE-2.0).
|
@ -0,0 +1,11 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please see the
|
||||
[Spring Framework Versions](https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions)
|
||||
wiki page.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please see https://pivotal.io/security.
|
@ -0,0 +1,51 @@
|
||||
== Spring Framework Concourse pipeline
|
||||
|
||||
The Spring Framework is using https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
|
||||
The Spring team has a dedicated Concourse instance available at https://ci.spring.io.
|
||||
|
||||
=== Setting up your development environment
|
||||
|
||||
If you're part of the Spring Framework project on GitHub, you can get access to CI management features.
|
||||
First, you need to go to https://ci.spring.io and install the client CLI for your platform (see bottom right of the screen).
|
||||
|
||||
You can then login with the instance using:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring login -n spring-framework -c https://ci.spring.io
|
||||
----
|
||||
|
||||
Once logged in, you should get something like:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly ts
|
||||
name url team expiry
|
||||
spring https://ci.spring.io spring-framework Wed, 25 Mar 2020 17:45:26 UTC
|
||||
----
|
||||
|
||||
=== Pipeline configuration and structure
|
||||
The build pipelines are described in `pipeline.yml` file.
|
||||
This file is listing Concourse resources, i.e. build inputs and outputs such as container images, artifact repositories, source repositories, notification services, etc.
|
||||
It also describes jobs (a job is a sequence of inputs, tasks and outputs); jobs are organized by groups.
|
||||
|
||||
The `pipeline.yml` definition contains `((parameters))` which are loaded from the `parameters.yml` file or from our https://docs.cloudfoundry.org/credhub/[credhub instance].
|
||||
|
||||
You'll find in this folder the following resources:
|
||||
* `pipeline.yml` the build pipeline
|
||||
* `parameters.yml` the build parameters used for the pipeline
|
||||
* `images/` holds the container images definitions used in this pipeline
|
||||
* `scripts/` holds the build scripts that ship within the CI container images
|
||||
* `tasks` contains the task definitions used in the main `pipeline.yml`
|
||||
|
||||
=== Updating the build pipeline
|
||||
|
||||
Updating files on the repository is not enough to update the build pipeline, as changes need to be applied.
|
||||
The pipeline can be deployed using the following command:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring set-pipeline -p spring-framework-5.3.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
----
|
||||
|
||||
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
|
@ -0,0 +1,17 @@
|
||||
changelog:
|
||||
repository: spring-projects/spring-framework
|
||||
sections:
|
||||
- title: ":star: New Features"
|
||||
labels:
|
||||
- "type: enhancement"
|
||||
- title: ":beetle: Bug Fixes"
|
||||
labels:
|
||||
- "type: bug"
|
||||
- "type: regression"
|
||||
- title: ":notebook_with_decorative_cover: Documentation"
|
||||
labels:
|
||||
- "type: documentation"
|
||||
- title: ":hammer: Dependency Upgrades"
|
||||
sort: "title"
|
||||
labels:
|
||||
- "type: dependency-upgrade"
|
@ -0,0 +1,9 @@
|
||||
logging:
|
||||
level:
|
||||
io.spring.concourse: DEBUG
|
||||
distribute:
|
||||
optional-deployments:
|
||||
- '.*\\.zip'
|
||||
spring:
|
||||
main:
|
||||
banner-mode: off
|
@ -0,0 +1,21 @@
|
||||
== CI Images
|
||||
|
||||
These images are used by CI to run the actual builds.
|
||||
|
||||
To build the image locally run the following from this directory:
|
||||
|
||||
----
|
||||
$ docker build --no-cache -f <image-folder>/Dockerfile .
|
||||
----
|
||||
|
||||
For example
|
||||
|
||||
----
|
||||
$ docker build --no-cache -f spring-framework-ci-image/Dockerfile .
|
||||
----
|
||||
|
||||
To test run:
|
||||
|
||||
----
|
||||
$ docker run -it --entrypoint /bin/bash <SHA>
|
||||
----
|
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
java8)
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u265-b01/OpenJDK8U-jdk_x64_linux_hotspot_8u265b01.tar.gz"
|
||||
;;
|
||||
java11)
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.8%2B10/OpenJDK11U-jdk_x64_linux_hotspot_11.0.8_10.tar.gz"
|
||||
;;
|
||||
java14)
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk14-binaries/releases/download/jdk-14.0.2%2B12/OpenJDK14U-jdk_x64_linux_hotspot_14.0.2_12.tar.gz"
|
||||
;;
|
||||
java15)
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk15-binaries/releases/download/jdk-15%2B36/OpenJDK15U-jdk_x64_linux_hotspot_15_36.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
exit 1
|
||||
esac
|
@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
###########################################################
|
||||
# UTILS
|
||||
###########################################################
|
||||
|
||||
apt-get update
|
||||
apt-get install --no-install-recommends -y ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.3/concourse-java.sh > /opt/concourse-java.sh
|
||||
|
||||
curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/io/spring/concourse/releasescripts/concourse-release-scripts/0.2.1/concourse-release-scripts-0.2.1.jar
|
||||
|
||||
###########################################################
|
||||
# JAVA
|
||||
###########################################################
|
||||
JDK_URL=$( ./get-jdk-url.sh $1 )
|
||||
|
||||
mkdir -p /opt/openjdk
|
||||
cd /opt/openjdk
|
||||
curl -L ${JDK_URL} | tar zx --strip-components=1
|
||||
test -f /opt/openjdk/bin/java
|
||||
test -f /opt/openjdk/bin/javac
|
||||
|
||||
###########################################################
|
||||
# GRADLE ENTERPRISE
|
||||
###########################################################
|
||||
cd /
|
||||
mkdir ~/.gradle
|
||||
echo 'systemProp.user.name=concourse' > ~/.gradle/gradle.properties
|
@ -0,0 +1,8 @@
|
||||
FROM ubuntu:bionic-20200713
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
RUN ./setup.sh java8
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
@ -0,0 +1,8 @@
|
||||
FROM ubuntu:bionic-20200713
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
RUN ./setup.sh java11
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
@ -0,0 +1,8 @@
|
||||
FROM ubuntu:bionic-20200713
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
RUN ./setup.sh java14
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
@ -0,0 +1,8 @@
|
||||
FROM ubuntu:bionic-20200713
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
RUN ./setup.sh java15
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
@ -0,0 +1,14 @@
|
||||
email-server: "smtp.svc.pivotal.io"
|
||||
email-from: "ci@spring.io"
|
||||
email-to: ["spring-framework-dev@pivotal.io"]
|
||||
github-repo: "https://github.com/spring-projects/spring-framework.git"
|
||||
github-repo-name: "spring-projects/spring-framework"
|
||||
docker-hub-organization: "springci"
|
||||
artifactory-server: "https://repo.spring.io"
|
||||
branch: "master"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
bintray-subject: "spring"
|
||||
bintray-repo: "jars"
|
||||
task-timeout: 1h00m
|
@ -0,0 +1,487 @@
|
||||
anchors:
|
||||
artifactory-task-params: &artifactory-task-params
|
||||
ARTIFACTORY_SERVER: ((artifactory-server))
|
||||
ARTIFACTORY_USERNAME: ((artifactory-username))
|
||||
ARTIFACTORY_PASSWORD: ((artifactory-password))
|
||||
bintray-task-params: &bintray-task-params
|
||||
BINTRAY_SUBJECT: ((bintray-subject))
|
||||
BINTRAY_REPO: ((bintray-repo))
|
||||
BINTRAY_USERNAME: ((bintray-username))
|
||||
BINTRAY_API_KEY: ((bintray-api-key))
|
||||
docker-resource-source: &docker-resource-source
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
tag: 5.3.x
|
||||
gradle-enterprise-task-params: &gradle-enterprise-task-params
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle_enterprise_cache_user.username))
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle_enterprise_cache_user.password))
|
||||
slack-fail-params: &slack-fail-params
|
||||
text: >
|
||||
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
|
||||
[$TEXT_FILE_CONTENT]
|
||||
text_file: git-repo/build/build-scan-uri.txt
|
||||
silent: true
|
||||
icon_emoji: ":concourse:"
|
||||
username: concourse-ci
|
||||
sonatype-task-params: &sonatype-task-params
|
||||
SONATYPE_USER_TOKEN: ((sonatype-user-token))
|
||||
SONATYPE_PASSWORD_TOKEN: ((sonatype-user-token-password))
|
||||
changelog-task-params: &changelog-task-params
|
||||
name: generated-changelog/tag
|
||||
tag: generated-changelog/tag
|
||||
body: generated-changelog/changelog.md
|
||||
github-task-params: &github-task-params
|
||||
GITHUB_USERNAME: ((github-username))
|
||||
GITHUB_TOKEN: ((github-ci-release-token))
|
||||
|
||||
resource_types:
|
||||
- name: artifactory-resource
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/artifactory-resource
|
||||
tag: 0.0.12
|
||||
- name: github-status-resource
|
||||
type: registry-image
|
||||
source:
|
||||
repository: dpb587/github-status-resource
|
||||
tag: master
|
||||
- name: slack-notification
|
||||
type: registry-image
|
||||
source:
|
||||
repository: cfcommunity/slack-notification-resource
|
||||
tag: latest
|
||||
|
||||
resources:
|
||||
- name: git-repo
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
uri: ((github-repo))
|
||||
username: ((github-username))
|
||||
password: ((github-password))
|
||||
branch: ((branch))
|
||||
- name: every-morning
|
||||
type: time
|
||||
icon: alarm
|
||||
source:
|
||||
start: 8:00 AM
|
||||
stop: 9:00 AM
|
||||
location: Europe/Vienna
|
||||
- name: ci-images-git-repo
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
uri: ((github-repo))
|
||||
branch: ((branch))
|
||||
paths: ["ci/images/*"]
|
||||
- name: spring-framework-ci-image
|
||||
type: docker-image
|
||||
icon: docker
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-ci-image
|
||||
- name: spring-framework-jdk11-ci-image
|
||||
type: docker-image
|
||||
icon: docker
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-jdk11-ci-image
|
||||
- name: spring-framework-jdk14-ci-image
|
||||
type: docker-image
|
||||
icon: docker
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-jdk14-ci-image
|
||||
- name: spring-framework-jdk15-ci-image
|
||||
type: docker-image
|
||||
icon: docker
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-jdk15-ci-image
|
||||
- name: artifactory-repo
|
||||
type: artifactory-resource
|
||||
icon: package-variant
|
||||
source:
|
||||
uri: ((artifactory-server))
|
||||
username: ((artifactory-username))
|
||||
password: ((artifactory-password))
|
||||
build_name: ((build-name))
|
||||
- name: repo-status-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: build
|
||||
- name: repo-status-jdk11-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk11-build
|
||||
- name: repo-status-jdk14-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk14-build
|
||||
- name: repo-status-jdk15-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk15-build
|
||||
- name: slack-alert
|
||||
type: slack-notification
|
||||
icon: slack
|
||||
source:
|
||||
url: ((slack-webhook-url))
|
||||
- name: github-pre-release
|
||||
type: github-release
|
||||
icon: briefcase-download-outline
|
||||
source:
|
||||
owner: spring-projects
|
||||
repository: spring-framework
|
||||
access_token: ((github-ci-release-token))
|
||||
pre_release: true
|
||||
release: false
|
||||
- name: github-release
|
||||
type: github-release
|
||||
icon: briefcase-download
|
||||
source:
|
||||
owner: spring-projects
|
||||
repository: spring-framework
|
||||
access_token: ((github-ci-release-token))
|
||||
pre_release: false
|
||||
|
||||
jobs:
|
||||
- name: build-spring-framework-ci-images
|
||||
plan:
|
||||
- get: ci-images-git-repo
|
||||
trigger: true
|
||||
- in_parallel:
|
||||
- put: spring-framework-ci-image
|
||||
params:
|
||||
build: ci-images-git-repo/ci/images
|
||||
dockerfile: ci-images-git-repo/ci/images/spring-framework-ci-image/Dockerfile
|
||||
- put: spring-framework-jdk11-ci-image
|
||||
params:
|
||||
build: ci-images-git-repo/ci/images
|
||||
dockerfile: ci-images-git-repo/ci/images/spring-framework-jdk11-ci-image/Dockerfile
|
||||
- put: spring-framework-jdk14-ci-image
|
||||
params:
|
||||
build: ci-images-git-repo/ci/images
|
||||
dockerfile: ci-images-git-repo/ci/images/spring-framework-jdk14-ci-image/Dockerfile
|
||||
- put: spring-framework-jdk15-ci-image
|
||||
params:
|
||||
build: ci-images-git-repo/ci/images
|
||||
dockerfile: ci-images-git-repo/ci/images/spring-framework-jdk15-ci-image/Dockerfile
|
||||
- name: build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: spring-framework-ci-image
|
||||
- get: git-repo
|
||||
trigger: true
|
||||
- put: repo-status-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: build-project
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
image: spring-framework-ci-image
|
||||
file: git-repo/ci/tasks/build-project.yml
|
||||
params:
|
||||
BRANCH: ((branch))
|
||||
<<: *gradle-enterprise-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- put: artifactory-repo
|
||||
params: &artifactory-params
|
||||
repo: libs-snapshot-local
|
||||
folder: distribution-repository
|
||||
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
|
||||
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
|
||||
disable_checksum_uploads: true
|
||||
threads: 8
|
||||
artifact_set:
|
||||
- include:
|
||||
- "/**/spring-*.zip"
|
||||
properties:
|
||||
"zip.name": "spring-framework"
|
||||
"zip.displayname": "Spring Framework"
|
||||
"zip.deployed": "false"
|
||||
- include:
|
||||
- "/**/spring-*-docs.zip"
|
||||
properties:
|
||||
"zip.type": "docs"
|
||||
- include:
|
||||
- "/**/spring-*-dist.zip"
|
||||
properties:
|
||||
"zip.type": "dist"
|
||||
- include:
|
||||
- "/**/spring-*-schema.zip"
|
||||
properties:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- name: jdk11-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: spring-framework-jdk11-ci-image
|
||||
- get: git-repo
|
||||
- get: every-morning
|
||||
trigger: true
|
||||
- put: repo-status-jdk11-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
image: spring-framework-jdk11-ci-image
|
||||
file: git-repo/ci/tasks/check-project.yml
|
||||
params:
|
||||
BRANCH: ((branch))
|
||||
<<: *gradle-enterprise-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk11-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk11-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: jdk14-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: spring-framework-jdk14-ci-image
|
||||
- get: git-repo
|
||||
- get: every-morning
|
||||
trigger: true
|
||||
- put: repo-status-jdk14-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
image: spring-framework-jdk14-ci-image
|
||||
file: git-repo/ci/tasks/check-project.yml
|
||||
params:
|
||||
BRANCH: ((branch))
|
||||
<<: *gradle-enterprise-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk14-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk14-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: jdk15-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: spring-framework-jdk15-ci-image
|
||||
- get: git-repo
|
||||
- get: every-morning
|
||||
trigger: true
|
||||
- put: repo-status-jdk15-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
image: spring-framework-jdk15-ci-image
|
||||
file: git-repo/ci/tasks/check-project.yml
|
||||
params:
|
||||
BRANCH: ((branch))
|
||||
<<: *gradle-enterprise-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk15-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk15-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: stage-milestone
|
||||
serial: true
|
||||
plan:
|
||||
- get: spring-framework-ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: spring-framework-ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
repo: libs-staging-local
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-milestone
|
||||
serial: true
|
||||
plan:
|
||||
- get: spring-framework-ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-milestone]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
image: spring-framework-ci-image
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *artifactory-task-params
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *github-task-params
|
||||
- put: github-pre-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
- name: stage-rc
|
||||
serial: true
|
||||
plan:
|
||||
- get: spring-framework-ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: spring-framework-ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
repo: libs-staging-local
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-rc
|
||||
serial: true
|
||||
plan:
|
||||
- get: spring-framework-ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-rc]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
image: spring-framework-ci-image
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *artifactory-task-params
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *github-task-params
|
||||
- put: github-pre-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
- name: stage-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: spring-framework-ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: spring-framework-ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
repo: libs-staging-local
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: spring-framework-ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-release]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
image: spring-framework-ci-image
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *artifactory-task-params
|
||||
<<: *bintray-task-params
|
||||
- name: sync-to-maven-central
|
||||
serial: true
|
||||
plan:
|
||||
- get: spring-framework-ci-image
|
||||
- get: git-repo
|
||||
- get: artifactory-repo
|
||||
trigger: true
|
||||
passed: [promote-release]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: sync-to-maven-central
|
||||
image: spring-framework-ci-image
|
||||
file: git-repo/ci/tasks/sync-to-maven-central.yml
|
||||
params:
|
||||
<<: *bintray-task-params
|
||||
<<: *sonatype-task-params
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *github-task-params
|
||||
- put: github-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
|
||||
groups:
|
||||
- name: "builds"
|
||||
jobs: ["build", "jdk11-build", "jdk14-build", "jdk15-build"]
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone","promote-rc", "promote-release", "sync-to-maven-central"]
|
||||
- name: "ci-images"
|
||||
jobs: ["build-spring-framework-ci-images"]
|
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
repository=$(pwd)/distribution-repository
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
|
||||
popd > /dev/null
|
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 check
|
||||
popd > /dev/null
|
@ -0,0 +1,2 @@
|
||||
source /opt/concourse-java.sh
|
||||
setup_symlinks
|
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CONFIG_DIR=git-repo/ci/config
|
||||
version=$( cat version/version )
|
||||
|
||||
java -jar /github-changelog-generator.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/changelog-generator.yml \
|
||||
${version} generated-changelog/changelog.md
|
||||
|
||||
echo ${version} > generated-changelog/version
|
||||
echo v${version} > generated-changelog/tag
|
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
CONFIG_DIR=git-repo/ci/config
|
||||
|
||||
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
|
||||
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
|
||||
|
||||
java -jar /opt/concourse-release-scripts.jar promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
|
||||
|
||||
java -jar /opt/concourse-release-scripts.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
|
||||
distribute $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
|
||||
|
||||
echo "Promotion complete"
|
||||
echo $version > version/version
|
@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
repository=$(pwd)/distribution-repository
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
git fetch --tags --all > /dev/null
|
||||
popd > /dev/null
|
||||
|
||||
git clone git-repo stage-git-repo > /dev/null
|
||||
|
||||
pushd stage-git-repo > /dev/null
|
||||
|
||||
snapshotVersion=$( awk -F '=' '$1 == "version" { print $2 }' gradle.properties )
|
||||
if [[ $RELEASE_TYPE = "M" ]]; then
|
||||
stageVersion=$( get_next_milestone_release $snapshotVersion)
|
||||
nextVersion=$snapshotVersion
|
||||
elif [[ $RELEASE_TYPE = "RC" ]]; then
|
||||
stageVersion=$( get_next_rc_release $snapshotVersion)
|
||||
nextVersion=$snapshotVersion
|
||||
elif [[ $RELEASE_TYPE = "RELEASE" ]]; then
|
||||
stageVersion=$( get_next_release $snapshotVersion)
|
||||
nextVersion=$( bump_version_number $snapshotVersion)
|
||||
else
|
||||
echo "Unknown release type $RELEASE_TYPE" >&2; exit 1;
|
||||
fi
|
||||
|
||||
echo "Staging $stageVersion (next version will be $nextVersion)"
|
||||
sed -i "s/version=$snapshotVersion/version=$stageVersion/" gradle.properties
|
||||
|
||||
git config user.name "Spring Buildmaster" > /dev/null
|
||||
git config user.email "buildmaster@springframework.org" > /dev/null
|
||||
git add gradle.properties > /dev/null
|
||||
git commit -m"Release v$stageVersion" > /dev/null
|
||||
git tag -a "v$stageVersion" -m"Release v$stageVersion" > /dev/null
|
||||
|
||||
./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
|
||||
|
||||
git reset --hard HEAD^ > /dev/null
|
||||
if [[ $nextVersion != $snapshotVersion ]]; then
|
||||
echo "Setting next development version (v$nextVersion)"
|
||||
sed -i "s/version=$snapshotVersion/version=$nextVersion/" gradle.properties
|
||||
git add gradle.properties > /dev/null
|
||||
git commit -m"Next development version (v$nextVersion)" > /dev/null
|
||||
fi;
|
||||
|
||||
echo "Staging Complete"
|
||||
|
||||
popd > /dev/null
|
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
|
||||
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
|
||||
java -jar /opt/concourse-release-scripts.jar syncToCentral "RELEASE" $BUILD_INFO_LOCATION || { exit 1; }
|
||||
|
||||
echo "Sync complete"
|
||||
echo $version > version/version
|
@ -0,0 +1,22 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
outputs:
|
||||
- name: distribution-repository
|
||||
- name: git-repo
|
||||
caches:
|
||||
- path: gradle
|
||||
params:
|
||||
BRANCH:
|
||||
CI: true
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
run:
|
||||
path: bash
|
||||
args:
|
||||
- -ec
|
||||
- |
|
||||
${PWD}/git-repo/ci/scripts/build-project.sh
|
@ -0,0 +1,22 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
outputs:
|
||||
- name: distribution-repository
|
||||
- name: git-repo
|
||||
caches:
|
||||
- path: gradle
|
||||
params:
|
||||
BRANCH:
|
||||
CI: true
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
run:
|
||||
path: bash
|
||||
args:
|
||||
- -ec
|
||||
- |
|
||||
${PWD}/git-repo/ci/scripts/check-project.sh
|
@ -0,0 +1,20 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: docker-image
|
||||
source:
|
||||
repository: springio/github-changelog-generator
|
||||
tag: '0.0.4'
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: version
|
||||
outputs:
|
||||
- name: generated-changelog
|
||||
params:
|
||||
GITHUB_ORGANIZATION:
|
||||
GITHUB_REPO:
|
||||
GITHUB_USERNAME:
|
||||
GITHUB_TOKEN:
|
||||
RELEASE_TYPE:
|
||||
run:
|
||||
path: git-repo/ci/scripts/generate-changelog.sh
|
@ -0,0 +1,18 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
outputs:
|
||||
- name: version
|
||||
params:
|
||||
RELEASE_TYPE:
|
||||
ARTIFACTORY_SERVER:
|
||||
ARTIFACTORY_USERNAME:
|
||||
ARTIFACTORY_PASSWORD:
|
||||
BINTRAY_SUBJECT:
|
||||
BINTRAY_REPO:
|
||||
BINTRAY_USERNAME:
|
||||
BINTRAY_API_KEY:
|
||||
run:
|
||||
path: git-repo/ci/scripts/promote-version.sh
|
@ -0,0 +1,17 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
outputs:
|
||||
- name: stage-git-repo
|
||||
- name: distribution-repository
|
||||
params:
|
||||
RELEASE_TYPE:
|
||||
CI: true
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
caches:
|
||||
- path: gradle
|
||||
run:
|
||||
path: git-repo/ci/scripts/stage-version.sh
|
@ -0,0 +1,16 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
outputs:
|
||||
- name: version
|
||||
params:
|
||||
BINTRAY_REPO:
|
||||
BINTRAY_SUBJECT:
|
||||
BINTRAY_USERNAME:
|
||||
BINTRAY_API_KEY:
|
||||
SONATYPE_USER_TOKEN:
|
||||
SONATYPE_PASSWORD_TOKEN:
|
||||
run:
|
||||
path: git-repo/ci/scripts/sync-to-maven-central.sh
|
@ -0,0 +1,5 @@
|
||||
version=5.3.2
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
kotlin.stdlib.default.dependency=false
|
@ -0,0 +1,80 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// This script adds support for the following two JVM system properties
|
||||
// that control the build for alternative JDKs (i.e., a JDK other than
|
||||
// the one used to launch the Gradle process).
|
||||
//
|
||||
// - customJavaHome: absolute path to the alternate JDK installation to
|
||||
// use to compile Java code and execute tests. This system property
|
||||
// is also used in spring-oxm.gradle to determine whether JiBX is
|
||||
// supported.
|
||||
//
|
||||
// - customJavaSourceVersion: Java version supplied to the `--release`
|
||||
// command line flag to control the Java source and target
|
||||
// compatibility version. Supported versions include 9 or higher.
|
||||
// Do not set this system property if Java 8 should be used.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// ./gradlew -DcustomJavaHome=/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home test
|
||||
//
|
||||
// ./gradlew --no-build-cache -DcustomJavaHome=/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home test
|
||||
//
|
||||
// ./gradlew -DcustomJavaHome=/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home -DcustomJavaSourceVersion=14 test
|
||||
//
|
||||
//
|
||||
// Credits: inspired by work from Marc Philipp and Stephane Nicoll
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
// import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile
|
||||
|
||||
def customJavaHome = System.getProperty("customJavaHome")
|
||||
|
||||
if (customJavaHome) {
|
||||
def customJavaHomeDir = new File(customJavaHome)
|
||||
def customJavaSourceVersion = System.getProperty("customJavaSourceVersion")
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
logger.info("Java home for " + it.name + " task in " + project.name + ": " + customJavaHomeDir)
|
||||
options.forkOptions.javaHome = customJavaHomeDir
|
||||
inputs.property("customJavaHome", customJavaHome)
|
||||
if (customJavaSourceVersion) {
|
||||
options.compilerArgs += [ "--release", customJavaSourceVersion]
|
||||
inputs.property("customJavaSourceVersion", customJavaSourceVersion)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(GroovyCompile) {
|
||||
logger.info("Java home for " + it.name + " task in " + project.name + ": " + customJavaHomeDir)
|
||||
options.forkOptions.javaHome = customJavaHomeDir
|
||||
inputs.property("customJavaHome", customJavaHome)
|
||||
if (customJavaSourceVersion) {
|
||||
options.compilerArgs += [ "--release", customJavaSourceVersion]
|
||||
inputs.property("customJavaSourceVersion", customJavaSourceVersion)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
tasks.withType(KotlinJvmCompile) {
|
||||
logger.info("Java home for " + it.name + " task in " + project.name + ": " + customJavaHome)
|
||||
kotlinOptions.jdkHome = customJavaHomeDir
|
||||
inputs.property("customJavaHome", customJavaHome)
|
||||
}
|
||||
*/
|
||||
|
||||
tasks.withType(Test) {
|
||||
def javaExecutable = customJavaHome + "/bin/java"
|
||||
if (OperatingSystem.current().isWindows()) {
|
||||
javaExecutable += ".exe"
|
||||
}
|
||||
logger.info("Java executable for " + it.name + " task in " + project.name + ": " + javaExecutable)
|
||||
executable = javaExecutable
|
||||
inputs.property("customJavaHome", customJavaHome)
|
||||
if (customJavaSourceVersion) {
|
||||
inputs.property("customJavaSourceVersion", customJavaSourceVersion)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,284 @@
|
||||
configurations {
|
||||
asciidoctorExt
|
||||
}
|
||||
|
||||
dependencies {
|
||||
asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.5.0")
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://repo.spring.io/release"
|
||||
mavenContent {
|
||||
includeGroup "io.spring.asciidoctor"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce Javadoc for all Spring Framework modules in "build/docs/javadoc"
|
||||
*/
|
||||
task api(type: Javadoc) {
|
||||
group = "Documentation"
|
||||
description = "Generates aggregated Javadoc API documentation."
|
||||
title = "${rootProject.description} ${version} API"
|
||||
|
||||
dependsOn {
|
||||
moduleProjects.collect {
|
||||
it.tasks.getByName("jar")
|
||||
}
|
||||
}
|
||||
doFirst {
|
||||
classpath = files(
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
project(":spring-aspects").sourceSets.main.output
|
||||
)
|
||||
classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
}
|
||||
|
||||
options {
|
||||
encoding = "UTF-8"
|
||||
memberLevel = JavadocMemberLevel.PROTECTED
|
||||
author = true
|
||||
header = rootProject.description
|
||||
use = true
|
||||
overview = "src/docs/api/overview.html"
|
||||
stylesheetFile = file("src/docs/api/stylesheet.css")
|
||||
splitIndex = true
|
||||
links(project.ext.javadocLinks)
|
||||
addStringOption('Xdoclint:none', '-quiet')
|
||||
if(JavaVersion.current().isJava9Compatible()) {
|
||||
addBooleanOption('html5', true)
|
||||
}
|
||||
}
|
||||
source moduleProjects.collect { project ->
|
||||
project.sourceSets.main.allJava
|
||||
}
|
||||
maxMemory = "1024m"
|
||||
destinationDir = file("$buildDir/docs/javadoc")
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce KDoc for all Spring Framework modules in "build/docs/kdoc"
|
||||
*/
|
||||
dokka {
|
||||
dependsOn {
|
||||
tasks.getByName("api")
|
||||
}
|
||||
|
||||
doFirst {
|
||||
configuration {
|
||||
classpath = moduleProjects.collect { project -> project.jar.outputs.files.getFiles() }.flatten()
|
||||
classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
|
||||
moduleProjects.findAll {
|
||||
it.pluginManager.hasPlugin("kotlin")
|
||||
}.each { project ->
|
||||
def kotlinDirs = project.sourceSets.main.kotlin.srcDirs.collect()
|
||||
kotlinDirs -= project.sourceSets.main.java.srcDirs
|
||||
kotlinDirs.each { dir ->
|
||||
if (dir.exists()) {
|
||||
sourceRoot {
|
||||
path = dir.path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outputFormat = "html"
|
||||
outputDirectory = "$buildDir/docs/kdoc"
|
||||
|
||||
configuration {
|
||||
moduleName = "spring-framework"
|
||||
|
||||
externalDocumentationLink {
|
||||
url = new URL("https://docs.spring.io/spring-framework/docs/$version/javadoc-api/")
|
||||
packageListUrl = new File(buildDir, "docs/javadoc/package-list").toURI().toURL()
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("https://projectreactor.io/docs/core/release/api/")
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("https://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/")
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/")
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url = new URL("https://r2dbc.io/spec/0.8.3.RELEASE/api/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task downloadResources(type: Download) {
|
||||
def version = "0.2.5"
|
||||
src "https://repo.spring.io/release/io/spring/docresources/" +
|
||||
"spring-doc-resources/$version/spring-doc-resources-${version}.zip"
|
||||
dest project.file("$buildDir/docs/spring-doc-resources.zip")
|
||||
onlyIfModified true
|
||||
useETag "all"
|
||||
}
|
||||
|
||||
task extractDocResources(type: Copy, dependsOn: downloadResources) {
|
||||
from project.zipTree(downloadResources.dest);
|
||||
into "$buildDir/docs/spring-docs-resources/"
|
||||
}
|
||||
|
||||
asciidoctorj {
|
||||
version = '2.4.1'
|
||||
fatalWarnings ".*"
|
||||
options doctype: 'book', eruby: 'erubis'
|
||||
attributes([
|
||||
icons: 'font',
|
||||
idprefix: '',
|
||||
idseparator: '-',
|
||||
docinfo: 'shared',
|
||||
revnumber: project.version,
|
||||
sectanchors: '',
|
||||
sectnums: '',
|
||||
'source-highlighter': 'highlight.js',
|
||||
highlightjsdir: 'js/highlight',
|
||||
'highlightjs-theme': 'googlecode',
|
||||
stylesdir: 'css/',
|
||||
stylesheet: 'stylesheet.css',
|
||||
'spring-version': project.version
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the Spring Framework Reference documentation from "src/docs/asciidoc"
|
||||
* in "build/docs/ref-docs/html5".
|
||||
*/
|
||||
asciidoctor {
|
||||
baseDirFollowsSourceDir()
|
||||
configurations 'asciidoctorExt'
|
||||
sources {
|
||||
include '*.adoc'
|
||||
}
|
||||
outputDir "$buildDir/docs/ref-docs/html5"
|
||||
logDocuments = true
|
||||
resources {
|
||||
from(sourceDir) {
|
||||
include 'images/*.png', 'css/**', 'js/**'
|
||||
}
|
||||
from extractDocResources
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the Spring Framework Reference documentation from "src/docs/asciidoc"
|
||||
* in "build/docs/ref-docs/pdf".
|
||||
*/
|
||||
asciidoctorPdf {
|
||||
baseDirFollowsSourceDir()
|
||||
configurations 'asciidoctorExt'
|
||||
sources {
|
||||
include '*.adoc'
|
||||
}
|
||||
outputDir "$buildDir/docs/ref-docs/pdf"
|
||||
logDocuments = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip all docs (API and reference) into a single archive
|
||||
*/
|
||||
task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', 'dokka']) {
|
||||
group = "Distribution"
|
||||
description = "Builds -${archiveClassifier} archive containing api and reference " +
|
||||
"for deployment at https://docs.spring.io/spring-framework/docs."
|
||||
|
||||
archiveBaseName.set("spring-framework")
|
||||
archiveClassifier.set("docs")
|
||||
from("src/dist") {
|
||||
include "changelog.txt"
|
||||
}
|
||||
from (api) {
|
||||
into "javadoc-api"
|
||||
}
|
||||
from ("$asciidoctor.outputDir") {
|
||||
into "reference/html"
|
||||
}
|
||||
from ("$asciidoctorPdf.outputDir") {
|
||||
into "reference/pdf"
|
||||
}
|
||||
from (dokka) {
|
||||
into "kdoc-api"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip all Spring Framework schemas into a single archive
|
||||
*/
|
||||
task schemaZip(type: Zip) {
|
||||
group = "Distribution"
|
||||
archiveBaseName.set("spring-framework")
|
||||
archiveClassifier.set("schema")
|
||||
description = "Builds -${archiveClassifier} archive containing all " +
|
||||
"XSDs for deployment at https://springframework.org/schema."
|
||||
duplicatesStrategy DuplicatesStrategy.EXCLUDE
|
||||
moduleProjects.each { module ->
|
||||
def Properties schemas = new Properties();
|
||||
|
||||
module.sourceSets.main.resources.find {
|
||||
(it.path.endsWith("META-INF/spring.schemas") || it.path.endsWith("META-INF\\spring.schemas"))
|
||||
}?.withInputStream { schemas.load(it) }
|
||||
|
||||
for (def key : schemas.keySet()) {
|
||||
def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1')
|
||||
assert shortName != key
|
||||
File xsdFile = module.sourceSets.main.resources.find {
|
||||
(it.path.endsWith(schemas.get(key)) || it.path.endsWith(schemas.get(key).replaceAll('\\/','\\\\')))
|
||||
}
|
||||
assert xsdFile != null
|
||||
into (shortName) {
|
||||
from xsdFile.path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a distribution zip with everything:
|
||||
* docs, schemas, jars, source jars, javadoc jars
|
||||
*/
|
||||
task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) {
|
||||
group = "Distribution"
|
||||
archiveBaseName.set("spring-framework")
|
||||
archiveClassifier.set("dist")
|
||||
description = "Builds -${archiveClassifier} archive, containing all jars and docs, " +
|
||||
"suitable for community download page."
|
||||
|
||||
ext.baseDir = "spring-framework-${project.version}";
|
||||
|
||||
from("src/docs/dist") {
|
||||
include "readme.txt"
|
||||
include "license.txt"
|
||||
include "notice.txt"
|
||||
into "${baseDir}"
|
||||
expand(copyright: new Date().format("yyyy"), version: project.version)
|
||||
}
|
||||
|
||||
from(zipTree(docsZip.archivePath)) {
|
||||
into "${baseDir}/docs"
|
||||
}
|
||||
|
||||
from(zipTree(schemaZip.archivePath)) {
|
||||
into "${baseDir}/schema"
|
||||
}
|
||||
|
||||
moduleProjects.each { module ->
|
||||
into ("${baseDir}/libs") {
|
||||
from module.jar
|
||||
if (module.tasks.findByPath("sourcesJar")) {
|
||||
from module.sourcesJar
|
||||
}
|
||||
if (module.tasks.findByPath("javadocJar")) {
|
||||
from module.javadocJar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
distZip.mustRunAfter moduleProjects.check
|
@ -0,0 +1,136 @@
|
||||
import org.gradle.plugins.ide.eclipse.model.ProjectDependency
|
||||
import org.gradle.plugins.ide.eclipse.model.SourceFolder
|
||||
|
||||
apply plugin: "eclipse"
|
||||
|
||||
eclipse.jdt {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
|
||||
// Replace classpath entries with project dependencies (GRADLE-1116)
|
||||
// https://issues.gradle.org/browse/GRADLE-1116
|
||||
eclipse.classpath.file.whenMerged { classpath ->
|
||||
def regexp = /.*?\/([^\/]+)\/build\/([^\/]+\/)+(?:main|test)/ // only match those that end in main or test (avoids removing necessary entries like build/classes/jaxb)
|
||||
def projectOutputDependencies = classpath.entries.findAll { entry -> entry.path =~ regexp }
|
||||
projectOutputDependencies.each { entry ->
|
||||
def matcher = (entry.path =~ regexp)
|
||||
if (matcher) {
|
||||
def projectName = matcher[0][1]
|
||||
def path = "/${projectName}"
|
||||
if(!classpath.entries.find { e -> e instanceof ProjectDependency && e.path == path }) {
|
||||
def dependency = new ProjectDependency(path)
|
||||
dependency.exported = true
|
||||
classpath.entries.add(dependency)
|
||||
}
|
||||
classpath.entries.remove(entry)
|
||||
}
|
||||
}
|
||||
classpath.entries.removeAll { entry -> (entry.path =~ /(?!.*?repack.*\.jar).*?\/([^\/]+)\/build\/libs\/[^\/]+\.jar/) }
|
||||
}
|
||||
|
||||
// Use separate main/test outputs (prevents WTP from packaging test classes)
|
||||
eclipse.classpath.defaultOutputDir = file(project.name+"/bin/eclipse")
|
||||
eclipse.classpath.file.beforeMerged { classpath ->
|
||||
classpath.entries.findAll{ it instanceof SourceFolder }.each {
|
||||
if (it.output.startsWith("bin/")) {
|
||||
it.output = null
|
||||
}
|
||||
}
|
||||
}
|
||||
eclipse.classpath.file.whenMerged { classpath ->
|
||||
classpath.entries.findAll{ it instanceof SourceFolder }.each {
|
||||
it.output = "bin/" + it.path.split("/")[1]
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure project dependencies come after 3rd-party libs (SPR-11836)
|
||||
// https://jira.spring.io/browse/SPR-11836
|
||||
eclipse.classpath.file.whenMerged { classpath ->
|
||||
classpath.entries.findAll { it instanceof ProjectDependency }.each {
|
||||
// delete from original position
|
||||
classpath.entries.remove(it)
|
||||
// append to end of classpath
|
||||
classpath.entries.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that test fixture dependencies are handled properly in Gradle 6.7.
|
||||
// Bug fixed in Gradle 6.8: https://github.com/gradle/gradle/issues/14932
|
||||
eclipse.classpath.file.whenMerged {
|
||||
entries.findAll { it instanceof ProjectDependency }.each {
|
||||
it.entryAttributes.remove('without_test_code')
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that JMH sources and resources are treated as test classpath entries
|
||||
// so that they can see test fixtures.
|
||||
// https://github.com/melix/jmh-gradle-plugin/issues/157
|
||||
eclipse.classpath.file.whenMerged {
|
||||
entries.findAll { it.path =~ /src\/jmh\/(java|resources)/ }.each {
|
||||
it.entryAttributes['test'] = 'true'
|
||||
}
|
||||
}
|
||||
|
||||
// Allow projects to be used as WTP modules
|
||||
eclipse.project.natures "org.eclipse.wst.common.project.facet.core.nature"
|
||||
|
||||
// Include project specific settings
|
||||
task eclipseSettings(type: Copy) {
|
||||
from rootProject.files(
|
||||
"src/eclipse/org.eclipse.jdt.ui.prefs",
|
||||
"src/eclipse/org.eclipse.wst.common.project.facet.core.xml")
|
||||
into project.file('.settings/')
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
task eclipseWstComponent(type: Copy) {
|
||||
from rootProject.files(
|
||||
"src/eclipse/org.eclipse.wst.common.component")
|
||||
into project.file('.settings/')
|
||||
expand(deployname: project.name)
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
task eclipseJdtPrepare(type: Copy) {
|
||||
from rootProject.file("src/eclipse/org.eclipse.jdt.core.prefs")
|
||||
into project.file(".settings/")
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
task cleanEclipseJdtUi(type: Delete) {
|
||||
delete project.file(".settings/org.eclipse.jdt.core.prefs")
|
||||
delete project.file(".settings/org.eclipse.jdt.ui.prefs")
|
||||
delete project.file(".settings/org.eclipse.wst.common.component")
|
||||
delete project.file(".settings/org.eclipse.wst.common.project.facet.core.xml")
|
||||
}
|
||||
|
||||
task eclipseBuildship(type: Copy) {
|
||||
from rootProject.files(
|
||||
"src/eclipse/org.eclipse.jdt.ui.prefs",
|
||||
"src/eclipse/org.eclipse.jdt.core.prefs")
|
||||
into project.file('.settings/')
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
tasks["eclipseJdt"].dependsOn(eclipseJdtPrepare)
|
||||
tasks["cleanEclipse"].dependsOn(cleanEclipseJdtUi)
|
||||
tasks["eclipse"].dependsOn(eclipseSettings, eclipseWstComponent)
|
||||
|
||||
|
||||
// Filter 'build' folder
|
||||
eclipse.project.file.withXml {
|
||||
def node = it.asNode()
|
||||
|
||||
def filteredResources = node.get("filteredResources")
|
||||
if(filteredResources) {
|
||||
node.remove(filteredResources)
|
||||
}
|
||||
def filterNode = node.appendNode("filteredResources").appendNode("filter")
|
||||
filterNode.appendNode("id", "1359048889071")
|
||||
filterNode.appendNode("name", "")
|
||||
filterNode.appendNode("type", "30")
|
||||
def matcherNode = filterNode.appendNode("matcher")
|
||||
matcherNode.appendNode("id", "org.eclipse.ui.ide.multiFilter")
|
||||
matcherNode.appendNode("arguments", "1.0-projectRelativePath-matches-false-false-build")
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
apply plugin: "maven-publish"
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
pom {
|
||||
afterEvaluate {
|
||||
name = project.description
|
||||
description = project.description
|
||||
}
|
||||
url = "https://github.com/spring-projects/spring-framework"
|
||||
organization {
|
||||
name = "Spring IO"
|
||||
url = "https://spring.io/projects/spring-framework"
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
name = "Apache License, Version 2.0"
|
||||
url = "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
distribution = "repo"
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = "https://github.com/spring-projects/spring-framework"
|
||||
connection = "scm:git:git://github.com/spring-projects/spring-framework"
|
||||
developerConnection = "scm:git:git://github.com/spring-projects/spring-framework"
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = "jhoeller"
|
||||
name = "Juergen Hoeller"
|
||||
email = "jhoeller@pivotal.io"
|
||||
}
|
||||
}
|
||||
issueManagement {
|
||||
system = "GitHub"
|
||||
url = "https://github.com/spring-projects/spring-framework/issues"
|
||||
}
|
||||
}
|
||||
versionMapping {
|
||||
usage('java-api') {
|
||||
fromResolutionResult()
|
||||
}
|
||||
usage('java-runtime') {
|
||||
fromResolutionResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configureDeploymentRepository(project)
|
||||
|
||||
void configureDeploymentRepository(Project project) {
|
||||
project.plugins.withType(MavenPublishPlugin.class).all {
|
||||
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
|
||||
if (project.hasProperty("deploymentRepository")) {
|
||||
publishing.repositories.maven {
|
||||
it.url = project.property("deploymentRepository")
|
||||
it.name = "deployment"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
apply plugin: 'org.springframework.build.compile'
|
||||
apply plugin: 'org.springframework.build.optional-dependencies'
|
||||
apply plugin: 'me.champeau.gradle.jmh'
|
||||
apply from: "$rootDir/gradle/publications.gradle"
|
||||
|
||||
dependencies {
|
||||
jmh 'org.openjdk.jmh:jmh-core:1.23'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.23'
|
||||
jmh 'net.sf.jopt-simple:jopt-simple:4.6'
|
||||
}
|
||||
jmh {
|
||||
duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest.attributes["Implementation-Title"] = project.name
|
||||
manifest.attributes["Implementation-Version"] = project.version
|
||||
manifest.attributes["Automatic-Module-Name"] = project.name.replace('-', '.') // for Jigsaw
|
||||
manifest.attributes["Created-By"] =
|
||||
"${System.getProperty("java.version")} (${System.getProperty("java.specification.vendor")})"
|
||||
|
||||
from("${rootDir}/src/docs/dist") {
|
||||
include "license.txt"
|
||||
include "notice.txt"
|
||||
into "META-INF"
|
||||
expand(copyright: new Date().format("yyyy"), version: project.version)
|
||||
}
|
||||
}
|
||||
|
||||
normalization {
|
||||
runtimeClasspath {
|
||||
ignore "META-INF/MANIFEST.MF"
|
||||
}
|
||||
}
|
||||
|
||||
javadoc {
|
||||
description = "Generates project-level javadoc for use in -javadoc jar"
|
||||
|
||||
options.encoding = "UTF-8"
|
||||
options.memberLevel = JavadocMemberLevel.PROTECTED
|
||||
options.author = true
|
||||
options.header = project.name
|
||||
options.use = true
|
||||
options.links(project.ext.javadocLinks)
|
||||
options.addStringOption("Xdoclint:none", "-quiet")
|
||||
|
||||
// Suppress warnings due to cross-module @see and @link references.
|
||||
// Note that global 'api' task does display all warnings.
|
||||
logging.captureStandardError LogLevel.INFO
|
||||
logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
archiveClassifier.set("sources")
|
||||
from sourceSets.main.allSource
|
||||
// Don't include or exclude anything explicitly by default. See SPR-12085.
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
archiveClassifier.set("javadoc")
|
||||
from javadoc
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourcesJar
|
||||
artifact javadocJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disable publication of test fixture artifacts.
|
||||
components.java.withVariantsFromConfiguration(configurations.testFixturesApiElements) { skip() }
|
||||
components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { skip() }
|
@ -0,0 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
#distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||
distributionUrl=https\://downloads.gradle-dn.com/distributions/gradle-6.7.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author 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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# 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"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="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
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
@ -0,0 +1,52 @@
|
||||
# Spring Framework - Eclipse/STS Project Import Guide
|
||||
|
||||
This document will guide you through the process of importing the Spring Framework
|
||||
projects into Eclipse or the Spring Tool Suite (_STS_). It is recommended that you
|
||||
have a recent version of Eclipse. As a bare minimum you will need Eclipse with full Java
|
||||
8 support, Eclipse Buildship, the Kotlin plugin, and the Groovy plugin.
|
||||
|
||||
The following instructions have been tested against [STS](https://spring.io/tools) 4.3.2
|
||||
([download](https://github.com/spring-projects/sts4/wiki/Previous-Versions#spring-tools-432-changelog))
|
||||
(based on Eclipse 4.12) with [Eclipse Buildship](https://projects.eclipse.org/projects/tools.buildship).
|
||||
The instructions should work with the latest Eclipse distribution as long as you install
|
||||
[Buildship](https://marketplace.eclipse.org/content/buildship-gradle-integration). Note
|
||||
that STS 4 comes with Buildship preinstalled.
|
||||
|
||||
## Steps
|
||||
|
||||
_When instructed to execute `./gradlew` from the command line, be sure to execute it within your locally cloned `spring-framework` working directory._
|
||||
|
||||
1. Ensure that Eclipse launches with JDK 8.
|
||||
- For example, on Mac OS this can be configured in the `Info.plist` file located in the `Contents` folder of the installed Eclipse or STS application (e.g., the `Eclipse.app` file).
|
||||
1. Install the [Kotlin Plugin for Eclipse](https://marketplace.eclipse.org/content/kotlin-plugin-eclipse) in Eclipse.
|
||||
1. Install the [Eclipse Groovy Development Tools](https://github.com/groovy/groovy-eclipse/wiki) in Eclipse.
|
||||
1. Switch to Groovy 2.5 (Preferences -> Groovy -> Compiler -> Switch to 2.5...) in Eclipse.
|
||||
1. Change the _Forbidden reference (access rule)_ in Eclipse from Error to Warning
|
||||
(Preferences -> Java -> Compiler -> Errors/Warnings -> Deprecated and restricted API -> Forbidden reference (access rule)).
|
||||
1. Optionally install the [AspectJ Development Tools](https://marketplace.eclipse.org/content/aspectj-development-tools) (_AJDT_) if you need to work with the `spring-aspects` project. The AspectJ Development Tools available in the Eclipse Marketplace have been tested with these instructions using STS 4.5 (Eclipse 4.14).
|
||||
1. Optionally install the [TestNG plugin](https://testng.org/doc/eclipse.html) in Eclipse if you need to execute TestNG tests in the `spring-test` module.
|
||||
1. Build `spring-oxm` from the command line with `./gradlew :spring-oxm:check`.
|
||||
1. To apply project specific settings, run `./gradlew eclipseBuildship` from the command line.
|
||||
1. Import into Eclipse (File -> Import -> Gradle -> Existing Gradle Project -> Navigate to the locally cloned `spring-framework` directory -> Select Finish).
|
||||
- If you have not installed AJDT, exclude the `spring-aspects` project from the import, if prompted, or close it after the import.
|
||||
- If you run into errors during the import, you may need to set the _Java home_ for Gradle Buildship to the location of your JDK 8 installation in Eclipse (Preferences -> Gradle -> Java home).
|
||||
1. If you need to execute JAXB-related tests in the `spring-oxm` project and wish to have the generated sources available, add the `build/generated-sources/jaxb` folder to the build path (right click on the `jaxb` folder and select `Build Path -> Use as Source Folder`).
|
||||
- If you do not see the `build` folder in the `spring-oxm` project, ensure that the "Gradle build folder" is not filtered out from the view. This setting is available under "Filters" in the configuration of the Package Explorer (available by clicking on the small downward facing arrow in the upper right corner of the Package Explorer).
|
||||
1. Code away!
|
||||
|
||||
## Known Issues
|
||||
|
||||
1. `spring-core` and `spring-oxm` should be pre-compiled due to repackaged dependencies.
|
||||
- See `*RepackJar` tasks in the build.
|
||||
1. `spring-aspects` does not compile due to references to aspect types unknown to Eclipse.
|
||||
- If you installed _AJDT_ into Eclipse it should work.
|
||||
1. While JUnit tests pass from the command line with Gradle, some may fail when run from
|
||||
the IDE.
|
||||
- Resolving this is a work in progress.
|
||||
- If attempting to run all JUnit tests from within the IDE, you may need to set the following VM options to avoid out of memory errors: `-XX:MaxPermSize=2048m -Xmx2048m -XX:MaxHeapSize=2048m`
|
||||
|
||||
## Tips
|
||||
|
||||
In any case, please do not check in your own generated `.classpath` file, `.project`
|
||||
file, or `.settings` folder. You'll notice these files are already intentionally in
|
||||
`.gitignore`. The same policy holds for IDEA metadata.
|
@ -0,0 +1,36 @@
|
||||
The following has been tested against IntelliJ IDEA 2016.2.2
|
||||
|
||||
## Steps
|
||||
|
||||
_Within your locally cloned spring-framework working directory:_
|
||||
|
||||
1. Precompile `spring-oxm` with `./gradlew :spring-oxm:compileTestJava`
|
||||
2. Import into IntelliJ (File -> New -> Project from Existing Sources -> Navigate to directory -> Select build.gradle)
|
||||
3. When prompted exclude the `spring-aspects` module (or after the import via File-> Project Structure -> Modules)
|
||||
4. Code away
|
||||
|
||||
## Known issues
|
||||
|
||||
1. `spring-core` and `spring-oxm` should be pre-compiled due to repackaged dependencies.
|
||||
See `*RepackJar` tasks in the build and https://youtrack.jetbrains.com/issue/IDEA-160605).
|
||||
2. `spring-aspects` does not compile due to references to aspect types unknown to
|
||||
IntelliJ IDEA. See https://youtrack.jetbrains.com/issue/IDEA-64446 for details. In the meantime, the
|
||||
'spring-aspects' can be excluded from the project to avoid compilation errors.
|
||||
3. While JUnit tests pass from the command line with Gradle, some may fail when run from
|
||||
IntelliJ IDEA. Resolving this is a work in progress. If attempting to run all JUnit tests from within
|
||||
IntelliJ IDEA, you will likely need to set the following VM options to avoid out of memory errors:
|
||||
-XX:MaxPermSize=2048m -Xmx2048m -XX:MaxHeapSize=2048m
|
||||
4. If you invoke "Rebuild Project" in the IDE, you'll have to generate some test
|
||||
resources of the `spring-oxm` module again (`./gradlew :spring-oxm:compileTestJava`)
|
||||
|
||||
|
||||
## Tips
|
||||
|
||||
In any case, please do not check in your own generated .iml, .ipr, or .iws files.
|
||||
You'll notice these files are already intentionally in .gitignore. The same policy goes for eclipse metadata.
|
||||
|
||||
## FAQ
|
||||
|
||||
Q. What about IntelliJ IDEA's own [Gradle support](https://confluence.jetbrains.net/display/IDEADEV/Gradle+integration)?
|
||||
|
||||
A. Keep an eye on https://youtrack.jetbrains.com/issue/IDEA-53476
|
@ -0,0 +1,66 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven { url 'https://maven.aliyun.com/repository/spring-plugin' }
|
||||
// maven { url 'https://repo.spring.io/plugins-release' }
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.gradle.enterprise" version "3.2"
|
||||
// id "io.spring.gradle-enterprise-conventions" version "0.0.2"
|
||||
}
|
||||
|
||||
include "spring-aop"
|
||||
include "spring-aspects"
|
||||
include "spring-beans"
|
||||
include "spring-context"
|
||||
include "spring-context-indexer"
|
||||
include "spring-context-support"
|
||||
include "spring-core"
|
||||
include "kotlin-coroutines"
|
||||
project(':kotlin-coroutines').projectDir = file('spring-core/kotlin-coroutines')
|
||||
include "spring-expression"
|
||||
include "spring-instrument"
|
||||
include "spring-jcl"
|
||||
include "spring-jdbc"
|
||||
include "spring-jms"
|
||||
include "spring-messaging"
|
||||
include "spring-orm"
|
||||
include "spring-oxm"
|
||||
include "spring-r2dbc"
|
||||
include "spring-test"
|
||||
include "spring-tx"
|
||||
include "spring-web"
|
||||
include "spring-webflux"
|
||||
include "spring-webmvc"
|
||||
include "spring-websocket"
|
||||
include "framework-bom"
|
||||
include "integration-tests"
|
||||
|
||||
rootProject.name = "spring"
|
||||
rootProject.children.each {project ->
|
||||
project.buildFileName = "${project.name}.gradle"
|
||||
}
|
||||
|
||||
settings.gradle.projectsLoaded {
|
||||
gradleEnterprise {
|
||||
buildScan {
|
||||
if (settings.gradle.rootProject.hasProperty('customJavaHome')) {
|
||||
value("Custom JAVA_HOME", settings.gradle.rootProject.getProperty('customJavaHome'))
|
||||
}
|
||||
if (settings.gradle.rootProject.hasProperty('customJavaSourceVersion')) {
|
||||
value("Custom Java Source Version", settings.gradle.rootProject.getProperty('customJavaSourceVersion'))
|
||||
}
|
||||
File buildDir = settings.gradle.rootProject.getBuildDir()
|
||||
buildDir.mkdirs()
|
||||
new File(buildDir, "build-scan-uri.txt").text = "(build scan not generated)"
|
||||
buildScanPublished { scan ->
|
||||
if (buildDir.exists()) {
|
||||
new File(buildDir, "build-scan-uri.txt").text = "${scan.buildScanUri}\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE suppressions PUBLIC "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN" "https://checkstyle.org/dtds/suppressions_1_2.dtd">
|
||||
<suppressions>
|
||||
|
||||
<!-- global -->
|
||||
<suppress files="[\\/]src[\\/](test|testFixtures)[\\/]java[\\/]" checks="AnnotationLocation|AnnotationUseStyle|AtclauseOrder|AvoidNestedBlocks|FinalClass|HideUtilityClassConstructor|InnerTypeLast|JavadocStyle|JavadocType|JavadocVariable|LeftCurly|MultipleVariableDeclarations|NeedBraces|OneTopLevelClass|OuterTypeFilename|RequireThis|SpringCatch|SpringJavadoc|SpringNoThis" />
|
||||
<suppress files="[\\/]src[\\/](test|testFixtures)[\\/]java[\\/]org[\\/]springframework[\\/].+(Tests|Suite)" checks="IllegalImport" id="bannedJUnitJupiterImports" />
|
||||
<suppress files="[\\/]src[\\/](test|testFixtures)[\\/]java[\\/]" checks="SpringJUnit5" message="should not be public" />
|
||||
|
||||
<!-- JMH benchmarks -->
|
||||
<suppress files="[\\/]src[\\/]jmh[\\/]java[\\/]org[\\/]springframework[\\/]" checks="JavadocVariable|JavadocStyle|InnerTypeLast" />
|
||||
|
||||
<!-- spring-beans -->
|
||||
<suppress files="TypeMismatchException" checks="MutableException"/>
|
||||
<suppress files="BeanCreationException" checks="MutableException"/>
|
||||
<suppress files="BeanDefinitionParserDelegate" checks="JavadocVariable" />
|
||||
<suppress files="DefaultBeanDefinitionDocumentReader" checks="JavadocVariable" />
|
||||
<suppress files="BeanComponentDefinition" checks="EqualsHashCode" />
|
||||
<suppress files="GenericBeanDefinition" checks="EqualsHashCode" />
|
||||
<suppress files="RootBeanDefinition" checks="EqualsHashCode" />
|
||||
|
||||
<!-- spring-context -->
|
||||
<suppress files="SpringAtInjectTckTests" checks="IllegalImportCheck" id="bannedJUnit3Imports" />
|
||||
|
||||
<!-- spring-core -->
|
||||
<suppress files="[\\/]src[\\/]main[\\/]java[\\/]org[\\/]springframework[\\/]asm[\\/]" checks=".*" />
|
||||
<suppress files="[\\/]src[\\/]main[\\/]java[\\/]org[\\/]springframework[\\/]cglib[\\/]" checks=".*" />
|
||||
<suppress files="ByteArrayEncoder" checks="SpringLambda" />
|
||||
<suppress files="SocketUtils" checks="HideUtilityClassConstructor" />
|
||||
<suppress files="ResolvableType" checks="FinalClass" />
|
||||
<suppress files="[\\/]src[\\/]testFixtures[\\/]java[\\/].+" checks="IllegalImport" id="bannedJUnitJupiterImports" />
|
||||
|
||||
<!-- spring-expression -->
|
||||
<suppress files="ExpressionException" checks="MutableException" />
|
||||
<suppress files="SpelMessage" checks="JavadocVariable|JavadocStyle" />
|
||||
<suppress files="SpelReproTests" checks="InterfaceIsType" />
|
||||
|
||||
<!-- spring-jcl -->
|
||||
<suppress files="[\\/]src[\\/]main[\\/]java[\\/]org[\\/]apache[\\/]commons[\\/]logging[\\/]" checks="Header|SpringNoThis|IllegalImport" />
|
||||
|
||||
<!-- spring-jdbc -->
|
||||
<suppress files="ResultSetWrappingSqlRowSet" checks="JavadocStyle" />
|
||||
|
||||
<!-- spring-jms -->
|
||||
<suppress files="JmsHeaderMapper" checks="InterfaceIsType" />
|
||||
<suppress files="JmsHeaders" checks="InterfaceIsType" />
|
||||
<suppress files="AbstractJmsListenerContainerFactory" checks="JavadocStyle" />
|
||||
<suppress files="DefaultJmsListenerContainerFactory" checks="JavadocStyle" />
|
||||
<suppress files="DefaultJcaListenerContainerFactory" checks="JavadocStyle" />
|
||||
|
||||
<!-- spring-messaging -->
|
||||
<suppress files="SimpMessageHeaderAccessor" checks="JavadocVariable" />
|
||||
<suppress files="SimpMessageType" checks="JavadocVariable" />
|
||||
<suppress files="StompCommand" checks="JavadocVariable" />
|
||||
<suppress files="StompHeaderAccessor" checks="JavadocVariable" />
|
||||
<suppress files="StompHeaders" checks="JavadocVariable" />
|
||||
<suppress files="org[\\/]springframework[\\/]messaging[\\/]handler[\\/]annotation[\\/]ValueConstants" checks="InterfaceIsType" />
|
||||
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]messaging[\\/]protobuf[\\/].*" checks=".*" />
|
||||
|
||||
<!-- spring-orm -->
|
||||
<suppress files="jpa[\\/]vendor[\\/]Database" checks="JavadocVariable|JavadocStyle"/>
|
||||
|
||||
<!-- spring-tx -->
|
||||
<suppress files="TransactionSystemException" checks="MutableException" />
|
||||
<suppress files="TransactionTemplate" checks="EqualsHashCode" />
|
||||
|
||||
<!-- spring-test - main and test -->
|
||||
<suppress files="org[\\/]springframework[\\/]test[\\/]context[\\/]junit4[\\/].+" checks="IllegalImport" id="bannedJUnit4Imports" />
|
||||
<suppress files="org[\\/]springframework[\\/]test[\\/]context[\\/]junit[\\/]jupiter[\\/].+" checks="IllegalImport" id="bannedJUnitJupiterImports" />
|
||||
<suppress files="org[\\/]springframework[\\/]test[\\/]context[\\/]testng[\\/].+" checks="IllegalImport" id="bannedTestNGImports" />
|
||||
<!-- spring-test - main -->
|
||||
<suppress files="src[\\/]main[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]util[\\/].+Helper" checks="IllegalImport" id="bannedHamcrestImports" />
|
||||
<suppress files="src[\\/]main[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]web[\\/]client[\\/]match[\\/].+Matchers" checks="IllegalImport" id="bannedHamcrestImports" />
|
||||
<suppress files="src[\\/]main[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]web[\\/]reactive[\\/]server[\\/].+" checks="IllegalImport" id="bannedHamcrestImports" />
|
||||
<suppress files="src[\\/]main[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]web[\\/]servlet[\\/]result[\\/].+Matchers" checks="IllegalImport" id="bannedHamcrestImports" />
|
||||
<!-- spring-test - test -->
|
||||
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/].+TestNGTests" checks="IllegalImport" id="bannedTestNGImports" />
|
||||
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]context[\\/]junit[\\/]jupiter[\\/]web[\\/].+Tests" checks="IllegalImport" id="bannedHamcrestImports" />
|
||||
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]util[\\/].+Tests" checks="IllegalImport" id="bannedHamcrestImports" />
|
||||
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]web[\\/](client|reactive|servlet)[\\/].+Tests" checks="IllegalImport" id="bannedHamcrestImports" />
|
||||
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]test[\\/]context[\\/]junit4" checks="SpringJUnit5" />
|
||||
<suppress files="ContextHierarchyDirtiesContextTests|ClassLevelDirtiesContextTests|ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests|ContextConfigurationWithPropertiesExtendingPropertiesTests|DirtiesContextInterfaceTests|.+WacTests|JUnit4SpringContextWebTests" checks="SpringJUnit5" />
|
||||
<suppress files=".+TestSuite|ContextHierarchyDirtiesContextTests|ClassLevelDirtiesContextTests|ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests|ContextConfigurationWithPropertiesExtendingPropertiesTests|DirtiesContextInterfaceTests|.+WacTests|JUnit4SpringContextWebTests" checks="IllegalImport" id="bannedJUnit4Imports" />
|
||||
<suppress files="org[\\/]springframework[\\/]test[\\/]context[\\/].+[\\/](ExpectedExceptionSpringRunnerTests|StandardJUnit4FeaturesTests|ProgrammaticTxMgmtTestNGTests)" checks="RegexpSinglelineJava" id="expectedExceptionAnnotation" />
|
||||
|
||||
<!-- spring-web -->
|
||||
<suppress files="SpringHandlerInstantiator" checks="JavadocStyle" />
|
||||
<suppress files="org[\\/]springframework[\\/]http[\\/]HttpMethod" checks="JavadocVariable|JavadocStyle" />
|
||||
<suppress files="org[\\/]springframework[\\/]http[\\/]HttpStatus" checks="JavadocVariable|JavadocStyle" />
|
||||
<suppress files="org[\\/]springframework[\\/]web[\\/]bind[\\/]annotation[\\/]CrossOrigin" checks="JavadocStyle" />
|
||||
<suppress files="org[\\/]springframework[\\/]web[\\/]bind[\\/]annotation[\\/]RequestMethod" checks="JavadocVariable" />
|
||||
<suppress files="org[\\/]springframework[\\/]web[\\/]bind[\\/]annotation[\\/]ValueConstants" checks="InterfaceIsType" />
|
||||
<suppress files="PatternParseException" checks="JavadocVariable" />
|
||||
<suppress files="web[\\/]reactive[\\/]socket[\\/]CloseStatus" checks="JavadocStyle" />
|
||||
|
||||
<!-- spring-webflux -->
|
||||
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]web[\\/]reactive[\\/]resource[\\/]GzipSupport" checks="IllegalImport" id="bannedJUnitJupiterImports" />
|
||||
|
||||
<!-- spring-webmvc -->
|
||||
<suppress files="org[\\/]springframework[\\/]web[\\/]servlet[\\/]tags[\\/]form[\\/].*Tag" checks="JavadocVariable" />
|
||||
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]protobuf[\\/].*" checks=".*" />
|
||||
<suppress files="ExtractingResponseErrorHandlerTests" checks="MutableException" />
|
||||
<suppress files="ServletAnnotationControllerHandlerMethodTests" checks="InterfaceIsType" />
|
||||
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]web[\\/]servlet[\\/]resource[\\/]GzipSupport" checks="IllegalImport" id="bannedJUnitJupiterImports" />
|
||||
|
||||
<!-- spring-websocket -->
|
||||
<suppress files="web[\\/]socket[\\/]CloseStatus" checks="JavadocStyle" />
|
||||
<suppress files="web[\\/]socket[\\/]WebSocketHttpHeaders" checks="JavadocVariable" />
|
||||
<suppress files="sockjs[\\/]frame[\\/]SockJsFrameType" checks="JavadocVariable" />
|
||||
<suppress files="sockjs[\\/]transport[\\/]TransportType" checks="JavadocVariable" />
|
||||
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]web[\\/]reactive[\\/]protobuf[\\/].*" checks=".*" />
|
||||
|
||||
</suppressions>
|
@ -0,0 +1,244 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
<module name="com.puppycrawl.tools.checkstyle.Checker">
|
||||
<!-- Suppressions -->
|
||||
<module name="SuppressionFilter">
|
||||
<property name="file" value="${config_loc}/checkstyle-suppressions.xml"/>
|
||||
</module>
|
||||
|
||||
<!-- Root Checks -->
|
||||
<module name="io.spring.javaformat.checkstyle.check.SpringHeaderCheck">
|
||||
<property name="fileExtensions" value="java" />
|
||||
<property name="headerType" value="apache2"/>
|
||||
<property name="headerCopyrightPattern" value="20\d\d-20\d\d"/>
|
||||
<property name="packageInfoHeaderType" value="none"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck">
|
||||
<property name="lineSeparator" value="lf"/>
|
||||
</module>
|
||||
|
||||
<!-- TreeWalker Checks -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
|
||||
<!-- Annotations -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck">
|
||||
<property name="elementStyle" value="compact" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.annotation.MissingOverrideCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.annotation.PackageAnnotationCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationLocationCheck">
|
||||
<property name="allowSamelineSingleParameterlessAnnotation"
|
||||
value="false" />
|
||||
</module>
|
||||
|
||||
<!-- Block Checks -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheck">
|
||||
<property name="option" value="text" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.blocks.RightCurlyCheck">
|
||||
<property name="option" value="alone" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.blocks.NeedBracesCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.blocks.AvoidNestedBlocksCheck" />
|
||||
|
||||
<!-- Class Design -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.FinalClassCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.InterfaceIsTypeCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.HideUtilityClassConstructorCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.MutableExceptionCheck">
|
||||
<property name="format" value="^.*Exception$" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.InnerTypeLastCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.OneTopLevelClassCheck" />
|
||||
|
||||
<!-- Type Names -->
|
||||
<module name="TypeName">
|
||||
<property name="format" value="^[A-Z][a-zA-Z0-9_]*(?<!Test)$" />
|
||||
<property name="tokens" value="CLASS_DEF" />
|
||||
<message key="name.invalidPattern"
|
||||
value="Class name ''{0}'' must not end with ''Test'' (checked pattern ''{1}'')." />
|
||||
</module>
|
||||
|
||||
<!-- Coding -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.coding.CovariantEqualsCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.coding.EmptyStatementCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.coding.EqualsHashCodeCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.coding.SimplifyBooleanExpressionCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.coding.SimplifyBooleanReturnCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.coding.StringLiteralEqualityCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.coding.NestedForDepthCheck">
|
||||
<property name="max" value="3" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.coding.NestedIfDepthCheck">
|
||||
<property name="max" value="5" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.coding.NestedTryDepthCheck">
|
||||
<property name="max" value="3" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.coding.MultipleVariableDeclarationsCheck" />
|
||||
|
||||
<module name="io.spring.javaformat.checkstyle.filter.RequiresOuterThisFilter" />
|
||||
<module name="io.spring.javaformat.checkstyle.filter.IdentCheckFilter">
|
||||
<property name="names" value="logger" />
|
||||
<module
|
||||
name="com.puppycrawl.tools.checkstyle.checks.coding.RequireThisCheck">
|
||||
<property name="checkMethods" value="false" />
|
||||
<property name="validateOnlyOverlapping" value="false" />
|
||||
</module>
|
||||
</module>
|
||||
<module name="io.spring.javaformat.checkstyle.check.SpringNoThisCheck">
|
||||
<property name="names" value="logger" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.coding.OneStatementPerLineCheck" />
|
||||
|
||||
<!-- Imports -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.RedundantImportCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck">
|
||||
<property name="processJavadoc" value="true" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck">
|
||||
<property name="groups" value="java,javax,*,org.springframework" />
|
||||
<property name="ordered" value="true" />
|
||||
<property name="separated" value="true" />
|
||||
<property name="option" value="bottom" />
|
||||
<property name="sortStaticImportsAlphabetically" value="true" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
|
||||
<property name="id" value="bannedImports"/>
|
||||
<property name="regexp" value="true" />
|
||||
<property name="illegalClasses"
|
||||
value="^reactor\.core\.support\.Assert,^org\.slf4j\.LoggerFactory" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
|
||||
<property name="id" value="bannedJUnit3Imports"/>
|
||||
<property name="regexp" value="true" />
|
||||
<property name="illegalClasses" value="^junit\.framework\..+" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
|
||||
<property name="id" value="bannedJUnit4Imports"/>
|
||||
<property name="regexp" value="true" />
|
||||
<property name="illegalClasses"
|
||||
value="^org\.junit\.(Test|BeforeClass|AfterClass|Before|After|Ignore|FixMethodOrder|Rule|ClassRule|Assert|Assume)$,^org\.junit\.(Assert|Assume)\..+,^org\.junit\.(experimental|internal|matchers|rules|runner|runners|validator)\..+" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
|
||||
<property name="id" value="bannedJUnitJupiterImports"/>
|
||||
<property name="regexp" value="true" />
|
||||
<property name="illegalClasses" value="^org\.junit\.jupiter\..+" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
|
||||
<property name="id" value="bannedTestNGImports"/>
|
||||
<property name="regexp" value="true" />
|
||||
<property name="illegalClasses" value="^org\.testng\..+," />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
|
||||
<property name="id" value="bannedHamcrestImports"/>
|
||||
<property name="regexp" value="true" />
|
||||
<property name="illegalClasses" value="^org\.hamcrest\..+" />
|
||||
</module>
|
||||
|
||||
<!-- Javadoc Comments -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTypeCheck">
|
||||
<property name="scope" value="package"/>
|
||||
<property name="authorFormat" value=".+\s.+"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck">
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingThrowsTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
<property name="allowMissingJavadoc" value="true"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocVariableCheck">
|
||||
<property name="scope" value="public"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocStyleCheck">
|
||||
<property name="checkEmptyJavadoc" value="true"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.javadoc.NonEmptyAtclauseDescriptionCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagContinuationIndentationCheck">
|
||||
<property name="offset" value="0"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.javadoc.AtclauseOrderCheck">
|
||||
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF"/>
|
||||
<property name="tagOrder" value="@author, @since, @param, @see, @version, @serial, @deprecated"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.javadoc.AtclauseOrderCheck">
|
||||
<property name="target" value="METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
||||
<property name="tagOrder" value="@param, @return, @throws, @since, @deprecated, @see"/>
|
||||
</module>
|
||||
|
||||
<!-- Miscellaneous -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.indentation.CommentsIndentationCheck">
|
||||
<property name="tokens" value="BLOCK_COMMENT_BEGIN"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.UpperEllCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.ArrayTypeStyleCheck" />
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.OuterTypeFilenameCheck" />
|
||||
|
||||
<!-- Regexp -->
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
|
||||
<property name="format" value="^\t* +\t*\S" />
|
||||
<property name="message"
|
||||
value="Line has leading space characters; indentation should be performed with tabs only." />
|
||||
<property name="ignoreComments" value="true" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpCheck">
|
||||
<property name="format" value="[ \t]+$" />
|
||||
<property name="illegalPattern" value="true" />
|
||||
<property name="message" value="Trailing whitespace" />
|
||||
</module>
|
||||
<module
|
||||
name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
|
||||
<property name="maximum" value="0" />
|
||||
<property name="format"
|
||||
value="assertThatExceptionOfType\((NullPointerException|IllegalArgumentException|IOException|IllegalStateException)\.class\)" />
|
||||
<property name="message"
|
||||
value="Please use specialized AssertJ assertThat*Exception method." />
|
||||
<property name="ignoreComments" value="true" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
|
||||
<property name="id" value="bddMockito"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="format" value="org\.mockito\.Mockito\.(when|doThrow|doAnswer)" />
|
||||
<property name="message" value="Please use BDDMockito." />
|
||||
<property name="ignoreComments" value="true" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
|
||||
<property name="id" value="expectedExceptionAnnotation"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="format" value="\@Test\(expected" />
|
||||
<property name="message" value="Please use AssertJ assertions." />
|
||||
<property name="ignoreComments" value="true" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
|
||||
<property name="id" value="junit4Assertions"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="format" value="org\.junit\.Assert\.assert" />
|
||||
<property name="message" value="Please use AssertJ assertions." />
|
||||
<property name="ignoreComments" value="true" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
|
||||
<property name="id" value="junitJupiterAssertions"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="format" value="org\.junit\.jupiter\.api\.Assertions\.assert" />
|
||||
<property name="message" value="Please use AssertJ assertions." />
|
||||
<property name="ignoreComments" value="true" />
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
|
||||
<property name="id" value="testNGAssertions"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<!-- should cover org.testng.Assert and org.testng.AssertJUnit -->
|
||||
<property name="format" value="org\.testng\.Assert(JUnit)?\.assert" />
|
||||
<property name="message" value="Please use AssertJ assertions." />
|
||||
<property name="ignoreComments" value="true" />
|
||||
</module>
|
||||
|
||||
<!-- Spring Conventions -->
|
||||
<module name="io.spring.javaformat.checkstyle.check.SpringLambdaCheck">
|
||||
<property name="singleArgumentParentheses" value="false" />
|
||||
</module>
|
||||
<module name="io.spring.javaformat.checkstyle.check.SpringCatchCheck" />
|
||||
<module name="io.spring.javaformat.checkstyle.check.SpringJavadocCheck" />
|
||||
<module name="io.spring.javaformat.checkstyle.check.SpringJUnit5Check" />
|
||||
</module>
|
||||
</module>
|
@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
This is the public API documentation for the <a href="https://github.com/SpringSource/spring-framework" target="_top">Spring Framework</a>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,599 @@
|
||||
/* Javadoc style sheet */
|
||||
/*
|
||||
Overall document style
|
||||
*/
|
||||
|
||||
@import url('resources/fonts/dejavu.css');
|
||||
|
||||
body {
|
||||
background-color:#ffffff;
|
||||
color:#353833;
|
||||
font-family:'DejaVu Sans', Arial, Helvetica, sans-serif;
|
||||
font-size:14px;
|
||||
margin:0;
|
||||
}
|
||||
a:link, a:visited {
|
||||
text-decoration:none;
|
||||
color:#4A6782;
|
||||
}
|
||||
a:hover, a:focus {
|
||||
text-decoration:none;
|
||||
color:#bb7a2a;
|
||||
}
|
||||
a:active {
|
||||
text-decoration:none;
|
||||
color:#4A6782;
|
||||
}
|
||||
a[name] {
|
||||
color:#353833;
|
||||
}
|
||||
a[name]:hover {
|
||||
text-decoration:none;
|
||||
color:#353833;
|
||||
}
|
||||
pre {
|
||||
font-family:'DejaVu Sans Mono', monospace;
|
||||
font-size:14px;
|
||||
}
|
||||
h1 {
|
||||
font-size:20px;
|
||||
}
|
||||
h2 {
|
||||
font-size:18px;
|
||||
}
|
||||
h3 {
|
||||
font-size:16px;
|
||||
font-style:italic;
|
||||
}
|
||||
h4 {
|
||||
font-size:13px;
|
||||
}
|
||||
h5 {
|
||||
font-size:12px;
|
||||
}
|
||||
h6 {
|
||||
font-size:11px;
|
||||
}
|
||||
ul {
|
||||
list-style-type:disc;
|
||||
}
|
||||
code, tt {
|
||||
font-family:'DejaVu Sans Mono', monospace;
|
||||
font-size:14px;
|
||||
padding-top:4px;
|
||||
margin-top:8px;
|
||||
line-height:1.4em;
|
||||
}
|
||||
dt code {
|
||||
font-family:'DejaVu Sans Mono', monospace;
|
||||
font-size:14px;
|
||||
padding-top:4px;
|
||||
}
|
||||
table tr td dt code {
|
||||
font-family:'DejaVu Sans Mono', monospace;
|
||||
font-size:14px;
|
||||
vertical-align:top;
|
||||
padding-top:4px;
|
||||
}
|
||||
sup {
|
||||
font-size:8px;
|
||||
}
|
||||
/*
|
||||
Document title and Copyright styles
|
||||
*/
|
||||
.clear {
|
||||
clear:both;
|
||||
height:0px;
|
||||
overflow:hidden;
|
||||
}
|
||||
.aboutLanguage {
|
||||
float:right;
|
||||
padding:0px 21px;
|
||||
font-size:11px;
|
||||
z-index:200;
|
||||
margin-top:-9px;
|
||||
}
|
||||
.legalCopy {
|
||||
margin-left:.5em;
|
||||
}
|
||||
.bar a, .bar a:link, .bar a:visited, .bar a:active {
|
||||
color:#FFFFFF;
|
||||
text-decoration:none;
|
||||
}
|
||||
.bar a:hover, .bar a:focus {
|
||||
color:#bb7a2a;
|
||||
}
|
||||
.tab {
|
||||
background-color:#0066FF;
|
||||
color:#ffffff;
|
||||
padding:8px;
|
||||
width:5em;
|
||||
font-weight:bold;
|
||||
}
|
||||
/*
|
||||
Navigation bar styles
|
||||
*/
|
||||
.bar {
|
||||
background-color:#4D7A97;
|
||||
color:#FFFFFF;
|
||||
padding:.8em .5em .4em .8em;
|
||||
height:auto;/*height:1.8em;*/
|
||||
font-size:11px;
|
||||
margin:0;
|
||||
}
|
||||
.topNav {
|
||||
background-color:#4D7A97;
|
||||
color:#FFFFFF;
|
||||
float:left;
|
||||
padding:0;
|
||||
width:100%;
|
||||
clear:right;
|
||||
height:2.8em;
|
||||
padding-top:10px;
|
||||
overflow:hidden;
|
||||
font-size:12px;
|
||||
}
|
||||
.bottomNav {
|
||||
margin-top:10px;
|
||||
background-color:#4D7A97;
|
||||
color:#FFFFFF;
|
||||
float:left;
|
||||
padding:0;
|
||||
width:100%;
|
||||
clear:right;
|
||||
height:2.8em;
|
||||
padding-top:10px;
|
||||
overflow:hidden;
|
||||
font-size:12px;
|
||||
}
|
||||
.subNav {
|
||||
background-color:#dee3e9;
|
||||
float:left;
|
||||
width:100%;
|
||||
overflow:hidden;
|
||||
font-size:12px;
|
||||
}
|
||||
.subNav div {
|
||||
clear:left;
|
||||
float:left;
|
||||
padding:0 0 5px 6px;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
ul.navList, ul.subNavList {
|
||||
float:left;
|
||||
margin:0 25px 0 0;
|
||||
padding:0;
|
||||
}
|
||||
ul.navList li{
|
||||
list-style:none;
|
||||
float:left;
|
||||
padding: 5px 6px;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
ul.subNavList li{
|
||||
list-style:none;
|
||||
float:left;
|
||||
}
|
||||
.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited {
|
||||
color:#FFFFFF;
|
||||
text-decoration:none;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
.topNav a:hover, .bottomNav a:hover {
|
||||
text-decoration:none;
|
||||
color:#bb7a2a;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
.navBarCell1Rev {
|
||||
background-color:#F8981D;
|
||||
color:#253441;
|
||||
margin: auto 5px;
|
||||
}
|
||||
.skipNav {
|
||||
position:absolute;
|
||||
top:auto;
|
||||
left:-9999px;
|
||||
overflow:hidden;
|
||||
}
|
||||
/*
|
||||
Page header and footer styles
|
||||
*/
|
||||
.header, .footer {
|
||||
clear:both;
|
||||
margin:0 20px;
|
||||
padding:5px 0 0 0;
|
||||
}
|
||||
.indexHeader {
|
||||
margin:10px;
|
||||
position:relative;
|
||||
}
|
||||
.indexHeader span{
|
||||
margin-right:15px;
|
||||
}
|
||||
.indexHeader h1 {
|
||||
font-size:13px;
|
||||
}
|
||||
.title {
|
||||
color:#2c4557;
|
||||
margin:10px 0;
|
||||
}
|
||||
.subTitle {
|
||||
margin:5px 0 0 0;
|
||||
}
|
||||
.header ul {
|
||||
margin:0 0 15px 0;
|
||||
padding:0;
|
||||
}
|
||||
.footer ul {
|
||||
margin:20px 0 5px 0;
|
||||
}
|
||||
.header ul li, .footer ul li {
|
||||
list-style:none;
|
||||
font-size:13px;
|
||||
}
|
||||
/*
|
||||
Heading styles
|
||||
*/
|
||||
div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 {
|
||||
background-color:#dee3e9;
|
||||
border:1px solid #d0d9e0;
|
||||
margin:0 0 6px -8px;
|
||||
padding:7px 5px;
|
||||
}
|
||||
ul.blockList ul.blockList ul.blockList li.blockList h3 {
|
||||
background-color:#dee3e9;
|
||||
border:1px solid #d0d9e0;
|
||||
margin:0 0 6px -8px;
|
||||
padding:7px 5px;
|
||||
}
|
||||
ul.blockList ul.blockList li.blockList h3 {
|
||||
padding:0;
|
||||
margin:15px 0;
|
||||
}
|
||||
ul.blockList li.blockList h2 {
|
||||
padding:0px 0 20px 0;
|
||||
}
|
||||
/*
|
||||
Page layout container styles
|
||||
*/
|
||||
.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer {
|
||||
clear:both;
|
||||
padding:10px 20px;
|
||||
position:relative;
|
||||
}
|
||||
.indexContainer {
|
||||
margin:10px;
|
||||
position:relative;
|
||||
font-size:12px;
|
||||
}
|
||||
.indexContainer h2 {
|
||||
font-size:13px;
|
||||
padding:0 0 3px 0;
|
||||
}
|
||||
.indexContainer ul {
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
.indexContainer ul li {
|
||||
list-style:none;
|
||||
padding-top:2px;
|
||||
}
|
||||
.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt {
|
||||
font-size:12px;
|
||||
font-weight:bold;
|
||||
margin:10px 0 0 0;
|
||||
color:#4E4E4E;
|
||||
}
|
||||
.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd {
|
||||
margin:5px 0 10px 0px;
|
||||
font-size:14px;
|
||||
font-family:'DejaVu Sans Mono',monospace;
|
||||
}
|
||||
.serializedFormContainer dl.nameValue dt {
|
||||
margin-left:1px;
|
||||
font-size:1.1em;
|
||||
display:inline;
|
||||
font-weight:bold;
|
||||
}
|
||||
.serializedFormContainer dl.nameValue dd {
|
||||
margin:0 0 0 1px;
|
||||
font-size:1.1em;
|
||||
display:inline;
|
||||
}
|
||||
/*
|
||||
List styles
|
||||
*/
|
||||
ul.horizontal li {
|
||||
display:inline;
|
||||
font-size:0.9em;
|
||||
}
|
||||
ul.inheritance {
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
ul.inheritance li {
|
||||
display:inline;
|
||||
list-style:none;
|
||||
}
|
||||
ul.inheritance li ul.inheritance {
|
||||
margin-left:15px;
|
||||
padding-left:15px;
|
||||
padding-top:1px;
|
||||
}
|
||||
ul.blockList, ul.blockListLast {
|
||||
margin:10px 0 10px 0;
|
||||
padding:0;
|
||||
}
|
||||
ul.blockList li.blockList, ul.blockListLast li.blockList {
|
||||
list-style:none;
|
||||
margin-bottom:15px;
|
||||
line-height:1.4;
|
||||
}
|
||||
ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList {
|
||||
padding:0px 20px 5px 10px;
|
||||
border:1px solid #ededed;
|
||||
background-color:#f8f8f8;
|
||||
}
|
||||
ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList {
|
||||
padding:0 0 5px 8px;
|
||||
background-color:#ffffff;
|
||||
border:none;
|
||||
}
|
||||
ul.blockList ul.blockList ul.blockList ul.blockList li.blockList {
|
||||
margin-left:0;
|
||||
padding-left:0;
|
||||
padding-bottom:15px;
|
||||
border:none;
|
||||
}
|
||||
ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast {
|
||||
list-style:none;
|
||||
border-bottom:none;
|
||||
padding-bottom:0;
|
||||
}
|
||||
table tr td dl, table tr td dl dt, table tr td dl dd {
|
||||
margin-top:0;
|
||||
margin-bottom:1px;
|
||||
}
|
||||
/*
|
||||
Table styles
|
||||
*/
|
||||
.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary {
|
||||
width:100%;
|
||||
border-left:1px solid #EEE;
|
||||
border-right:1px solid #EEE;
|
||||
border-bottom:1px solid #EEE;
|
||||
}
|
||||
.overviewSummary, .memberSummary {
|
||||
padding:0px;
|
||||
}
|
||||
.overviewSummary caption, .memberSummary caption, .typeSummary caption,
|
||||
.useSummary caption, .constantsSummary caption, .deprecatedSummary caption {
|
||||
position:relative;
|
||||
text-align:left;
|
||||
background-repeat:no-repeat;
|
||||
color:#253441;
|
||||
font-weight:bold;
|
||||
clear:none;
|
||||
overflow:hidden;
|
||||
padding:0px;
|
||||
padding-top:10px;
|
||||
padding-left:1px;
|
||||
margin:0px;
|
||||
white-space:pre;
|
||||
}
|
||||
.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link,
|
||||
.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link,
|
||||
.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover,
|
||||
.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover,
|
||||
.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active,
|
||||
.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active,
|
||||
.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited,
|
||||
.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited {
|
||||
color:#FFFFFF;
|
||||
}
|
||||
.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span,
|
||||
.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span {
|
||||
white-space:nowrap;
|
||||
padding-top:5px;
|
||||
padding-left:12px;
|
||||
padding-right:12px;
|
||||
padding-bottom:7px;
|
||||
display:inline-block;
|
||||
float:left;
|
||||
background-color:#F8981D;
|
||||
border: none;
|
||||
height:16px;
|
||||
}
|
||||
.memberSummary caption span.activeTableTab span {
|
||||
white-space:nowrap;
|
||||
padding-top:5px;
|
||||
padding-left:12px;
|
||||
padding-right:12px;
|
||||
margin-right:3px;
|
||||
display:inline-block;
|
||||
float:left;
|
||||
background-color:#F8981D;
|
||||
height:16px;
|
||||
}
|
||||
.memberSummary caption span.tableTab span {
|
||||
white-space:nowrap;
|
||||
padding-top:5px;
|
||||
padding-left:12px;
|
||||
padding-right:12px;
|
||||
margin-right:3px;
|
||||
display:inline-block;
|
||||
float:left;
|
||||
background-color:#4D7A97;
|
||||
height:16px;
|
||||
}
|
||||
.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab {
|
||||
padding-top:0px;
|
||||
padding-left:0px;
|
||||
padding-right:0px;
|
||||
background-image:none;
|
||||
float:none;
|
||||
display:inline;
|
||||
}
|
||||
.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd,
|
||||
.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd {
|
||||
display:none;
|
||||
width:5px;
|
||||
position:relative;
|
||||
float:left;
|
||||
background-color:#F8981D;
|
||||
}
|
||||
.memberSummary .activeTableTab .tabEnd {
|
||||
display:none;
|
||||
width:5px;
|
||||
margin-right:3px;
|
||||
position:relative;
|
||||
float:left;
|
||||
background-color:#F8981D;
|
||||
}
|
||||
.memberSummary .tableTab .tabEnd {
|
||||
display:none;
|
||||
width:5px;
|
||||
margin-right:3px;
|
||||
position:relative;
|
||||
background-color:#4D7A97;
|
||||
float:left;
|
||||
|
||||
}
|
||||
.overviewSummary td, .memberSummary td, .typeSummary td,
|
||||
.useSummary td, .constantsSummary td, .deprecatedSummary td {
|
||||
text-align:left;
|
||||
padding:0px 0px 12px 10px;
|
||||
width:100%;
|
||||
}
|
||||
th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th,
|
||||
td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{
|
||||
vertical-align:top;
|
||||
padding-right:0px;
|
||||
padding-top:8px;
|
||||
padding-bottom:3px;
|
||||
}
|
||||
th.colFirst, th.colLast, th.colOne, .constantsSummary th {
|
||||
background:#dee3e9;
|
||||
text-align:left;
|
||||
padding:8px 3px 3px 7px;
|
||||
}
|
||||
td.colFirst, th.colFirst {
|
||||
white-space:nowrap;
|
||||
font-size:13px;
|
||||
}
|
||||
td.colLast, th.colLast {
|
||||
font-size:13px;
|
||||
}
|
||||
td.colOne, th.colOne {
|
||||
font-size:13px;
|
||||
}
|
||||
.overviewSummary td.colFirst, .overviewSummary th.colFirst,
|
||||
.overviewSummary td.colOne, .overviewSummary th.colOne,
|
||||
.memberSummary td.colFirst, .memberSummary th.colFirst,
|
||||
.memberSummary td.colOne, .memberSummary th.colOne,
|
||||
.typeSummary td.colFirst{
|
||||
width:25%;
|
||||
vertical-align:top;
|
||||
}
|
||||
td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover {
|
||||
font-weight:bold;
|
||||
}
|
||||
.tableSubHeadingColor {
|
||||
background-color:#EEEEFF;
|
||||
}
|
||||
.altColor {
|
||||
background-color:#FFFFFF;
|
||||
}
|
||||
.rowColor {
|
||||
background-color:#EEEEEF;
|
||||
}
|
||||
/*
|
||||
Content styles
|
||||
*/
|
||||
.description pre {
|
||||
margin-top:0;
|
||||
}
|
||||
.deprecatedContent {
|
||||
margin:0;
|
||||
padding:10px 0;
|
||||
}
|
||||
.docSummary {
|
||||
padding:0;
|
||||
}
|
||||
|
||||
ul.blockList ul.blockList ul.blockList li.blockList h3 {
|
||||
font-style:normal;
|
||||
}
|
||||
|
||||
div.block {
|
||||
font-size:14px;
|
||||
font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif;
|
||||
}
|
||||
|
||||
td.colLast div {
|
||||
padding-top:0px;
|
||||
}
|
||||
|
||||
|
||||
td.colLast a {
|
||||
padding-bottom:3px;
|
||||
}
|
||||
/*
|
||||
Formatting effect styles
|
||||
*/
|
||||
.sourceLineNo {
|
||||
color:green;
|
||||
padding:0 30px 0 0;
|
||||
}
|
||||
h1.hidden {
|
||||
visibility:hidden;
|
||||
overflow:hidden;
|
||||
font-size:10px;
|
||||
}
|
||||
.block {
|
||||
display:block;
|
||||
margin:3px 10px 2px 0px;
|
||||
color:#474747;
|
||||
}
|
||||
.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink,
|
||||
.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel,
|
||||
.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink {
|
||||
font-weight:bold;
|
||||
}
|
||||
.deprecationComment, .emphasizedPhrase, .interfaceName {
|
||||
font-style:italic;
|
||||
}
|
||||
|
||||
div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase,
|
||||
div.block div.block span.interfaceName {
|
||||
font-style:normal;
|
||||
}
|
||||
|
||||
div.contentContainer ul.blockList li.blockList h2{
|
||||
padding-bottom:0px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Spring
|
||||
*/
|
||||
|
||||
pre.code {
|
||||
background-color: #F8F8F8;
|
||||
border: 1px solid #CCCCCC;
|
||||
border-radius: 3px 3px 3px 3px;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
margin: 4px 20px 2px 0px;
|
||||
}
|
||||
|
||||
pre.code code, pre.code code * {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre.code code, pre.code code * {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
@ -0,0 +1,42 @@
|
||||
[[spring-core]]
|
||||
= Core Technologies
|
||||
:doc-root: https://docs.spring.io
|
||||
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
|
||||
:toc: left
|
||||
:toclevels: 4
|
||||
:tabsize: 4
|
||||
:docinfo1:
|
||||
|
||||
This part of the reference documentation covers all the technologies that are
|
||||
absolutely integral to the Spring Framework.
|
||||
|
||||
Foremost amongst these is the Spring Framework's Inversion of Control (IoC) container.
|
||||
A thorough treatment of the Spring Framework's IoC container is closely followed by
|
||||
comprehensive coverage of Spring's Aspect-Oriented Programming (AOP) technologies.
|
||||
The Spring Framework has its own AOP framework, which is conceptually easy to
|
||||
understand and which successfully addresses the 80% sweet spot of AOP requirements
|
||||
in Java enterprise programming.
|
||||
|
||||
Coverage of Spring's integration with AspectJ (currently the richest -- in terms of
|
||||
features -- and certainly most mature AOP implementation in the Java enterprise space)
|
||||
is also provided.
|
||||
|
||||
include::core/core-beans.adoc[leveloffset=+1]
|
||||
|
||||
include::core/core-resources.adoc[leveloffset=+1]
|
||||
|
||||
include::core/core-validation.adoc[leveloffset=+1]
|
||||
|
||||
include::core/core-expressions.adoc[leveloffset=+1]
|
||||
|
||||
include::core/core-aop.adoc[leveloffset=+1]
|
||||
|
||||
include::core/core-aop-api.adoc[leveloffset=+1]
|
||||
|
||||
include::core/core-null-safety.adoc[leveloffset=+1]
|
||||
|
||||
include::core/core-databuffer-codec.adoc[leveloffset=+1]
|
||||
|
||||
include::core/core-spring-jcl.adoc[leveloffset=+1]
|
||||
|
||||
include::core/core-appendix.adoc[leveloffset=+1]
|
@ -0,0 +1,181 @@
|
||||
[[databuffers]]
|
||||
= Data Buffers and Codecs
|
||||
|
||||
Java NIO provides `ByteBuffer` but many libraries build their own byte buffer API on top,
|
||||
especially for network operations where reusing buffers and/or using direct buffers is
|
||||
beneficial for performance. For example Netty has the `ByteBuf` hierarchy, Undertow uses
|
||||
XNIO, Jetty uses pooled byte buffers with a callback to be released, and so on.
|
||||
The `spring-core` module provides a set of abstractions to work with various byte buffer
|
||||
APIs as follows:
|
||||
|
||||
* <<databuffers-factory>> abstracts the creation of a data buffer.
|
||||
* <<databuffers-buffer>> represents a byte buffer, which may be
|
||||
<<databuffers-buffer-pooled, pooled>>.
|
||||
* <<databuffers-utils>> offers utility methods for data buffers.
|
||||
* <<Codecs>> decode or encode streams data buffer streams into higher level objects.
|
||||
|
||||
|
||||
|
||||
|
||||
[[databuffers-factory]]
|
||||
== `DataBufferFactory`
|
||||
|
||||
`DataBufferFactory` is used to create data buffers in one of two ways:
|
||||
|
||||
. Allocate a new data buffer, optionally specifying capacity upfront, if known, which is
|
||||
more efficient even though implementations of `DataBuffer` can grow and shrink on demand.
|
||||
. Wrap an existing `byte[]` or `java.nio.ByteBuffer`, which decorates the given data with
|
||||
a `DataBuffer` implementation and that does not involve allocation.
|
||||
|
||||
Note that WebFlux applications do not create a `DataBufferFactory` directly but instead
|
||||
access it through the `ServerHttpResponse` or the `ClientHttpRequest` on the client side.
|
||||
The type of factory depends on the underlying client or server, e.g.
|
||||
`NettyDataBufferFactory` for Reactor Netty, `DefaultDataBufferFactory` for others.
|
||||
|
||||
|
||||
|
||||
|
||||
[[databuffers-buffer]]
|
||||
== `DataBuffer`
|
||||
|
||||
The `DataBuffer` interface offers similar operations as `java.nio.ByteBuffer` but also
|
||||
brings a few additional benefits some of which are inspired by the Netty `ByteBuf`.
|
||||
Below is a partial list of benefits:
|
||||
|
||||
* Read and write with independent positions, i.e. not requiring a call to `flip()` to
|
||||
alternate between read and write.
|
||||
* Capacity expanded on demand as with `java.lang.StringBuilder`.
|
||||
* Pooled buffers and reference counting via <<databuffers-buffer-pooled>>.
|
||||
* View a buffer as `java.nio.ByteBuffer`, `InputStream`, or `OutputStream`.
|
||||
* Determine the index, or the last index, for a given byte.
|
||||
|
||||
|
||||
|
||||
|
||||
[[databuffers-buffer-pooled]]
|
||||
== `PooledDataBuffer`
|
||||
|
||||
As explained in the Javadoc for
|
||||
https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html[ByteBuffer],
|
||||
byte buffers can be direct or non-direct. Direct buffers may reside outside the Java heap
|
||||
which eliminates the need for copying for native I/O operations. That makes direct buffers
|
||||
particularly useful for receiving and sending data over a socket, but they're also more
|
||||
expensive to create and release, which leads to the idea of pooling buffers.
|
||||
|
||||
`PooledDataBuffer` is an extension of `DataBuffer` that helps with reference counting which
|
||||
is essential for byte buffer pooling. How does it work? When a `PooledDataBuffer` is
|
||||
allocated the reference count is at 1. Calls to `retain()` increment the count, while
|
||||
calls to `release()` decrement it. As long as the count is above 0, the buffer is
|
||||
guaranteed not to be released. When the count is decreased to 0, the pooled buffer can be
|
||||
released, which in practice could mean the reserved memory for the buffer is returned to
|
||||
the memory pool.
|
||||
|
||||
Note that instead of operating on `PooledDataBuffer` directly, in most cases it's better
|
||||
to use the convenience methods in `DataBufferUtils` that apply release or retain to a
|
||||
`DataBuffer` only if it is an instance of `PooledDataBuffer`.
|
||||
|
||||
|
||||
|
||||
|
||||
[[databuffers-utils]]
|
||||
== `DataBufferUtils`
|
||||
|
||||
`DataBufferUtils` offers a number of utility methods to operate on data buffers:
|
||||
|
||||
* Join a stream of data buffers into a single buffer possibly with zero copy, e.g. via
|
||||
composite buffers, if that's supported by the underlying byte buffer API.
|
||||
* Turn `InputStream` or NIO `Channel` into `Flux<DataBuffer>`, and vice versa a
|
||||
`Publisher<DataBuffer>` into `OutputStream` or NIO `Channel`.
|
||||
* Methods to release or retain a `DataBuffer` if the buffer is an instance of
|
||||
`PooledDataBuffer`.
|
||||
* Skip or take from a stream of bytes until a specific byte count.
|
||||
|
||||
|
||||
|
||||
|
||||
[[codecs]]
|
||||
== Codecs
|
||||
|
||||
The `org.springframework.core.codec` package provides the following strategy interfaces:
|
||||
|
||||
* `Encoder` to encode `Publisher<T>` into a stream of data buffers.
|
||||
* `Decoder` to decode `Publisher<DataBuffer>` into a stream of higher level objects.
|
||||
|
||||
The `spring-core` module provides `byte[]`, `ByteBuffer`, `DataBuffer`, `Resource`, and
|
||||
`String` encoder and decoder implementations. The `spring-web` module adds Jackson JSON,
|
||||
Jackson Smile, JAXB2, Protocol Buffers and other encoders and decoders. See
|
||||
<<web-reactive.adoc#webflux-codecs, Codecs>> in the WebFlux section.
|
||||
|
||||
|
||||
|
||||
|
||||
[[databuffers-using]]
|
||||
== Using `DataBuffer`
|
||||
|
||||
When working with data buffers, special care must be taken to ensure buffers are released
|
||||
since they may be <<databuffers-buffer-pooled, pooled>>. We'll use codecs to illustrate
|
||||
how that works but the concepts apply more generally. Let's see what codecs must do
|
||||
internally to manage data buffers.
|
||||
|
||||
A `Decoder` is the last to read input data buffers, before creating higher level
|
||||
objects, and therefore it must release them as follows:
|
||||
|
||||
. If a `Decoder` simply reads each input buffer and is ready to
|
||||
release it immediately, it can do so via `DataBufferUtils.release(dataBuffer)`.
|
||||
. If a `Decoder` is using `Flux` or `Mono` operators such as `flatMap`, `reduce`, and
|
||||
others that prefetch and cache data items internally, or is using operators such as
|
||||
`filter`, `skip`, and others that leave out items, then
|
||||
`doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)` must be added to the
|
||||
composition chain to ensure such buffers are released prior to being discarded, possibly
|
||||
also as a result an error or cancellation signal.
|
||||
. If a `Decoder` holds on to one or more data buffers in any other way, it must
|
||||
ensure they are released when fully read, or in case an error or cancellation signals that
|
||||
take place before the cached data buffers have been read and released.
|
||||
|
||||
Note that `DataBufferUtils#join` offers a safe and efficient way to aggregate a data
|
||||
buffer stream into a single data buffer. Likewise `skipUntilByteCount` and
|
||||
`takeUntilByteCount` are additional safe methods for decoders to use.
|
||||
|
||||
An `Encoder` allocates data buffers that others must read (and release). So an `Encoder`
|
||||
doesn't have much to do. However an `Encoder` must take care to release a data buffer if
|
||||
a serialization error occurs while populating the buffer with data. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
DataBuffer buffer = factory.allocateBuffer();
|
||||
boolean release = true;
|
||||
try {
|
||||
// serialize and populate buffer..
|
||||
release = false;
|
||||
}
|
||||
finally {
|
||||
if (release) {
|
||||
DataBufferUtils.release(buffer);
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val buffer = factory.allocateBuffer()
|
||||
var release = true
|
||||
try {
|
||||
// serialize and populate buffer..
|
||||
release = false
|
||||
} finally {
|
||||
if (release) {
|
||||
DataBufferUtils.release(buffer)
|
||||
}
|
||||
}
|
||||
return buffer
|
||||
----
|
||||
|
||||
The consumer of an `Encoder` is responsible for releasing the data buffers it receives.
|
||||
In a WebFlux application, the output of the `Encoder` is used to write to the HTTP server
|
||||
response, or to the client HTTP request, in which case releasing the data buffers is the
|
||||
responsibility of the code writing to the server response, or to the client request.
|
||||
|
||||
Note that when running on Netty, there are debugging options for
|
||||
https://github.com/netty/netty/wiki/Reference-counted-objects#troubleshooting-buffer-leaks[troubleshooting buffer leaks].
|
@ -0,0 +1,56 @@
|
||||
[[null-safety]]
|
||||
= Null-safety
|
||||
|
||||
Although Java does not let you express null-safety with its type system, the Spring Framework
|
||||
now provides the following annotations in the `org.springframework.lang` package to let you
|
||||
declare nullability of APIs and fields:
|
||||
|
||||
* {api-spring-framework}/lang/Nullable.html[`@Nullable`]: Annotation to indicate that a
|
||||
specific parameter, return value, or field can be `null`.
|
||||
* {api-spring-framework}/lang/NonNull.html[`@NonNull`]: Annotation to indicate that a specific
|
||||
parameter, return value, or field cannot be `null` (not needed on parameters / return values
|
||||
and fields where `@NonNullApi` and `@NonNullFields` apply, respectively).
|
||||
* {api-spring-framework}/lang/NonNullApi.html[`@NonNullApi`]: Annotation at the package level
|
||||
that declares non-null as the default semantics for parameters and return values.
|
||||
* {api-spring-framework}/lang/NonNullFields.html[`@NonNullFields`]: Annotation at the package
|
||||
level that declares non-null as the default semantics for fields.
|
||||
|
||||
The Spring Framework itself leverages these annotations, but they can also be used in any
|
||||
Spring-based Java project to declare null-safe APIs and optionally null-safe fields.
|
||||
Generic type arguments, varargs and array elements nullability are not supported yet but
|
||||
should be in an upcoming release, see https://jira.spring.io/browse/SPR-15942[SPR-15942]
|
||||
for up-to-date information. Nullability declarations are expected to be fine-tuned between
|
||||
Spring Framework releases, including minor ones. Nullability of types used inside method
|
||||
bodies is outside of the scope of this feature.
|
||||
|
||||
NOTE: Other common libraries such as Reactor and Spring Data provide null-safe APIs that
|
||||
use a similar nullability arrangement, delivering a consistent overall experience for
|
||||
Spring application developers.
|
||||
|
||||
|
||||
|
||||
|
||||
== Use cases
|
||||
|
||||
In addition to providing an explicit declaration for Spring Framework API nullability,
|
||||
these annotations can be used by an IDE (such as IDEA or Eclipse) to provide useful
|
||||
warnings related to null-safety in order to avoid `NullPointerException` at runtime.
|
||||
|
||||
They are also used to make Spring API null-safe in Kotlin projects, since Kotlin natively
|
||||
supports https://kotlinlang.org/docs/reference/null-safety.html[null-safety]. More details
|
||||
are available in the <<languages#kotlin-null-safety, Kotlin support documentation>>.
|
||||
|
||||
|
||||
|
||||
|
||||
== JSR-305 meta-annotations
|
||||
|
||||
Spring annotations are meta-annotated with https://jcp.org/en/jsr/detail?id=305[JSR 305]
|
||||
annotations (a dormant but wide-spread JSR). JSR-305 meta-annotations let tooling vendors
|
||||
like IDEA or Kotlin provide null-safety support in a generic way, without having to
|
||||
hard-code support for Spring annotations.
|
||||
|
||||
It is not necessary nor recommended to add a JSR-305 dependency to the project classpath to
|
||||
take advantage of Spring null-safe API. Only projects such as Spring-based libraries that use
|
||||
null-safety annotations in their codebase should add `com.google.code.findbugs:jsr305:3.0.2`
|
||||
with `compileOnly` Gradle configuration or Maven `provided` scope to avoid compile warnings.
|
@ -0,0 +1,829 @@
|
||||
[[resources]]
|
||||
= Resources
|
||||
|
||||
This chapter covers how Spring handles resources and how you can work with resources in
|
||||
Spring. It includes the following topics:
|
||||
|
||||
* <<resources-introduction>>
|
||||
* <<resources-resource>>
|
||||
* <<resources-implementations>>
|
||||
* <<resources-resourceloader>>
|
||||
* <<resources-resourceloaderaware>>
|
||||
* <<resources-as-dependencies>>
|
||||
* <<resources-app-ctx>>
|
||||
|
||||
|
||||
|
||||
|
||||
[[resources-introduction]]
|
||||
== Introduction
|
||||
|
||||
Java's standard `java.net.URL` class and standard handlers for various URL prefixes,
|
||||
unfortunately, are not quite adequate enough for all access to low-level resources. For
|
||||
example, there is no standardized `URL` implementation that may be used to access a
|
||||
resource that needs to be obtained from the classpath or relative to a
|
||||
`ServletContext`. While it is possible to register new handlers for specialized `URL`
|
||||
prefixes (similar to existing handlers for prefixes such as `http:`), this is generally
|
||||
quite complicated, and the `URL` interface still lacks some desirable functionality,
|
||||
such as a method to check for the existence of the resource being pointed to.
|
||||
|
||||
|
||||
|
||||
|
||||
[[resources-resource]]
|
||||
== The Resource Interface
|
||||
|
||||
Spring's `Resource` interface is meant to be a more capable interface for abstracting
|
||||
access to low-level resources. The following listing shows the `Resource` interface
|
||||
definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public interface Resource extends InputStreamSource {
|
||||
|
||||
boolean exists();
|
||||
|
||||
boolean isOpen();
|
||||
|
||||
URL getURL() throws IOException;
|
||||
|
||||
File getFile() throws IOException;
|
||||
|
||||
Resource createRelative(String relativePath) throws IOException;
|
||||
|
||||
String getFilename();
|
||||
|
||||
String getDescription();
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
interface Resource : InputStreamSource {
|
||||
|
||||
fun exists(): Boolean
|
||||
|
||||
val isOpen: Boolean
|
||||
|
||||
val url: URL
|
||||
|
||||
val file: File
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun createRelative(relativePath: String): Resource
|
||||
|
||||
val filename: String
|
||||
|
||||
val description: String
|
||||
}
|
||||
----
|
||||
|
||||
As the definition of the `Resource` interface shows, it extends the `InputStreamSource`
|
||||
interface. The following listing shows the definition of the `InputStreamSource`
|
||||
interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public interface InputStreamSource {
|
||||
|
||||
InputStream getInputStream() throws IOException;
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
interface InputStreamSource {
|
||||
|
||||
val inputStream: InputStream
|
||||
}
|
||||
----
|
||||
|
||||
Some of the most important methods from the `Resource` interface are:
|
||||
|
||||
* `getInputStream()`: Locates and opens the resource, returning an `InputStream` for
|
||||
reading from the resource. It is expected that each invocation returns a fresh
|
||||
`InputStream`. It is the responsibility of the caller to close the stream.
|
||||
* `exists()`: Returns a `boolean` indicating whether this resource actually exists in
|
||||
physical form.
|
||||
* `isOpen()`: Returns a `boolean` indicating whether this resource represents a handle
|
||||
with an open stream. If `true`, the `InputStream` cannot be read multiple times and
|
||||
must be read once only and then closed to avoid resource leaks. Returns `false` for
|
||||
all usual resource implementations, with the exception of `InputStreamResource`.
|
||||
* `getDescription()`: Returns a description for this resource, to be used for error
|
||||
output when working with the resource. This is often the fully qualified file name or
|
||||
the actual URL of the resource.
|
||||
|
||||
Other methods let you obtain an actual `URL` or `File` object representing the
|
||||
resource (if the underlying implementation is compatible and supports that
|
||||
functionality).
|
||||
|
||||
Spring itself uses the `Resource` abstraction extensively, as an argument type in
|
||||
many method signatures when a resource is needed. Other methods in some Spring APIs
|
||||
(such as the constructors to various `ApplicationContext` implementations) take a
|
||||
`String` which in unadorned or simple form is used to create a `Resource` appropriate to
|
||||
that context implementation or, via special prefixes on the `String` path, let the
|
||||
caller specify that a specific `Resource` implementation must be created and used.
|
||||
|
||||
While the `Resource` interface is used a lot with Spring and by Spring, it is actually
|
||||
very useful to use as a general utility class by itself in your own code, for access to
|
||||
resources, even when your code does not know or care about any other parts of Spring.
|
||||
While this couples your code to Spring, it really only couples it to this small set of
|
||||
utility classes, which serve as a more capable replacement for `URL` and can be
|
||||
considered equivalent to any other library you would use for this purpose.
|
||||
|
||||
NOTE: The `Resource` abstraction does not replace functionality.
|
||||
It wraps it where possible. For example, a `UrlResource` wraps a URL and uses the
|
||||
wrapped `URL` to do its work.
|
||||
|
||||
|
||||
|
||||
|
||||
[[resources-implementations]]
|
||||
== Built-in Resource Implementations
|
||||
|
||||
Spring includes the following `Resource` implementations:
|
||||
|
||||
* <<resources-implementations-urlresource>>
|
||||
* <<resources-implementations-classpathresource>>
|
||||
* <<resources-implementations-filesystemresource>>
|
||||
* <<resources-implementations-servletcontextresource>>
|
||||
* <<resources-implementations-inputstreamresource>>
|
||||
* <<resources-implementations-bytearrayresource>>
|
||||
|
||||
|
||||
|
||||
[[resources-implementations-urlresource]]
|
||||
=== `UrlResource`
|
||||
|
||||
`UrlResource` wraps a `java.net.URL` and can be used to access any object that is
|
||||
normally accessible with a URL, such as files, an HTTP target, an FTP target, and others. All
|
||||
URLs have a standardized `String` representation, such that appropriate standardized
|
||||
prefixes are used to indicate one URL type from another. This includes `file:` for
|
||||
accessing filesystem paths, `http:` for accessing resources through the HTTP protocol,
|
||||
`ftp:` for accessing resources through FTP, and others.
|
||||
|
||||
A `UrlResource` is created by Java code by explicitly using the `UrlResource` constructor
|
||||
but is often created implicitly when you call an API method that takes a `String`
|
||||
argument meant to represent a path. For the latter case, a JavaBeans
|
||||
`PropertyEditor` ultimately decides which type of `Resource` to create. If the path
|
||||
string contains well-known (to it, that is) prefix (such as `classpath:`), it
|
||||
creates an appropriate specialized `Resource` for that prefix. However, if it does not
|
||||
recognize the prefix, it assume the string is a standard URL string and
|
||||
creates a `UrlResource`.
|
||||
|
||||
|
||||
|
||||
[[resources-implementations-classpathresource]]
|
||||
=== `ClassPathResource`
|
||||
|
||||
This class represents a resource that should be obtained from the classpath. It uses
|
||||
either the thread context class loader, a given class loader, or a given class for
|
||||
loading resources.
|
||||
|
||||
This `Resource` implementation supports resolution as `java.io.File` if the class path
|
||||
resource resides in the file system but not for classpath resources that reside in a
|
||||
jar and have not been expanded (by the servlet engine or whatever the environment is)
|
||||
to the filesystem. To address this, the various `Resource` implementations always support
|
||||
resolution as a `java.net.URL`.
|
||||
|
||||
A `ClassPathResource` is created by Java code by explicitly using the `ClassPathResource`
|
||||
constructor but is often created implicitly when you call an API method that takes a
|
||||
`String` argument meant to represent a path. For the latter case, a JavaBeans
|
||||
`PropertyEditor` recognizes the special prefix, `classpath:`, on the string path and
|
||||
creates a `ClassPathResource` in that case.
|
||||
|
||||
|
||||
|
||||
[[resources-implementations-filesystemresource]]
|
||||
=== `FileSystemResource`
|
||||
|
||||
This is a `Resource` implementation for `java.io.File` and `java.nio.file.Path` handles.
|
||||
It supports resolution as a `File` and as a `URL`.
|
||||
|
||||
|
||||
|
||||
[[resources-implementations-servletcontextresource]]
|
||||
=== `ServletContextResource`
|
||||
|
||||
This is a `Resource` implementation for `ServletContext` resources that interprets
|
||||
relative paths within the relevant web application's root directory.
|
||||
|
||||
It always supports stream access and URL access but allows `java.io.File` access only
|
||||
when the web application archive is expanded and the resource is physically on the
|
||||
filesystem. Whether or not it is expanded and on the filesystem or accessed
|
||||
directly from the JAR or somewhere else like a database (which is conceivable) is actually
|
||||
dependent on the Servlet container.
|
||||
|
||||
|
||||
|
||||
[[resources-implementations-inputstreamresource]]
|
||||
=== `InputStreamResource`
|
||||
|
||||
An `InputStreamResource` is a `Resource` implementation for a given `InputStream`. It should be used only if no
|
||||
specific `Resource` implementation is applicable. In particular, prefer
|
||||
`ByteArrayResource` or any of the file-based `Resource` implementations where possible.
|
||||
|
||||
In contrast to other `Resource` implementations, this is a descriptor for an already-opened
|
||||
resource. Therefore, it returns `true` from `isOpen()`. Do not use it if you need
|
||||
to keep the resource descriptor somewhere or if you need to read a stream multiple
|
||||
times.
|
||||
|
||||
|
||||
|
||||
[[resources-implementations-bytearrayresource]]
|
||||
=== `ByteArrayResource`
|
||||
|
||||
This is a `Resource` implementation for a given byte array. It creates a
|
||||
`ByteArrayInputStream` for the given byte array.
|
||||
|
||||
It is useful for loading content from any given byte array without having to resort to a
|
||||
single-use `InputStreamResource`.
|
||||
|
||||
|
||||
|
||||
[[resources-resourceloader]]
|
||||
== The `ResourceLoader`
|
||||
|
||||
The `ResourceLoader` interface is meant to be implemented by objects that can return
|
||||
(that is, load) `Resource` instances. The following listing shows the `ResourceLoader`
|
||||
interface definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public interface ResourceLoader {
|
||||
|
||||
Resource getResource(String location);
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
interface ResourceLoader {
|
||||
|
||||
fun getResource(location: String): Resource
|
||||
}
|
||||
----
|
||||
|
||||
All application contexts implement the `ResourceLoader` interface. Therefore, all
|
||||
application contexts may be used to obtain `Resource` instances.
|
||||
|
||||
When you call `getResource()` on a specific application context, and the location path
|
||||
specified doesn't have a specific prefix, you get back a `Resource` type that is
|
||||
appropriate to that particular application context. For example, assume the following
|
||||
snippet of code was run against a `ClassPathXmlApplicationContext` instance:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val template = ctx.getResource("some/resource/path/myTemplate.txt")
|
||||
----
|
||||
|
||||
Against a `ClassPathXmlApplicationContext`, that code returns a `ClassPathResource`. If the same method were run
|
||||
against a `FileSystemXmlApplicationContext` instance, it would return a
|
||||
`FileSystemResource`. For a `WebApplicationContext`, it would return a
|
||||
`ServletContextResource`. It would similarly return appropriate objects for each context.
|
||||
|
||||
As a result, you can load resources in a fashion appropriate to the particular application
|
||||
context.
|
||||
|
||||
On the other hand, you may also force `ClassPathResource` to be used, regardless of the
|
||||
application context type, by specifying the special `classpath:` prefix, as the following
|
||||
example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")
|
||||
----
|
||||
|
||||
Similarly, you can force a `UrlResource` to be used by specifying any of the standard
|
||||
`java.net.URL` prefixes. The following pair of examples use the `file` and `http`
|
||||
prefixes:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val template = ctx.getResource("file:///some/resource/path/myTemplate.txt")
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt")
|
||||
----
|
||||
|
||||
The following table summarizes the strategy for converting `String` objects to `Resource` objects:
|
||||
|
||||
[[resources-resource-strings]]
|
||||
.Resource strings
|
||||
|===
|
||||
| Prefix| Example| Explanation
|
||||
|
||||
| classpath:
|
||||
| `classpath:com/myapp/config.xml`
|
||||
| Loaded from the classpath.
|
||||
|
||||
| file:
|
||||
| `file:///data/config.xml`
|
||||
| Loaded as a `URL` from the filesystem. See also <<resources-filesystemresource-caveats>>.
|
||||
|
||||
| http:
|
||||
| `https://myserver/logo.png`
|
||||
| Loaded as a `URL`.
|
||||
|
||||
| (none)
|
||||
| `/data/config.xml`
|
||||
| Depends on the underlying `ApplicationContext`.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
|
||||
[[resources-resourceloaderaware]]
|
||||
== The `ResourceLoaderAware` interface
|
||||
|
||||
The `ResourceLoaderAware` interface is a special callback interface which identifies
|
||||
components that expect to be provided with a `ResourceLoader` reference. The following
|
||||
listing shows the definition of the `ResourceLoaderAware` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public interface ResourceLoaderAware {
|
||||
|
||||
void setResourceLoader(ResourceLoader resourceLoader);
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
interface ResourceLoaderAware {
|
||||
|
||||
fun setResourceLoader(resourceLoader: ResourceLoader)
|
||||
}
|
||||
----
|
||||
|
||||
When a class implements `ResourceLoaderAware` and is deployed into an application context
|
||||
(as a Spring-managed bean), it is recognized as `ResourceLoaderAware` by the application
|
||||
context. The application context then invokes `setResourceLoader(ResourceLoader)`,
|
||||
supplying itself as the argument (remember, all application contexts in Spring implement
|
||||
the `ResourceLoader` interface).
|
||||
|
||||
Since an `ApplicationContext` is a `ResourceLoader`, the bean could also implement the
|
||||
`ApplicationContextAware` interface and use the supplied application context directly to
|
||||
load resources. However, in general, it is better to use the specialized `ResourceLoader`
|
||||
interface if that is all you need. The code would be coupled only to the resource loading
|
||||
interface (which can be considered a utility interface) and not to the whole Spring
|
||||
`ApplicationContext` interface.
|
||||
|
||||
In application components, you may also rely upon autowiring of the `ResourceLoader` as
|
||||
an alternative to implementing the `ResourceLoaderAware` interface. The "`traditional`"
|
||||
`constructor` and `byType` autowiring modes (as described in <<beans-factory-autowire>>)
|
||||
are capable of providing a `ResourceLoader` for either a constructor argument or a
|
||||
setter method parameter, respectively. For more flexibility (including the ability to
|
||||
autowire fields and multiple parameter methods), consider using the annotation-based
|
||||
autowiring features. In that case, the `ResourceLoader` is autowired into a field,
|
||||
constructor argument, or method parameter that expects the `ResourceLoader` type as long
|
||||
as the field, constructor, or method in question carries the `@Autowired` annotation.
|
||||
For more information, see <<beans-autowired-annotation>>.
|
||||
|
||||
|
||||
|
||||
|
||||
[[resources-as-dependencies]]
|
||||
== Resources as Dependencies
|
||||
|
||||
If the bean itself is going to determine and supply the resource path through some sort
|
||||
of dynamic process, it probably makes sense for the bean to use the `ResourceLoader`
|
||||
interface to load resources. For example, consider the loading of a template of some
|
||||
sort, where the specific resource that is needed depends on the role of the user. If the
|
||||
resources are static, it makes sense to eliminate the use of the `ResourceLoader`
|
||||
interface completely, have the bean expose the `Resource` properties it needs,
|
||||
and expect them to be injected into it.
|
||||
|
||||
What makes it trivial to then inject these properties is that all application contexts
|
||||
register and use a special JavaBeans `PropertyEditor`, which can convert `String` paths
|
||||
to `Resource` objects. So, if `myBean` has a template property of type `Resource`, it can
|
||||
be configured with a simple string for that resource, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="myBean" class="...">
|
||||
<property name="template" value="some/resource/path/myTemplate.txt"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that the resource path has no prefix. Consequently, because the application context itself is
|
||||
going to be used as the `ResourceLoader`, the resource itself is loaded through a
|
||||
`ClassPathResource`, a `FileSystemResource`, or a `ServletContextResource`,
|
||||
depending on the exact type of the context.
|
||||
|
||||
If you need to force a specific `Resource` type to be used, you can use a prefix.
|
||||
The following two examples show how to force a `ClassPathResource` and a
|
||||
`UrlResource` (the latter being used to access a filesystem file):
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
|
||||
----
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
[[resources-app-ctx]]
|
||||
== Application Contexts and Resource Paths
|
||||
|
||||
This section covers how to create application contexts with resources, including shortcuts
|
||||
that work with XML, how to use wildcards, and other details.
|
||||
|
||||
|
||||
|
||||
[[resources-app-ctx-construction]]
|
||||
=== Constructing Application Contexts
|
||||
|
||||
An application context constructor (for a specific application context type) generally
|
||||
takes a string or array of strings as the location paths of the resources, such as
|
||||
XML files that make up the definition of the context.
|
||||
|
||||
When such a location path does not have a prefix, the specific `Resource` type built from
|
||||
that path and used to load the bean definitions depends on and is appropriate to the
|
||||
specific application context. For example, consider the following example, which creates a
|
||||
`ClassPathXmlApplicationContext`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")
|
||||
----
|
||||
|
||||
The bean definitions are loaded from the classpath, because a `ClassPathResource` is
|
||||
used. However, consider the following example, which creates a `FileSystemXmlApplicationContext`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ApplicationContext ctx =
|
||||
new FileSystemXmlApplicationContext("conf/appContext.xml");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")
|
||||
----
|
||||
|
||||
Now the bean definition is loaded from a filesystem location (in this case, relative to
|
||||
the current working directory).
|
||||
|
||||
Note that the use of the special classpath prefix or a standard URL prefix on the
|
||||
location path overrides the default type of `Resource` created to load the
|
||||
definition. Consider the following example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ApplicationContext ctx =
|
||||
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")
|
||||
----
|
||||
|
||||
Using `FileSystemXmlApplicationContext` loads the bean definitions from the classpath. However, it is still a
|
||||
`FileSystemXmlApplicationContext`. If it is subsequently used as a `ResourceLoader`, any
|
||||
unprefixed paths are still treated as filesystem paths.
|
||||
|
||||
|
||||
[[resources-app-ctx-classpathxml]]
|
||||
==== Constructing `ClassPathXmlApplicationContext` Instances -- Shortcuts
|
||||
|
||||
The `ClassPathXmlApplicationContext` exposes a number of constructors to enable
|
||||
convenient instantiation. The basic idea is that you can supply merely a string array
|
||||
that contains only the filenames of the XML files themselves (without the leading path
|
||||
information) and also supplies a `Class`. The `ClassPathXmlApplicationContext`
|
||||
then derives the path information from the supplied class.
|
||||
|
||||
Consider the following directory layout:
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
com/
|
||||
foo/
|
||||
services.xml
|
||||
daos.xml
|
||||
MessengerService.class
|
||||
----
|
||||
|
||||
The following example shows how a `ClassPathXmlApplicationContext` instance composed of the beans defined in
|
||||
files named `services.xml` and `daos.xml` (which are on the classpath) can be instantiated:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ApplicationContext ctx = new ClassPathXmlApplicationContext(
|
||||
new String[] {"services.xml", "daos.xml"}, MessengerService.class);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "daos.xml"), MessengerService::class.java)
|
||||
----
|
||||
|
||||
See the {api-spring-framework}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`]
|
||||
javadoc for details on the various constructors.
|
||||
|
||||
|
||||
|
||||
[[resources-app-ctx-wildcards-in-resource-paths]]
|
||||
=== Wildcards in Application Context Constructor Resource Paths
|
||||
|
||||
The resource paths in application context constructor values may be simple paths (as
|
||||
shown earlier), each of which has a one-to-one mapping to a target `Resource` or, alternately, may
|
||||
contain the special "classpath*:" prefix or internal Ant-style regular expressions
|
||||
(matched by using Spring's `PathMatcher` utility). Both of the latter are effectively
|
||||
wildcards.
|
||||
|
||||
One use for this mechanism is when you need to do component-style application assembly. All
|
||||
components can 'publish' context definition fragments to a well-known location path, and,
|
||||
when the final application context is created using the same path prefixed with
|
||||
`classpath*:`, all component fragments are automatically picked up.
|
||||
|
||||
Note that this wildcarding is specific to the use of resource paths in application context
|
||||
constructors (or when you use the `PathMatcher` utility class hierarchy directly) and is
|
||||
resolved at construction time. It has nothing to do with the `Resource` type itself.
|
||||
You cannot use the `classpath*:` prefix to construct an actual `Resource`, as
|
||||
a resource points to just one resource at a time.
|
||||
|
||||
|
||||
[[resources-app-ctx-ant-patterns-in-paths]]
|
||||
==== Ant-style Patterns
|
||||
|
||||
Path locations can contain Ant-style patterns, as the following example shows:
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
/WEB-INF/\*-context.xml
|
||||
com/mycompany/\**/applicationContext.xml
|
||||
file:C:/some/path/\*-context.xml
|
||||
classpath:com/mycompany/**/applicationContext.xml
|
||||
----
|
||||
|
||||
When the path location contains an Ant-style pattern, the resolver follows a more complex procedure to try to resolve the
|
||||
wildcard. It produces a `Resource` for the path up to the last non-wildcard segment and
|
||||
obtains a URL from it. If this URL is not a `jar:` URL or container-specific variant
|
||||
(such as `zip:` in WebLogic, `wsjar` in WebSphere, and so on), a `java.io.File` is
|
||||
obtained from it and used to resolve the wildcard by traversing the filesystem. In the
|
||||
case of a jar URL, the resolver either gets a `java.net.JarURLConnection` from it or
|
||||
manually parses the jar URL and then traverses the contents of the jar file to resolve
|
||||
the wildcards.
|
||||
|
||||
[[resources-app-ctx-portability]]
|
||||
===== Implications on Portability
|
||||
|
||||
If the specified path is already a file URL (either implicitly because the base
|
||||
`ResourceLoader` is a filesystem one or explicitly), wildcarding is guaranteed to
|
||||
work in a completely portable fashion.
|
||||
|
||||
If the specified path is a classpath location, the resolver must obtain the last
|
||||
non-wildcard path segment URL by making a `Classloader.getResource()` call. Since this
|
||||
is just a node of the path (not the file at the end), it is actually undefined (in the
|
||||
`ClassLoader` javadoc) exactly what sort of a URL is returned in this case. In practice,
|
||||
it is always a `java.io.File` representing the directory (where the classpath resource
|
||||
resolves to a filesystem location) or a jar URL of some sort (where the classpath resource
|
||||
resolves to a jar location). Still, there is a portability concern on this operation.
|
||||
|
||||
If a jar URL is obtained for the last non-wildcard segment, the resolver must be able to
|
||||
get a `java.net.JarURLConnection` from it or manually parse the jar URL, to be able to
|
||||
walk the contents of the jar and resolve the wildcard. This does work in most environments
|
||||
but fails in others, and we strongly recommend that the wildcard resolution of resources
|
||||
coming from jars be thoroughly tested in your specific environment before you rely on it.
|
||||
|
||||
|
||||
[[resources-classpath-wildcards]]
|
||||
==== The `classpath*:` Prefix
|
||||
|
||||
When constructing an XML-based application context, a location string may use the
|
||||
special `classpath*:` prefix, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ApplicationContext ctx =
|
||||
new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")
|
||||
----
|
||||
|
||||
This special prefix specifies that all classpath resources that match the given name
|
||||
must be obtained (internally, this essentially happens through a call to
|
||||
`ClassLoader.getResources(...)`) and then merged to form the final application
|
||||
context definition.
|
||||
|
||||
NOTE: The wildcard classpath relies on the `getResources()` method of the underlying
|
||||
classloader. As most application servers nowadays supply their own classloader
|
||||
implementation, the behavior might differ, especially when dealing with jar files. A
|
||||
simple test to check if `classpath*` works is to use the classloader to load a file from
|
||||
within a jar on the classpath:
|
||||
`getClass().getClassLoader().getResources("<someFileInsideTheJar>")`. Try this test with
|
||||
files that have the same name but are placed inside two different locations. In case an
|
||||
inappropriate result is returned, check the application server documentation for
|
||||
settings that might affect the classloader behavior.
|
||||
|
||||
You can also combine the `classpath*:` prefix with a `PathMatcher` pattern in the
|
||||
rest of the location path (for example, `classpath*:META-INF/*-beans.xml`). In this
|
||||
case, the resolution strategy is fairly simple: A `ClassLoader.getResources()` call is
|
||||
used on the last non-wildcard path segment to get all the matching resources in the
|
||||
class loader hierarchy and then, off each resource, the same `PathMatcher` resolution
|
||||
strategy described earlier is used for the wildcard subpath.
|
||||
|
||||
|
||||
[[resources-wildcards-in-path-other-stuff]]
|
||||
==== Other Notes Relating to Wildcards
|
||||
|
||||
Note that `classpath*:`, when combined with Ant-style patterns, only works
|
||||
reliably with at least one root directory before the pattern starts, unless the actual
|
||||
target files reside in the file system. This means that a pattern such as
|
||||
`classpath*:*.xml` might not retrieve files from the root of jar files but rather only
|
||||
from the root of expanded directories.
|
||||
|
||||
Spring's ability to retrieve classpath entries originates from the JDK's
|
||||
`ClassLoader.getResources()` method, which only returns file system locations for an
|
||||
empty string (indicating potential roots to search). Spring evaluates
|
||||
`URLClassLoader` runtime configuration and the `java.class.path` manifest in jar files
|
||||
as well, but this is not guaranteed to lead to portable behavior.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The scanning of classpath packages requires the presence of corresponding directory
|
||||
entries in the classpath. When you build JARs with Ant, do not activate the files-only
|
||||
switch of the JAR task. Also, classpath directories may not get exposed based on security
|
||||
policies in some environments -- for example, stand-alone applications on JDK 1.7.0_45
|
||||
and higher (which requires 'Trusted-Library' to be set up in your manifests. See
|
||||
https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources).
|
||||
|
||||
On JDK 9's module path (Jigsaw), Spring's classpath scanning generally works as expected.
|
||||
Putting resources into a dedicated directory is highly recommendable here as well,
|
||||
avoiding the aforementioned portability problems with searching the jar file root level.
|
||||
====
|
||||
|
||||
Ant-style patterns with `classpath:` resources are not guaranteed to find matching
|
||||
resources if the root package to search is available in multiple class path locations.
|
||||
Consider the following example of a resource location:
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
com/mycompany/package1/service-context.xml
|
||||
----
|
||||
|
||||
Now consider an Ant-style path that someone might use to try to find that file:
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
classpath:com/mycompany/**/service-context.xml
|
||||
----
|
||||
|
||||
Such a resource may be in only one location, but when a path such as the preceding example
|
||||
is used to try to resolve it, the resolver works off the (first) URL returned by
|
||||
`getResource("com/mycompany");`. If this base package node exists in multiple
|
||||
classloader locations, the actual end resource may not be there. Therefore, in such a case
|
||||
you should prefer using `classpath*:` with the same Ant-style pattern, which
|
||||
searches all class path locations that contain the root package.
|
||||
|
||||
|
||||
|
||||
[[resources-filesystemresource-caveats]]
|
||||
=== `FileSystemResource` Caveats
|
||||
|
||||
A `FileSystemResource` that is not attached to a `FileSystemApplicationContext` (that
|
||||
is, when a `FileSystemApplicationContext` is not the actual `ResourceLoader`) treats
|
||||
absolute and relative paths as you would expect. Relative paths are relative to the
|
||||
current working directory, while absolute paths are relative to the root of the
|
||||
filesystem.
|
||||
|
||||
For backwards compatibility (historical) reasons however, this changes when the
|
||||
`FileSystemApplicationContext` is the `ResourceLoader`. The
|
||||
`FileSystemApplicationContext` forces all attached `FileSystemResource` instances
|
||||
to treat all location paths as relative, whether they start with a leading slash or not.
|
||||
In practice, this means the following examples are equivalent:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ApplicationContext ctx =
|
||||
new FileSystemXmlApplicationContext("conf/context.xml");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val ctx = FileSystemXmlApplicationContext("conf/context.xml")
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ApplicationContext ctx =
|
||||
new FileSystemXmlApplicationContext("/conf/context.xml");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val ctx = FileSystemXmlApplicationContext("/conf/context.xml")
|
||||
----
|
||||
|
||||
The following examples are also equivalent (even though it would make sense for them to be different, as one
|
||||
case is relative and the other absolute):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
FileSystemXmlApplicationContext ctx = ...;
|
||||
ctx.getResource("some/resource/path/myTemplate.txt");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val ctx: FileSystemXmlApplicationContext = ...
|
||||
ctx.getResource("some/resource/path/myTemplate.txt")
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
FileSystemXmlApplicationContext ctx = ...;
|
||||
ctx.getResource("/some/resource/path/myTemplate.txt");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val ctx: FileSystemXmlApplicationContext = ...
|
||||
ctx.getResource("/some/resource/path/myTemplate.txt")
|
||||
----
|
||||
|
||||
In practice, if you need true absolute filesystem paths, you should avoid using
|
||||
absolute paths with `FileSystemResource` or `FileSystemXmlApplicationContext` and
|
||||
force the use of a `UrlResource` by using the `file:` URL prefix. The following examples
|
||||
show how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// actual context type doesn't matter, the Resource will always be UrlResource
|
||||
ctx.getResource("file:///some/resource/path/myTemplate.txt");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// actual context type doesn't matter, the Resource will always be UrlResource
|
||||
ctx.getResource("file:///some/resource/path/myTemplate.txt")
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
|
||||
ApplicationContext ctx =
|
||||
new FileSystemXmlApplicationContext("file:///conf/context.xml");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
|
||||
val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml")
|
||||
----
|
@ -0,0 +1,41 @@
|
||||
[[spring-jcl]]
|
||||
= Logging
|
||||
|
||||
Since Spring Framework 5.0, Spring comes with its own Commons Logging bridge implemented
|
||||
in the `spring-jcl` module. The implementation checks for the presence of the Log4j 2.x
|
||||
API and the SLF4J 1.7 API in the classpath and uses the first one of those found as the
|
||||
logging implementation, falling back to the Java platform's core logging facilities (also
|
||||
known as _JUL_ or `java.util.logging`) if neither Log4j 2.x nor SLF4J is available.
|
||||
|
||||
Put Log4j 2.x or Logback (or another SLF4J provider) in your classpath, without any extra
|
||||
bridges, and let the framework auto-adapt to your choice. For further information see the
|
||||
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-logging[Spring
|
||||
Boot Logging Reference Documentation].
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Spring's Commons Logging variant is only meant to be used for infrastructure logging
|
||||
purposes in the core framework and in extensions.
|
||||
|
||||
For logging needs within application code, prefer direct use of Log4j 2.x, SLF4J, or JUL.
|
||||
====
|
||||
|
||||
A `Log` implementation may be retrieved via `org.apache.commons.logging.LogFactory` as in
|
||||
the following example.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class MyBean {
|
||||
private final Log log = LogFactory.getLog(getClass());
|
||||
// ...
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class MyBean {
|
||||
private val log = LogFactory.getLog(javaClass)
|
||||
// ...
|
||||
}
|
||||
----
|
@ -0,0 +1,31 @@
|
||||
@import 'css/spring.css';
|
||||
|
||||
.listingblock .switch {
|
||||
border-style: none;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
bottom: -3px;
|
||||
}
|
||||
|
||||
.listingblock .switch--item {
|
||||
padding: 10px;
|
||||
background-color: #e6e1dc;
|
||||
color: #282c34;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.listingblock .switch--item:not(:first-child) {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.listingblock .switch--item.selected {
|
||||
background-color: #282c34;
|
||||
color: #e6e1dc;
|
||||
}
|
||||
|
||||
.listingblock pre.highlightjs {
|
||||
padding: 0;
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
= Appendix
|
||||
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas]]
|
||||
== XML Schemas
|
||||
|
||||
This part of the appendix lists XML schemas for data access, including the following:
|
||||
|
||||
* <<xsd-schemas-tx>>
|
||||
* <<xsd-schemas-jdbc>>
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-tx]]
|
||||
=== The `tx` Schema
|
||||
|
||||
The `tx` tags deal with configuring all of those beans in Spring's comprehensive support
|
||||
for transactions. These tags are covered in the chapter entitled
|
||||
<<data-access.adoc#transaction, Transaction Management>>.
|
||||
|
||||
TIP: We strongly encourage you to look at the `'spring-tx.xsd'` file that ships with the
|
||||
Spring distribution. This file contains the XML Schema for Spring's transaction
|
||||
configuration and covers all of the various elements in the `tx` namespace, including
|
||||
attribute defaults and similar information. This file is documented inline, and, thus,
|
||||
the information is not repeated here in the interests of adhering to the DRY (Don't
|
||||
Repeat Yourself) principle.
|
||||
|
||||
In the interest of completeness, to use the elements in the `tx` schema, you need to have
|
||||
the following preamble at the top of your Spring XML configuration file. The text in the
|
||||
following snippet references the correct schema so that the tags in the `tx` namespace
|
||||
are available to you:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx" <1>
|
||||
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">
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
<1> Declare usage of the `tx` namespace.
|
||||
<2> Specify the location (with other schema locations).
|
||||
|
||||
NOTE: Often, when you use the elements in the `tx` namespace, you are also using the
|
||||
elements from the `aop` namespace (since the declarative transaction support in Spring is
|
||||
implemented by using AOP). The preceding XML snippet contains the relevant lines needed
|
||||
to reference the `aop` schema so that the elements in the `aop` namespace are available
|
||||
to you.
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-jdbc]]
|
||||
=== The `jdbc` Schema
|
||||
|
||||
The `jdbc` elements let you quickly configure an embedded database or initialize an
|
||||
existing data source. These elements are documented in
|
||||
<<data-access.adoc#jdbc-embedded-database-support, Embedded Database Support>> and
|
||||
<<data-access.adoc#jdbc-initializing-datasource, Initializing a DataSource>>, respectively.
|
||||
|
||||
To use the elements in the `jdbc` schema, you need to have the following preamble at the
|
||||
top of your Spring XML configuration file. The text in the following snippet references
|
||||
the correct schema so that the elements in the `jdbc` namespace are available to you:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
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>
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
<1> Declare usage of the `jdbc` namespace.
|
||||
<2> Specify the location (with other schema locations).
|
@ -0,0 +1,2 @@
|
||||
<script type="text/javascript" src="js/tocbot/tocbot.min.js"></script>
|
||||
<script type="text/javascript" src="js/toc.js"></script>
|
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 47 KiB |
@ -0,0 +1,39 @@
|
||||
= Spring Framework Documentation
|
||||
:doc-root: https://docs.spring.io
|
||||
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
|
||||
|
||||
****
|
||||
_What's New_, _Upgrade Notes_, _Supported Versions_, and other topics,
|
||||
independent of release cadence, are maintained externally on the project's
|
||||
https://github.com/spring-projects/spring-framework/wiki[*Github Wiki*].
|
||||
****
|
||||
|
||||
[horizontal]
|
||||
<<overview.adoc#overview, Overview>> :: history, design philosophy, feedback,
|
||||
getting started.
|
||||
<<core.adoc#spring-core, Core>> :: IoC Container, Events, Resources, i18n,
|
||||
Validation, Data Binding, Type Conversion, SpEL, AOP.
|
||||
<<testing.adoc#testing, Testing>> :: Mock Objects, TestContext Framework,
|
||||
Spring MVC Test, WebTestClient.
|
||||
<<data-access.adoc#spring-data-tier, Data Access>> :: Transactions, DAO Support,
|
||||
JDBC, R2DBC, O/R Mapping, XML Marshalling.
|
||||
<<web.adoc#spring-web, Web Servlet>> :: Spring MVC, WebSocket, SockJS,
|
||||
STOMP Messaging.
|
||||
<<web-reactive.adoc#spring-webflux, Web Reactive>> :: Spring WebFlux, WebClient,
|
||||
WebSocket.
|
||||
<<integration.adoc#spring-integration, Integration>> :: Remoting, JMS, JCA, JMX,
|
||||
Email, Tasks, Scheduling, Caching.
|
||||
<<languages.adoc#languages, Languages>> :: Kotlin, Groovy, Dynamic Languages.
|
||||
|
||||
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 - 2020 Pivotal, 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.
|
@ -0,0 +1,349 @@
|
||||
= Appendix
|
||||
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas]]
|
||||
== XML Schemas
|
||||
|
||||
This part of the appendix lists XML schemas related to integration technologies.
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-jee]]
|
||||
=== The `jee` Schema
|
||||
|
||||
The `jee` elements deal with issues related to Java EE (Java Enterprise Edition) configuration,
|
||||
such as looking up a JNDI object and defining EJB references.
|
||||
|
||||
To use the elements in the `jee` schema, you need to have the following preamble at the top
|
||||
of your Spring XML configuration file. The text in the following snippet references the
|
||||
correct schema so that the elements in the `jee` namespace are available to you:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
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">
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-jee-jndi-lookup]]
|
||||
==== <jee:jndi-lookup/> (simple)
|
||||
|
||||
The following example shows how to use JNDI to look up a data source without the `jee` schema:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
|
||||
<property name="jndiName" value="jdbc/MyDataSource"/>
|
||||
</bean>
|
||||
<bean id="userDao" class="com.foo.JdbcUserDao">
|
||||
<!-- Spring will do the cast automatically (as usual) -->
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to use JNDI to look up a data source with the `jee`
|
||||
schema:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/MyDataSource"/>
|
||||
|
||||
<bean id="userDao" class="com.foo.JdbcUserDao">
|
||||
<!-- Spring will do the cast automatically (as usual) -->
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-jee-jndi-lookup-environment-single]]
|
||||
==== `<jee:jndi-lookup/>` (with Single JNDI Environment Setting)
|
||||
|
||||
The following example shows how to use JNDI to look up an environment variable without
|
||||
`jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
|
||||
<property name="jndiName" value="jdbc/MyDataSource"/>
|
||||
<property name="jndiEnvironment">
|
||||
<props>
|
||||
<prop key="ping">pong</prop>
|
||||
</props>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to use JNDI to look up an environment variable with `jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<jee:jndi-lookup id="simple" jndi-name="jdbc/MyDataSource">
|
||||
<jee:environment>ping=pong</jee:environment>
|
||||
</jee:jndi-lookup>
|
||||
----
|
||||
|
||||
|
||||
[[xsd-schemas-jee-jndi-lookup-evironment-multiple]]
|
||||
==== `<jee:jndi-lookup/>` (with Multiple JNDI Environment Settings)
|
||||
|
||||
The following example shows how to use JNDI to look up multiple environment variables
|
||||
without `jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
|
||||
<property name="jndiName" value="jdbc/MyDataSource"/>
|
||||
<property name="jndiEnvironment">
|
||||
<props>
|
||||
<prop key="sing">song</prop>
|
||||
<prop key="ping">pong</prop>
|
||||
</props>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to use JNDI to look up multiple environment variables with
|
||||
`jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<jee:jndi-lookup id="simple" jndi-name="jdbc/MyDataSource">
|
||||
<!-- newline-separated, key-value pairs for the environment (standard Properties format) -->
|
||||
<jee:environment>
|
||||
sing=song
|
||||
ping=pong
|
||||
</jee:environment>
|
||||
</jee:jndi-lookup>
|
||||
----
|
||||
|
||||
|
||||
[[xsd-schemas-jee-jndi-lookup-complex]]
|
||||
==== `<jee:jndi-lookup/>` (Complex)
|
||||
|
||||
The following example shows how to use JNDI to look up a data source and a number of
|
||||
different properties without `jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
|
||||
<property name="jndiName" value="jdbc/MyDataSource"/>
|
||||
<property name="cache" value="true"/>
|
||||
<property name="resourceRef" value="true"/>
|
||||
<property name="lookupOnStartup" value="false"/>
|
||||
<property name="expectedType" value="com.myapp.DefaultThing"/>
|
||||
<property name="proxyInterface" value="com.myapp.Thing"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to use JNDI to look up a data source and a number of
|
||||
different properties with `jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<jee:jndi-lookup id="simple"
|
||||
jndi-name="jdbc/MyDataSource"
|
||||
cache="true"
|
||||
resource-ref="true"
|
||||
lookup-on-startup="false"
|
||||
expected-type="com.myapp.DefaultThing"
|
||||
proxy-interface="com.myapp.Thing"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-jee-local-slsb]]
|
||||
==== `<jee:local-slsb/>` (Simple)
|
||||
|
||||
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
without `jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="simple"
|
||||
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
|
||||
<property name="jndiName" value="ejb/RentalServiceBean"/>
|
||||
<property name="businessInterface" value="com.foo.service.RentalService"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
with `jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<jee:local-slsb id="simpleSlsb" jndi-name="ejb/RentalServiceBean"
|
||||
business-interface="com.foo.service.RentalService"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-jee-local-slsb-complex]]
|
||||
==== `<jee:local-slsb/>` (Complex)
|
||||
|
||||
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
and a number of properties without `jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="complexLocalEjb"
|
||||
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
|
||||
<property name="jndiName" value="ejb/RentalServiceBean"/>
|
||||
<property name="businessInterface" value="com.example.service.RentalService"/>
|
||||
<property name="cacheHome" value="true"/>
|
||||
<property name="lookupHomeOnStartup" value="true"/>
|
||||
<property name="resourceRef" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
and a number of properties with `jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<jee:local-slsb id="complexLocalEjb"
|
||||
jndi-name="ejb/RentalServiceBean"
|
||||
business-interface="com.foo.service.RentalService"
|
||||
cache-home="true"
|
||||
lookup-home-on-startup="true"
|
||||
resource-ref="true">
|
||||
----
|
||||
|
||||
|
||||
[[xsd-schemas-jee-remote-slsb]]
|
||||
==== <jee:remote-slsb/>
|
||||
|
||||
The `<jee:remote-slsb/>` element configures a reference to a `remote` EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
|
||||
without `jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="complexRemoteEjb"
|
||||
class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
|
||||
<property name="jndiName" value="ejb/MyRemoteBean"/>
|
||||
<property name="businessInterface" value="com.foo.service.RentalService"/>
|
||||
<property name="cacheHome" value="true"/>
|
||||
<property name="lookupHomeOnStartup" value="true"/>
|
||||
<property name="resourceRef" value="true"/>
|
||||
<property name="homeInterface" value="com.foo.service.RentalService"/>
|
||||
<property name="refreshHomeOnConnectFailure" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
|
||||
with `jee`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<jee:remote-slsb id="complexRemoteEjb"
|
||||
jndi-name="ejb/MyRemoteBean"
|
||||
business-interface="com.foo.service.RentalService"
|
||||
cache-home="true"
|
||||
lookup-home-on-startup="true"
|
||||
resource-ref="true"
|
||||
home-interface="com.foo.service.RentalService"
|
||||
refresh-home-on-connect-failure="true">
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-jms]]
|
||||
=== The `jms` Schema
|
||||
|
||||
The `jms` elements deal with configuring JMS-related beans, such as Spring's
|
||||
<<integration.adoc#jms-mdp, Message Listener Containers>>. These elements are detailed in the
|
||||
section of the <<integration.adoc#jms, JMS chapter>> entitled <<integration.adoc#jms-namespace,
|
||||
JMS Namespace Support>>. See that chapter for full details on this support
|
||||
and the `jms` elements themselves.
|
||||
|
||||
In the interest of completeness, to use the elements in the `jms` schema, you need to have
|
||||
the following preamble at the top of your Spring XML configuration file. The text in the
|
||||
following snippet references the correct schema so that the elements in the `jms` namespace
|
||||
are available to you:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
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">
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-context-mbe]]
|
||||
=== Using `<context:mbean-export/>`
|
||||
|
||||
This element is detailed in
|
||||
<<integration.adoc#jmx-context-mbeanexport, Configuring Annotation-based MBean Export>>.
|
||||
|
||||
|
||||
|
||||
[[xsd-schemas-cache]]
|
||||
=== The `cache` Schema
|
||||
|
||||
You can use the `cache` elements to enable support for Spring's `@CacheEvict`, `@CachePut`,
|
||||
and `@Caching` annotations. It it also supports declarative XML-based caching. See
|
||||
<<integration.adoc#cache-annotation-enable, Enabling Caching Annotations>> and
|
||||
<<integration.adoc#cache-declarative-xml, Declarative XML-based Caching>> for details.
|
||||
|
||||
To use the elements in the `cache` schema, you need to have the following preamble at the
|
||||
top of your Spring XML configuration file. The text in the following snippet references
|
||||
the correct schema so that the elements in the `cache` namespace are available to you:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
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">
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
@ -0,0 +1,16 @@
|
||||
[[languages]]
|
||||
= Language Support
|
||||
:doc-root: https://docs.spring.io
|
||||
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
|
||||
:toc: left
|
||||
:toclevels: 4
|
||||
:tabsize: 4
|
||||
:docinfo1:
|
||||
|
||||
include::languages/kotlin.adoc[leveloffset=+1]
|
||||
|
||||
include::languages/groovy.adoc[leveloffset=+1]
|
||||
|
||||
include::languages/dynamic-languages.adoc[leveloffset=+1]
|
||||
|
||||
|
@ -0,0 +1,13 @@
|
||||
[[groovy]]
|
||||
= Apache Groovy
|
||||
|
||||
Groovy is a powerful, optionally typed, and dynamic language, with static-typing and static
|
||||
compilation capabilities. It offers a concise syntax and integrates smoothly with any
|
||||
existing Java application.
|
||||
|
||||
The Spring Framework provides a dedicated `ApplicationContext` that supports a Groovy-based
|
||||
Bean Definition DSL. For more details, see
|
||||
<<core.adoc#groovy-bean-definition-dsl, The Groovy Bean Definition DSL>>.
|
||||
|
||||
Further support for Groovy, including beans written in Groovy, refreshable script beans,
|
||||
and more is available in <<dynamic-language>>.
|
@ -0,0 +1,906 @@
|
||||
[[rsocket]]
|
||||
= RSocket
|
||||
:gh-rsocket: https://github.com/rsocket
|
||||
:gh-rsocket-java: {gh-rsocket}/rsocket-java
|
||||
:gh-rsocket-extentions: {gh-rsocket}/rsocket/blob/master/Extensions
|
||||
|
||||
This section describes Spring Framework's support for the RSocket protocol.
|
||||
|
||||
|
||||
[[rsocket-overview]]
|
||||
== Overview
|
||||
|
||||
RSocket is an application protocol for multiplexed, duplex communication over TCP,
|
||||
WebSocket, and other byte stream transports, using one of the following interaction
|
||||
models:
|
||||
|
||||
* `Request-Response` -- send one message and receive one back.
|
||||
* `Request-Stream` -- send one message and receive a stream of messages back.
|
||||
* `Channel` -- send streams of messages in both directions.
|
||||
* `Fire-and-Forget` -- send a one-way message.
|
||||
|
||||
Once the initial connection is made, the "client" vs "server" distinction is lost as
|
||||
both sides become symmetrical and each side can initiate one of the above interactions.
|
||||
This is why in the protocol calls the participating sides "requester" and "responder"
|
||||
while the above interactions are called "request streams" or simply "requests".
|
||||
|
||||
These are the key features and benefits of the RSocket protocol:
|
||||
|
||||
* https://www.reactive-streams.org/[Reactive Streams] semantics across network boundary --
|
||||
for streaming requests such as `Request-Stream` and `Channel`, back pressure signals
|
||||
travel between requester and responder, allowing a requester to slow down a responder at
|
||||
the source, hence reducing reliance on network layer congestion control, and the need
|
||||
for buffering at the network level or at any level.
|
||||
* Request throttling -- this feature is named "Leasing" after the `LEASE` frame that
|
||||
can be sent from each end to limit the total number of requests allowed by other end
|
||||
for a given time. Leases are renewed periodically.
|
||||
* Session resumption -- this is designed for loss of connectivity and requires some state
|
||||
to be maintained. The state management is transparent for applications, and works well
|
||||
in combination with back pressure which can stop a producer when possible and reduce
|
||||
the amount of state required.
|
||||
* Fragmentation and re-assembly of large messages.
|
||||
* Keepalive (heartbeats).
|
||||
|
||||
RSocket has {gh-rsocket}[implementations] in multiple languages. The
|
||||
{gh-rsocket-java}[Java library] is built on https://projectreactor.io/[Project Reactor],
|
||||
and https://github.com/reactor/reactor-netty[Reactor Netty] for the transport. That means
|
||||
signals from Reactive Streams Publishers in your application propagate transparently
|
||||
through RSocket across the network.
|
||||
|
||||
|
||||
|
||||
[[rsocket-protocol]]
|
||||
=== The Protocol
|
||||
|
||||
One of the benefits of RSocket is that it has well defined behavior on the wire and an
|
||||
easy to read https://rsocket.io/docs/Protocol[specification] along with some protocol
|
||||
{gh-rsocket}/rsocket/tree/master/Extensions[extensions]. Therefore it is
|
||||
a good idea to read the spec, independent of language implementations and higher level
|
||||
framework APIs. This section provides a succinct overview to establish some context.
|
||||
|
||||
**Connecting**
|
||||
|
||||
Initially a client connects to a server via some low level streaming transport such
|
||||
as TCP or WebSocket and sends a `SETUP` frame to the server to set parameters for the
|
||||
connection.
|
||||
|
||||
The server may reject the `SETUP` frame, but generally after it is sent (for the client)
|
||||
and received (for the server), both sides can begin to make requests, unless `SETUP`
|
||||
indicates use of leasing semantics to limit the number of requests, in which case
|
||||
both sides must wait for a `LEASE` frame from the other end to permit making requests.
|
||||
|
||||
**Making Requests**
|
||||
|
||||
Once a connection is established, both sides may initiate a request through one of the
|
||||
frames `REQUEST_RESPONSE`, `REQUEST_STREAM`, `REQUEST_CHANNEL`, or `REQUEST_FNF`. Each of
|
||||
those frames carries one message from the requester to the responder.
|
||||
|
||||
The responder may then return `PAYLOAD` frames with response messages, and in the case
|
||||
of `REQUEST_CHANNEL` the requester may also send `PAYLOAD` frames with more request
|
||||
messages.
|
||||
|
||||
When a request involves a stream of messages such as `Request-Stream` and `Channel`,
|
||||
the responder must respect demand signals from the requester. Demand is expressed as a
|
||||
number of messages. Initial demand is specified in `REQUEST_STREAM` and
|
||||
`REQUEST_CHANNEL` frames. Subsequent demand is signaled via `REQUEST_N` frames.
|
||||
|
||||
Each side may also send metadata notifications, via the `METADATA_PUSH` frame, that do not
|
||||
pertain to any individual request but rather to the connection as a whole.
|
||||
|
||||
**Message Format**
|
||||
|
||||
RSocket messages contain data and metadata. Metadata can be used to send a route, a
|
||||
security token, etc. Data and metadata can be formatted differently. Mime types for each
|
||||
are declared in the `SETUP` frame and apply to all requests on a given connection.
|
||||
|
||||
While all messages can have metadata, typically metadata such as a route are per-request
|
||||
and therefore only included in the first message on a request, i.e. with one of the frames
|
||||
`REQUEST_RESPONSE`, `REQUEST_STREAM`, `REQUEST_CHANNEL`, or `REQUEST_FNF`.
|
||||
|
||||
Protocol extensions define common metadata formats for use in applications:
|
||||
|
||||
* {gh-rsocket-extentions}/CompositeMetadata.md[Composite Metadata]-- multiple,
|
||||
independently formatted metadata entries.
|
||||
* {gh-rsocket-extentions}/Routing.md[Routing] -- the route for a request.
|
||||
|
||||
|
||||
|
||||
[[rsocket-java]]
|
||||
=== Java Implementation
|
||||
|
||||
The {gh-rsocket-java}[Java implementation] for RSocket is built on
|
||||
https://projectreactor.io/[Project Reactor]. The transports for TCP and WebSocket are
|
||||
built on https://github.com/reactor/reactor-netty[Reactor Netty]. As a Reactive Streams
|
||||
library, Reactor simplifies the job of implementing the protocol. For applications it is
|
||||
a natural fit to use `Flux` and `Mono` with declarative operators and transparent back
|
||||
pressure support.
|
||||
|
||||
The API in RSocket Java is intentionally minimal and basic. It focuses on protocol
|
||||
features and leaves the application programming model (e.g. RPC codegen vs other) as a
|
||||
higher level, independent concern.
|
||||
|
||||
The main contract
|
||||
{gh-rsocket-java}/blob/master/rsocket-core/src/main/java/io/rsocket/RSocket.java[io.rsocket.RSocket]
|
||||
models the four request interaction types with `Mono` representing a promise for a
|
||||
single message, `Flux` a stream of messages, and `io.rsocket.Payload` the actual
|
||||
message with access to data and metadata as byte buffers. The `RSocket` contract is used
|
||||
symmetrically. For requesting, the application is given an `RSocket` to perform
|
||||
requests with. For responding, the application implements `RSocket` to handle requests.
|
||||
|
||||
This is not meant to be a thorough introduction. For the most part, Spring applications
|
||||
will not have to use its API directly. However it may be important to see or experiment
|
||||
with RSocket independent of Spring. The RSocket Java repository contains a number of
|
||||
{gh-rsocket-java}/tree/master/rsocket-examples[sample apps] that
|
||||
demonstrate its API and protocol features.
|
||||
|
||||
|
||||
|
||||
[[rsocket-spring]]
|
||||
=== Spring Support
|
||||
|
||||
The `spring-messaging` module contains the following:
|
||||
|
||||
* <<rsocket-requester>> -- fluent API to make requests through an `io.rsocket.RSocket`
|
||||
with data and metadata encoding/decoding.
|
||||
* <<rsocket-annot-responders>> -- `@MessageMapping` annotated handler methods for
|
||||
responding.
|
||||
|
||||
The `spring-web` module contains `Encoder` and `Decoder` implementations such as Jackson
|
||||
CBOR/JSON, and Protobuf that RSocket applications will likely need. It also contains the
|
||||
`PathPatternParser` that can be plugged in for efficient route matching.
|
||||
|
||||
Spring Boot 2.2 supports standing up an RSocket server over TCP or WebSocket, including
|
||||
the option to expose RSocket over WebSocket in a WebFlux server. There is also client
|
||||
support and auto-configuration for an `RSocketRequester.Builder` and `RSocketStrategies`.
|
||||
See the
|
||||
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-rsocket[RSocket section]
|
||||
in the Spring Boot reference for more details.
|
||||
|
||||
Spring Security 5.2 provides RSocket support.
|
||||
|
||||
Spring Integration 5.2 provides inbound and outbound gateways to interact with RSocket
|
||||
clients and servers. See the Spring Integration Reference Manual for more details.
|
||||
|
||||
Spring Cloud Gateway supports RSocket connections.
|
||||
|
||||
|
||||
|
||||
[[rsocket-requester]]
|
||||
== RSocketRequester
|
||||
|
||||
`RSocketRequester` provides a fluent API to perform RSocket requests, accepting and
|
||||
returning objects for data and metadata instead of low level data buffers. It can be used
|
||||
symmetrically, to make requests from clients and to make requests from servers.
|
||||
|
||||
|
||||
[[rsocket-requester-client]]
|
||||
=== Client Requester
|
||||
|
||||
To obtain an `RSocketRequester` on the client side is to connect to a server which involves
|
||||
sending an RSocket `SETUP` frame with connection settings. `RSocketRequester` provides a
|
||||
builder that helps to prepare an `io.rsocket.core.RSocketConnector` including connection
|
||||
settings for the `SETUP` frame.
|
||||
|
||||
This is the most basic way to connect with default settings:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RSocketRequester requester = RSocketRequester.builder().tcp("localhost", 7000);
|
||||
|
||||
URI url = URI.create("https://example.org:8080/rsocket");
|
||||
RSocketRequester requester = RSocketRequester.builder().webSocket(url);
|
||||
----
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val requester = RSocketRequester.builder().tcp("localhost", 7000)
|
||||
|
||||
URI url = URI.create("https://example.org:8080/rsocket");
|
||||
val requester = RSocketRequester.builder().webSocket(url)
|
||||
----
|
||||
|
||||
The above does not connect immediately. When requests are made, a shared connection is
|
||||
established transparently and used.
|
||||
|
||||
|
||||
[[rsocket-requester-client-setup]]
|
||||
==== Connection Setup
|
||||
|
||||
`RSocketRequester.Builder` provides the following to customize the initial `SETUP` frame:
|
||||
|
||||
* `dataMimeType(MimeType)` -- set the mime type for data on the connection.
|
||||
* `metadataMimeType(MimeType)` -- set the mime type for metadata on the connection.
|
||||
* `setupData(Object)` -- data to include in the `SETUP`.
|
||||
* `setupRoute(String, Object...)` -- route in the metadata to include in the `SETUP`.
|
||||
* `setupMetadata(Object, MimeType)` -- other metadata to include in the `SETUP`.
|
||||
|
||||
For data, the default mime type is derived from the first configured `Decoder`. For
|
||||
metadata, the default mime type is
|
||||
{gh-rsocket-extentions}/CompositeMetadata.md[composite metadata] which allows multiple
|
||||
metadata value and mime type pairs per request. Typically both don't need to be changed.
|
||||
|
||||
Data and metadata in the `SETUP` frame is optional. On the server side,
|
||||
<<rsocket-annot-connectmapping>> methods can be used to handle the start of a
|
||||
connection and the content of the `SETUP` frame. Metadata may be used for connection
|
||||
level security.
|
||||
|
||||
|
||||
[[rsocket-requester-client-strategies]]
|
||||
==== Strategies
|
||||
|
||||
`RSocketRequester.Builder` accepts `RSocketStrategies` to configure the requester.
|
||||
You'll need to use this to provide encoders and decoders for (de)-serialization of data and
|
||||
metadata values. By default only the basic codecs from `spring-core` for `String`,
|
||||
`byte[]`, and `ByteBuffer` are registered. Adding `spring-web` provides access to more that
|
||||
can be registered as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RSocketStrategies strategies = RSocketStrategies.builder()
|
||||
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
|
||||
.build();
|
||||
|
||||
RSocketRequester requester = RSocketRequester.builder()
|
||||
.rsocketStrategies(strategies)
|
||||
.tcp("localhost", 7000);
|
||||
----
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val strategies = RSocketStrategies.builder()
|
||||
.encoders { it.add(Jackson2CborEncoder()) }
|
||||
.decoders { it.add(Jackson2CborDecoder()) }
|
||||
.build()
|
||||
|
||||
val requester = RSocketRequester.builder()
|
||||
.rsocketStrategies(strategies)
|
||||
.tcp("localhost", 7000)
|
||||
----
|
||||
|
||||
`RSocketStrategies` is designed for re-use. In some scenarios, e.g. client and server in
|
||||
the same application, it may be preferable to declare it in Spring configuration.
|
||||
|
||||
|
||||
[[rsocket-requester-client-responder]]
|
||||
==== Client Responders
|
||||
|
||||
`RSocketRequester.Builder` can be used to configure responders to requests from the
|
||||
server.
|
||||
|
||||
You can use annotated handlers for client-side responding based on the same
|
||||
infrastructure that's used on a server, but registered programmatically as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RSocketStrategies strategies = RSocketStrategies.builder()
|
||||
.routeMatcher(new PathPatternRouteMatcher()) // <1>
|
||||
.build();
|
||||
|
||||
SocketAcceptor responder =
|
||||
RSocketMessageHandler.responder(strategies, new ClientHandler()); // <2>
|
||||
|
||||
RSocketRequester requester = RSocketRequester.builder()
|
||||
.rsocketConnector(connector -> connector.acceptor(responder)) // <3>
|
||||
.tcp("localhost", 7000);
|
||||
----
|
||||
<1> Use `PathPatternRouteMatcher`, if `spring-web` is present, for efficient
|
||||
route matching.
|
||||
<2> Create a responder from a class with `@MessageMaping` and/or `@ConnectMapping` methods.
|
||||
<3> Register the responder.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val strategies = RSocketStrategies.builder()
|
||||
.routeMatcher(PathPatternRouteMatcher()) // <1>
|
||||
.build()
|
||||
|
||||
val responder =
|
||||
RSocketMessageHandler.responder(strategies, new ClientHandler()); // <2>
|
||||
|
||||
val requester = RSocketRequester.builder()
|
||||
.rsocketConnector { it.acceptor(responder) } // <3>
|
||||
.tcp("localhost", 7000)
|
||||
----
|
||||
<1> Use `PathPatternRouteMatcher`, if `spring-web` is present, for efficient
|
||||
route matching.
|
||||
<2> Create a responder from a class with `@MessageMaping` and/or `@ConnectMapping` methods.
|
||||
<3> Register the responder.
|
||||
|
||||
Note the above is only a shortcut designed for programmatic registration of client
|
||||
responders. For alternative scenarios, where client responders are in Spring configuration,
|
||||
you can still declare `RSocketMessageHandler` as a Spring bean and then apply as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ApplicationContext context = ... ;
|
||||
RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class);
|
||||
|
||||
RSocketRequester requester = RSocketRequester.builder()
|
||||
.rsocketConnector(connector -> connector.acceptor(handler.responder()))
|
||||
.tcp("localhost", 7000);
|
||||
----
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
val context: ApplicationContext = ...
|
||||
val handler = context.getBean<RSocketMessageHandler>()
|
||||
|
||||
val requester = RSocketRequester.builder()
|
||||
.rsocketConnector { it.acceptor(handler.responder()) }
|
||||
.tcp("localhost", 7000)
|
||||
----
|
||||
|
||||
For the above you may also need to use `setHandlerPredicate` in `RSocketMessageHandler` to
|
||||
switch to a different strategy for detecting client responders, e.g. based on a custom
|
||||
annotation such as `@RSocketClientResponder` vs the default `@Controller`. This
|
||||
is necessary in scenarios with client and server, or multiple clients in the same
|
||||
application.
|
||||
|
||||
See also <<rsocket-annot-responders>>, for more on the programming model.
|
||||
|
||||
|
||||
[[rsocket-requester-client-advanced]]
|
||||
==== Advanced
|
||||
|
||||
`RSocketRequesterBuilder` provides a callback to expose the underlying
|
||||
`io.rsocket.core.RSocketConnector` for further configuration options for keepalive
|
||||
intervals, session resumption, interceptors, and more. You can configure options
|
||||
at that level as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RSocketRequester requester = RSocketRequester.builder()
|
||||
.rsocketConnector(connector -> {
|
||||
// ...
|
||||
})
|
||||
.tcp("localhost", 7000);
|
||||
----
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val requester = RSocketRequester.builder()
|
||||
.rsocketConnector {
|
||||
//...
|
||||
}
|
||||
.tcp("localhost", 7000)
|
||||
----
|
||||
|
||||
|
||||
[[rsocket-requester-server]]
|
||||
=== Server Requester
|
||||
|
||||
To make requests from a server to connected clients is a matter of obtaining the
|
||||
requester for the connected client from the server.
|
||||
|
||||
In <<rsocket-annot-responders>>, `@ConnectMapping` and `@MessageMapping` methods support an
|
||||
`RSocketRequester` argument. Use it to access the requester for the connection. Keep in
|
||||
mind that `@ConnectMapping` methods are essentially handlers of the `SETUP` frame which
|
||||
must be handled before requests can begin. Therefore, requests at the very start must be
|
||||
decoupled from handling. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@ConnectMapping
|
||||
Mono<Void> handle(RSocketRequester requester) {
|
||||
requester.route("status").data("5")
|
||||
.retrieveFlux(StatusReport.class)
|
||||
.subscribe(bar -> { // <1>
|
||||
// ...
|
||||
});
|
||||
return ... // <2>
|
||||
}
|
||||
----
|
||||
<1> Start the request asynchronously, independent from handling.
|
||||
<2> Perform handling and return completion `Mono<Void>`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ConnectMapping
|
||||
suspend fun handle(requester: RSocketRequester) {
|
||||
GlobalScope.launch {
|
||||
requester.route("status").data("5").retrieveFlow<StatusReport>().collect { // <1>
|
||||
// ...
|
||||
}
|
||||
}
|
||||
/// ... <2>
|
||||
}
|
||||
----
|
||||
<1> Start the request asynchronously, independent from handling.
|
||||
<2> Perform handling in the suspending function.
|
||||
|
||||
|
||||
|
||||
[[rsocket-requester-requests]]
|
||||
=== Requests
|
||||
|
||||
Once you have a <<rsocket-requester-client,client>> or
|
||||
<<rsocket-requester-server,server>> requester, you can make requests as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ViewBox viewBox = ... ;
|
||||
|
||||
Flux<AirportLocation> locations = requester.route("locate.radars.within") // <1>
|
||||
.data(viewBox) // <2>
|
||||
.retrieveFlux(AirportLocation.class); // <3>
|
||||
|
||||
----
|
||||
<1> Specify a route to include in the metadata of the request message.
|
||||
<2> Provide data for the request message.
|
||||
<3> Declare the expected response.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val viewBox: ViewBox = ...
|
||||
|
||||
val locations = requester.route("locate.radars.within") // <1>
|
||||
.data(viewBox) // <2>
|
||||
.retrieveFlow<AirportLocation>() // <3>
|
||||
----
|
||||
<1> Specify a route to include in the metadata of the request message.
|
||||
<2> Provide data for the request message.
|
||||
<3> Declare the expected response.
|
||||
|
||||
The interaction type is determined implicitly from the cardinality of the input and
|
||||
output. The above example is a `Request-Stream` because one value is sent and a stream
|
||||
of values is received. For the most part you don't need to think about this as long as the
|
||||
choice of input and output matches an RSocket interaction type and the types of input and
|
||||
output expected by the responder. The only example of an invalid combination is many-to-one.
|
||||
|
||||
The `data(Object)` method also accepts any Reactive Streams `Publisher`, including
|
||||
`Flux` and `Mono`, as well as any other producer of value(s) that is registered in the
|
||||
`ReactiveAdapterRegistry`. For a multi-value `Publisher` such as `Flux` which produces the
|
||||
same types of values, consider using one of the overloaded `data` methods to avoid having
|
||||
type checks and `Encoder` lookup on every element:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
data(Object producer, Class<?> elementClass);
|
||||
data(Object producer, ParameterizedTypeReference<?> elementTypeRef);
|
||||
----
|
||||
|
||||
The `data(Object)` step is optional. Skip it for requests that don't send data:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<AirportLocation> location = requester.route("find.radar.EWR"))
|
||||
.retrieveMono(AirportLocation.class);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.messaging.rsocket.retrieveAndAwait
|
||||
|
||||
val location = requester.route("find.radar.EWR")
|
||||
.retrieveAndAwait<AirportLocation>()
|
||||
----
|
||||
|
||||
Extra metadata values can be added if using
|
||||
{gh-rsocket-extentions}/CompositeMetadata.md[composite metadata] (the default) and if the
|
||||
values are supported by a registered `Encoder`. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
String securityToken = ... ;
|
||||
ViewBox viewBox = ... ;
|
||||
MimeType mimeType = MimeType.valueOf("message/x.rsocket.authentication.bearer.v0");
|
||||
|
||||
Flux<AirportLocation> locations = requester.route("locate.radars.within")
|
||||
.metadata(securityToken, mimeType)
|
||||
.data(viewBox)
|
||||
.retrieveFlux(AirportLocation.class);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.messaging.rsocket.retrieveFlow
|
||||
|
||||
val requester: RSocketRequester = ...
|
||||
|
||||
val securityToken: String = ...
|
||||
val viewBox: ViewBox = ...
|
||||
val mimeType = MimeType.valueOf("message/x.rsocket.authentication.bearer.v0")
|
||||
|
||||
val locations = requester.route("locate.radars.within")
|
||||
.metadata(securityToken, mimeType)
|
||||
.data(viewBox)
|
||||
.retrieveFlow<AirportLocation>()
|
||||
----
|
||||
|
||||
For `Fire-and-Forget` use the `send()` method that returns `Mono<Void>`. Note that the `Mono`
|
||||
indicates only that the message was successfully sent, and not that it was handled.
|
||||
|
||||
For `Metadata-Push` use the `sendMetadata()` method with a `Mono<Void>` return value.
|
||||
|
||||
|
||||
|
||||
[[rsocket-annot-responders]]
|
||||
== Annotated Responders
|
||||
|
||||
RSocket responders can be implemented as `@MessageMapping` and `@ConnectMapping` methods.
|
||||
`@MessageMapping` methods handle individual requests while `@ConnectMapping` methods handle
|
||||
connection-level events (setup and metadata push). Annotated responders are supported
|
||||
symmetrically, for responding from the server side and for responding from the client side.
|
||||
|
||||
|
||||
|
||||
[[rsocket-annot-responders-server]]
|
||||
=== Server Responders
|
||||
|
||||
To use annotated responders on the server side, add `RSocketMessageHandler` to your Spring
|
||||
configuration to detect `@Controller` beans with `@MessageMapping` and `@ConnectMapping`
|
||||
methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
static class ServerConfig {
|
||||
|
||||
@Bean
|
||||
public RSocketMessageHandler rsocketMessageHandler() {
|
||||
RSocketMessageHandler handler = new RSocketMessageHandler();
|
||||
handler.routeMatcher(new PathPatternRouteMatcher());
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
class ServerConfig {
|
||||
|
||||
@Bean
|
||||
fun rsocketMessageHandler() = RSocketMessageHandler().apply {
|
||||
routeMatcher = PathPatternRouteMatcher()
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Then start an RSocket server through the Java RSocket API and plug the
|
||||
`RSocketMessageHandler` for the responder as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ApplicationContext context = ... ;
|
||||
RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class);
|
||||
|
||||
CloseableChannel server =
|
||||
RSocketServer.create(handler.responder())
|
||||
.bind(TcpServerTransport.create("localhost", 7000))
|
||||
.block();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
val context: ApplicationContext = ...
|
||||
val handler = context.getBean<RSocketMessageHandler>()
|
||||
|
||||
val server = RSocketServer.create(handler.responder())
|
||||
.bind(TcpServerTransport.create("localhost", 7000))
|
||||
.awaitSingle()
|
||||
----
|
||||
|
||||
`RSocketMessageHandler` supports
|
||||
{gh-rsocket-extentions}/CompositeMetadata.md[composite] and
|
||||
{gh-rsocket-extentions}/Routing.md[routing] metadata by default. You can set its
|
||||
<<rsocket-metadata-extractor>> if you need to switch to a
|
||||
different mime type or register additional metadata mime types.
|
||||
|
||||
You'll need to set the `Encoder` and `Decoder` instances required for metadata and data
|
||||
formats to support. You'll likely need the `spring-web` module for codec implementations.
|
||||
|
||||
By default `SimpleRouteMatcher` is used for matching routes via `AntPathMatcher`.
|
||||
We recommend plugging in the `PathPatternRouteMatcher` from `spring-web` for
|
||||
efficient route matching. RSocket routes can be hierarchical but are not URL paths.
|
||||
Both route matchers are configured to use "." as separator by default and there is no URL
|
||||
decoding as with HTTP URLs.
|
||||
|
||||
`RSocketMessageHandler` can be configured via `RSocketStrategies` which may be useful if
|
||||
you need to share configuration between a client and a server in the same process:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
static class ServerConfig {
|
||||
|
||||
@Bean
|
||||
public RSocketMessageHandler rsocketMessageHandler() {
|
||||
RSocketMessageHandler handler = new RSocketMessageHandler();
|
||||
handler.setRSocketStrategies(rsocketStrategies());
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.builder()
|
||||
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
|
||||
.routeMatcher(new PathPatternRouteMatcher())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
class ServerConfig {
|
||||
|
||||
@Bean
|
||||
fun rsocketMessageHandler() = RSocketMessageHandler().apply {
|
||||
rSocketStrategies = rsocketStrategies()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun rsocketStrategies() = RSocketStrategies.builder()
|
||||
.encoders { it.add(Jackson2CborEncoder()) }
|
||||
.decoders { it.add(Jackson2CborDecoder()) }
|
||||
.routeMatcher(PathPatternRouteMatcher())
|
||||
.build()
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[rsocket-annot-responders-client]]
|
||||
=== Client Responders
|
||||
|
||||
Annotated responders on the client side need to be configured in the
|
||||
`RSocketRequester.Builder`. For details, see
|
||||
<<rsocket-requester-client-responder>>.
|
||||
|
||||
|
||||
|
||||
[[rsocket-annot-messagemapping]]
|
||||
=== @MessageMapping
|
||||
|
||||
Once <<rsocket-annot-responders-server,server>> or
|
||||
<<rsocket-annot-responders-client,client>> responder configuration is in place,
|
||||
`@MessageMapping` methods can be used as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Controller
|
||||
public class RadarsController {
|
||||
|
||||
@MessageMapping("locate.radars.within")
|
||||
public Flux<AirportLocation> radars(MapRequest request) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Controller
|
||||
class RadarsController {
|
||||
|
||||
@MessageMapping("locate.radars.within")
|
||||
fun radars(request: MapRequest): Flow<AirportLocation> {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The above `@MessageMapping` method responds to a Request-Stream interaction having the
|
||||
route "locate.radars.within". It supports a flexible method signature with the option to
|
||||
use the following method arguments:
|
||||
|
||||
[cols="1,3",options="header"]
|
||||
|===
|
||||
| Method Argument
|
||||
| Description
|
||||
|
||||
| `@Payload`
|
||||
| The payload of the request. This can be a concrete value of asynchronous types like
|
||||
`Mono` or `Flux`.
|
||||
|
||||
*Note:* Use of the annotation is optional. A method argument that is not a simple type
|
||||
and is not any of the other supported arguments, is assumed to be the expected payload.
|
||||
|
||||
| `RSocketRequester`
|
||||
| Requester for making requests to the remote end.
|
||||
|
||||
| `@DestinationVariable`
|
||||
| Value extracted from the route based on variables in the mapping pattern, e.g.
|
||||
pass:q[`@MessageMapping("find.radar.{id}")`].
|
||||
|
||||
| `@Header`
|
||||
| Metadata value registered for extraction as described in <<rsocket-metadata-extractor>>.
|
||||
|
||||
| `@Headers Map<String, Object>`
|
||||
| All metadata values registered for extraction as described in <<rsocket-metadata-extractor>>.
|
||||
|
||||
|===
|
||||
|
||||
The return value is expected to be one or more Objects to be serialized as response
|
||||
payloads. That can be asynchronous types like `Mono` or `Flux`, a concrete value, or
|
||||
either `void` or a no-value asynchronous type such as `Mono<Void>`.
|
||||
|
||||
The RSocket interaction type that an `@MessageMapping` method supports is determined from
|
||||
the cardinality of the input (i.e. `@Payload` argument) and of the output, where
|
||||
cardinality means the following:
|
||||
|
||||
[%autowidth]
|
||||
[cols=2*,options="header"]
|
||||
|===
|
||||
| Cardinality
|
||||
| Description
|
||||
|
||||
| 1
|
||||
| Either an explicit value, or a single-value asynchronous type such as `Mono<T>`.
|
||||
|
||||
| Many
|
||||
| A multi-value asynchronous type such as `Flux<T>`.
|
||||
|
||||
| 0
|
||||
| For input this means the method does not have an `@Payload` argument.
|
||||
|
||||
For output this is `void` or a no-value asynchronous type such as `Mono<Void>`.
|
||||
|===
|
||||
|
||||
The table below shows all input and output cardinality combinations and the corresponding
|
||||
interaction type(s):
|
||||
|
||||
[%autowidth]
|
||||
[cols=3*,options="header"]
|
||||
|===
|
||||
| Input Cardinality
|
||||
| Output Cardinality
|
||||
| Interaction Types
|
||||
|
||||
| 0, 1
|
||||
| 0
|
||||
| Fire-and-Forget, Request-Response
|
||||
|
||||
| 0, 1
|
||||
| 1
|
||||
| Request-Response
|
||||
|
||||
| 0, 1
|
||||
| Many
|
||||
| Request-Stream
|
||||
|
||||
| Many
|
||||
| 0, 1, Many
|
||||
| Request-Channel
|
||||
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[rsocket-annot-connectmapping]]
|
||||
=== @ConnectMapping
|
||||
|
||||
`@ConnectMapping` handles the `SETUP` frame at the start of an RSocket connection, and
|
||||
any subsequent metadata push notifications through the `METADATA_PUSH` frame, i.e.
|
||||
`metadataPush(Payload)` in `io.rsocket.RSocket`.
|
||||
|
||||
`@ConnectMapping` methods support the same arguments as
|
||||
<<rsocket-annot-messagemapping>> but based on metadata and data from the `SETUP` and
|
||||
`METADATA_PUSH` frames. `@ConnectMapping` can have a pattern to narrow handling to
|
||||
specific connections that have a route in the metadata, or if no patterns are declared
|
||||
then all connections match.
|
||||
|
||||
`@ConnectMapping` methods cannot return data and must be declared with `void` or
|
||||
`Mono<Void>` as the return value. If handling returns an error for a new
|
||||
connection then the connection is rejected. Handling must not be held up to make
|
||||
requests to the `RSocketRequester` for the connection. See
|
||||
<<rsocket-requester-server>> for details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[rsocket-metadata-extractor]]
|
||||
== MetadataExtractor
|
||||
|
||||
Responders must interpret metadata.
|
||||
{gh-rsocket-extentions}/CompositeMetadata.md[Composite metadata] allows independently
|
||||
formatted metadata values (e.g. for routing, security, tracing) each with its own mime
|
||||
type. Applications need a way to configure metadata mime types to support, and a way
|
||||
to access extracted values.
|
||||
|
||||
`MetadataExtractor` is a contract to take serialized metadata and return decoded
|
||||
name-value pairs that can then be accessed like headers by name, for example via `@Header`
|
||||
in annotated handler methods.
|
||||
|
||||
`DefaultMetadataExtractor` can be given `Decoder` instances to decode metadata. Out of
|
||||
the box it has built-in support for
|
||||
{gh-rsocket-extentions}/Routing.md["message/x.rsocket.routing.v0"] which it decodes to
|
||||
`String` and saves under the "route" key. For any other mime type you'll need to provide
|
||||
a `Decoder` and register the mime type as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders);
|
||||
extractor.metadataToExtract(fooMimeType, Foo.class, "foo");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.messaging.rsocket.metadataToExtract
|
||||
|
||||
val extractor = DefaultMetadataExtractor(metadataDecoders)
|
||||
extractor.metadataToExtract<Foo>(fooMimeType, "foo")
|
||||
----
|
||||
|
||||
Composite metadata works well to combine independent metadata values. However the
|
||||
requester might not support composite metadata, or may choose not to use it. For this,
|
||||
`DefaultMetadataExtractor` may needs custom logic to map the decoded value to the output
|
||||
map. Here is an example where JSON is used for metadata:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders);
|
||||
extractor.metadataToExtract(
|
||||
MimeType.valueOf("application/vnd.myapp.metadata+json"),
|
||||
new ParameterizedTypeReference<Map<String,String>>() {},
|
||||
(jsonMap, outputMap) -> {
|
||||
outputMap.putAll(jsonMap);
|
||||
});
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.messaging.rsocket.metadataToExtract
|
||||
|
||||
val extractor = DefaultMetadataExtractor(metadataDecoders)
|
||||
extractor.metadataToExtract<Map<String, String>>(MimeType.valueOf("application/vnd.myapp.metadata+json")) { jsonMap, outputMap ->
|
||||
outputMap.putAll(jsonMap)
|
||||
}
|
||||
----
|
||||
|
||||
When configuring `MetadataExtractor` through `RSocketStrategies`, you can let
|
||||
`RSocketStrategies.Builder` create the extractor with the configured decoders, and
|
||||
simply use a callback to customize registrations as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RSocketStrategies strategies = RSocketStrategies.builder()
|
||||
.metadataExtractorRegistry(registry -> {
|
||||
registry.metadataToExtract(fooMimeType, Foo.class, "foo");
|
||||
// ...
|
||||
})
|
||||
.build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.messaging.rsocket.metadataToExtract
|
||||
|
||||
val strategies = RSocketStrategies.builder()
|
||||
.metadataExtractorRegistry { registry: MetadataExtractorRegistry ->
|
||||
registry.metadataToExtract<Foo>(fooMimeType, "foo")
|
||||
// ...
|
||||
}
|
||||
.build()
|
||||
----
|
@ -0,0 +1,579 @@
|
||||
[[webtestclient]]
|
||||
= WebTestClient
|
||||
:doc-root: https://docs.spring.io
|
||||
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
|
||||
|
||||
`WebTestClient` is an HTTP client designed for testing server applications. It wraps
|
||||
Spring's <<web-reactive.adoc#webflux-client, WebClient>> and uses it to perform requests
|
||||
but exposes a testing facade for verifying responses. `WebTestClient` can be used to
|
||||
perform end-to-end HTTP tests. It can also be used to test Spring MVC and Spring WebFlux
|
||||
applications without a running server via mock server request and response objects.
|
||||
|
||||
TIP: Kotlin users: See <<languages.adoc#kotlin-webtestclient-issue, this section>>
|
||||
related to use of the `WebTestClient`.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webtestclient-setup]]
|
||||
== Setup
|
||||
|
||||
To set up a `WebTestClient` you need to choose a server setup to bind to. This can be one
|
||||
of several mock server setup choices or a connection to a live server.
|
||||
|
||||
|
||||
|
||||
[[webtestclient-controller-config]]
|
||||
=== Bind to Controller
|
||||
|
||||
This setup allows you to test specific controller(s) via mock request and response objects,
|
||||
without a running server.
|
||||
|
||||
For WebFlux applications, use the following which loads infrastructure equivalent to the
|
||||
<<web-reactive.adoc#webflux-config, WebFlux Java config>>, registers the given
|
||||
controller(s), and creates a <<web-reactive.adoc#webflux-web-handler-api, WebHandler chain>>
|
||||
to handle requests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebTestClient client =
|
||||
WebTestClient.bindToController(new TestController()).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = WebTestClient.bindToController(TestController()).build()
|
||||
----
|
||||
|
||||
For Spring MVC, use the following which delegates to the
|
||||
{api-spring-framework}/https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.html[StandaloneMockMvcBuilder]
|
||||
to load infrastructure equivalent to the <<web.adoc#mvc-config, WebMvc Java config>>,
|
||||
registers the given controller(s), and creates an instance of
|
||||
<<testing.adoc#spring-mvc-test-framework, MockMvc>> to handle requests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebTestClient client =
|
||||
MockMvcWebTestClient.bindToController(new TestController()).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = MockMvcWebTestClient.bindToController(TestController()).build()
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[webtestclient-context-config]]
|
||||
=== Bind to `ApplicationContext`
|
||||
|
||||
This setup allows you to load Spring configuration with Spring MVC or Spring WebFlux
|
||||
infrastructure and controller declarations and use it to handle requests via mock request
|
||||
and response objects, without a running server.
|
||||
|
||||
For WebFlux, use the following where the Spring `ApplicationContext` is passed to
|
||||
{api-spring-framework}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder]
|
||||
to create the <<web-reactive.adoc#webflux-web-handler-api, WebHandler chain>> to handle
|
||||
requests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@SpringJUnitConfig(WebConfig.class) // <1>
|
||||
class MyTests {
|
||||
|
||||
WebTestClient client;
|
||||
|
||||
@BeforeEach
|
||||
void setUp(ApplicationContext context) { // <2>
|
||||
client = WebTestClient.bindToApplicationContext(context).build(); // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@SpringJUnitConfig(WebConfig::class) // <1>
|
||||
class MyTests {
|
||||
|
||||
lateinit var client: WebTestClient
|
||||
|
||||
@BeforeEach
|
||||
fun setUp(context: ApplicationContext) { // <2>
|
||||
client = WebTestClient.bindToApplicationContext(context).build() // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
|
||||
For Spring MVC, use the following where the Spring `ApplicationContext` is passed to
|
||||
{api-spring-framework}/test/web/servlet/setup/MockMvcBuilders.html#webAppContextSetup-org.springframework.web.context.WebApplicationContext-[MockMvcBuilders.webAppContextSetup]
|
||||
to create a <<testing.adoc#spring-mvc-test-framework, MockMvc>> instance to handle
|
||||
requests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WebAppConfiguration("classpath:META-INF/web-resources") // <1>
|
||||
@ContextHierarchy({
|
||||
@ContextConfiguration(classes = RootConfig.class),
|
||||
@ContextConfiguration(classes = WebConfig.class)
|
||||
})
|
||||
class MyTests {
|
||||
|
||||
@Autowired
|
||||
WebApplicationContext wac; // <2>
|
||||
|
||||
WebTestClient client;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
client = MockMvcWebTestClient.bindToApplicationContext(this.wac).build(); // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WebAppConfiguration("classpath:META-INF/web-resources") // <1>
|
||||
@ContextHierarchy({
|
||||
@ContextConfiguration(classes = RootConfig.class),
|
||||
@ContextConfiguration(classes = WebConfig.class)
|
||||
})
|
||||
class MyTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var wac: WebApplicationContext; // <2>
|
||||
|
||||
lateinit var client: WebTestClient
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() { // <2>
|
||||
client = MockMvcWebTestClient.bindToApplicationContext(wac).build() // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `WebTestClient`
|
||||
|
||||
|
||||
|
||||
[[webtestclient-fn-config]]
|
||||
=== Bind to Router Function
|
||||
|
||||
This setup allows you to test <<web-reactive.adoc#webflux-fn, functional endpoints>> via
|
||||
mock request and response objects, without a running server.
|
||||
|
||||
For WebFlux, use the following which delegates to `RouterFunctions.toWebHandler` to
|
||||
create a server setup to handle requests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<?> route = ...
|
||||
client = WebTestClient.bindToRouterFunction(route).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val route: RouterFunction<*> = ...
|
||||
val client = WebTestClient.bindToRouterFunction(route).build()
|
||||
----
|
||||
|
||||
For Spring MVC there are currently no options to test
|
||||
<<web.adoc#webmvc-fn, WebMvc functional endpoints>>.
|
||||
|
||||
|
||||
|
||||
[[webtestclient-server-config]]
|
||||
=== Bind to Server
|
||||
|
||||
This setup connects to a running server to perform full, end-to-end HTTP tests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[webtestclient-client-config]]
|
||||
=== Client Config
|
||||
|
||||
In addition to the server setup options described earlier, you can also configure client
|
||||
options, including base URL, default headers, client filters, and others. These options
|
||||
are readily available following `bindToServer()`. For all other configuration options,
|
||||
you need to use `configureClient()` to transition from server to client configuration, as
|
||||
follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client = WebTestClient.bindToController(new TestController())
|
||||
.configureClient()
|
||||
.baseUrl("/test")
|
||||
.build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client = WebTestClient.bindToController(TestController())
|
||||
.configureClient()
|
||||
.baseUrl("/test")
|
||||
.build()
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
[[webtestclient-tests]]
|
||||
== Writing Tests
|
||||
|
||||
`WebTestClient` provides an API identical to <<web-reactive.adoc#webflux-client, WebClient>>
|
||||
up to the point of performing a request by using `exchange()`. See the
|
||||
<<web-reactive.adoc#webflux-client-body, WebClient>> documentation for examples on how to
|
||||
prepare a request with any content including form data, multipart data, and more.
|
||||
|
||||
After the call to `exchange()`, `WebTestClient` diverges from the `WebClient` and
|
||||
instead continues with a workflow to verify responses.
|
||||
|
||||
To assert the response status and headers, use the following:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
----
|
||||
|
||||
You can then choose to decode the response body through one of the following:
|
||||
|
||||
* `expectBody(Class<T>)`: Decode to single object.
|
||||
* `expectBodyList(Class<T>)`: Decode and collect objects to `List<T>`.
|
||||
* `expectBody()`: Decode to `byte[]` for <<webtestclient-json>> or an empty body.
|
||||
|
||||
And perform assertions on the resulting higher level Object(s):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBodyList(Person.class).hasSize(3).contains(person);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.test.web.reactive.server.expectBodyList
|
||||
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBodyList<Person>().hasSize(3).contains(person)
|
||||
----
|
||||
|
||||
If the built-in assertions are insufficient, you can consume the object instead and
|
||||
perform any other assertions:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import org.springframework.test.web.reactive.server.expectBody
|
||||
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.consumeWith(result -> {
|
||||
// custom assertions (e.g. AssertJ)...
|
||||
});
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody<Person>()
|
||||
.consumeWith {
|
||||
// custom assertions (e.g. AssertJ)...
|
||||
}
|
||||
----
|
||||
|
||||
Or you can exit the workflow and obtain an `EntityExchangeResult`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.returnResult();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.test.web.reactive.server.expectBody
|
||||
|
||||
val result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
.expectBody<Person>()
|
||||
.returnResult()
|
||||
----
|
||||
|
||||
TIP: When you need to decode to a target type with generics, look for the overloaded methods
|
||||
that accept
|
||||
{api-spring-framework}/core/ParameterizedTypeReference.html[`ParameterizedTypeReference`]
|
||||
instead of `Class<T>`.
|
||||
|
||||
|
||||
|
||||
[[webtestclient-no-content]]
|
||||
=== No Content
|
||||
|
||||
If the response is not expected to have content, you can assert that as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.post().uri("/persons")
|
||||
.body(personMono, Person.class)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBody().isEmpty();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.post().uri("/persons")
|
||||
.bodyValue(person)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBody().isEmpty()
|
||||
----
|
||||
|
||||
If you want to ignore the response content, the following releases the content without
|
||||
any assertions:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.get().uri("/persons/123")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound()
|
||||
.expectBody(Void.class);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.get().uri("/persons/123")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound
|
||||
.expectBody<Unit>()
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[webtestclient-json]]
|
||||
=== JSON Content
|
||||
|
||||
You can use `expectBody()` without a target type to perform assertions on the raw
|
||||
content rather than through higher level Object(s).
|
||||
|
||||
To verify the full JSON content with https://jsonassert.skyscreamer.org[JSONAssert]:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.json("{\"name\":\"Jane\"}")
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.json("{\"name\":\"Jane\"}")
|
||||
----
|
||||
|
||||
To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.jsonPath("$[0].name").isEqualTo("Jane")
|
||||
.jsonPath("$[1].name").isEqualTo("Jason");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.jsonPath("$[0].name").isEqualTo("Jane")
|
||||
.jsonPath("$[1].name").isEqualTo("Jason")
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[webtestclient-stream]]
|
||||
=== Streaming Responses
|
||||
|
||||
To test potentially infinite streams such as `"text/event-stream"` or
|
||||
`"application/x-ndjson"`, start by verifying the response status and headers, and then
|
||||
obtain a `FluxExchangeResult`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
|
||||
.accept(TEXT_EVENT_STREAM)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.returnResult(MyEvent.class);
|
||||
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.test.web.reactive.server.returnResult
|
||||
|
||||
val result = client.get().uri("/events")
|
||||
.accept(TEXT_EVENT_STREAM)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.returnResult<MyEvent>()
|
||||
----
|
||||
|
||||
Now you're ready to consume the response stream with `StepVerifier` from `reactor-test`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Flux<Event> eventFlux = result.getResponseBody();
|
||||
|
||||
StepVerifier.create(eventFlux)
|
||||
.expectNext(person)
|
||||
.expectNextCount(4)
|
||||
.consumeNextWith(p -> ...)
|
||||
.thenCancel()
|
||||
.verify();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val eventFlux = result.getResponseBody()
|
||||
|
||||
StepVerifier.create(eventFlux)
|
||||
.expectNext(person)
|
||||
.expectNextCount(4)
|
||||
.consumeNextWith { p -> ... }
|
||||
.thenCancel()
|
||||
.verify()
|
||||
----
|
||||
|
||||
|
||||
[[webtestclient-mockmvc]]
|
||||
=== MockMvc Assertions
|
||||
|
||||
`WebTestClient` is an HTTP client and as such it can only verify what is in the client
|
||||
response including status, headers, and body.
|
||||
|
||||
When testing a Spring MVC application with a MockMvc server setup, you have the extra
|
||||
choice to perform further assertions on the server response. To do that start by
|
||||
obtaining an `ExchangeResult` after asserting the body:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// For a response with a body
|
||||
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.returnResult();
|
||||
|
||||
// For a response without a body
|
||||
EntityExchangeResult<Void> result = client.get().uri("/path")
|
||||
.exchange()
|
||||
.expectBody().isEmpty();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// For a response with a body
|
||||
val result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.returnResult();
|
||||
|
||||
// For a response without a body
|
||||
val result = client.get().uri("/path")
|
||||
.exchange()
|
||||
.expectBody().isEmpty();
|
||||
----
|
||||
|
||||
Then switch to MockMvc server response assertions:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
MockMvcWebTestClient.resultActionsFor(result)
|
||||
.andExpect(model().attribute("integer", 3))
|
||||
.andExpect(model().attribute("string", "a string value"));
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
MockMvcWebTestClient.resultActionsFor(result)
|
||||
.andExpect(model().attribute("integer", 3))
|
||||
.andExpect(model().attribute("string", "a string value"));
|
||||
----
|
||||
|
@ -0,0 +1,79 @@
|
||||
[[spring-web-reactive]]
|
||||
= Web on Reactive Stack
|
||||
:doc-root: https://docs.spring.io
|
||||
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
|
||||
:toc: left
|
||||
:toclevels: 4
|
||||
:tabsize: 4
|
||||
:docinfo1:
|
||||
|
||||
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 3.1+ containers. Individual chapters cover
|
||||
the <<webflux.adoc#webflux, Spring WebFlux>> framework,
|
||||
the reactive <<webflux-client, `WebClient`>>, support for <<webflux-test, testing>>,
|
||||
and <<webflux-reactive-libraries, reactive libraries>>. For Servlet-stack web applications,
|
||||
see <<web.adoc#spring-web, Web on Servlet Stack>>.
|
||||
|
||||
include::web/webflux.adoc[leveloffset=+1]
|
||||
|
||||
include::web/webflux-webclient.adoc[leveloffset=+1]
|
||||
|
||||
include::web/webflux-websocket.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
|
||||
[[webflux-test]]
|
||||
== Testing
|
||||
[.small]#<<web.adoc#testing, Same in Spring MVC>>#
|
||||
|
||||
The `spring-test` module provides mock implementations of `ServerHttpRequest`,
|
||||
`ServerHttpResponse`, and `ServerWebExchange`.
|
||||
See <<testing.adoc#mock-objects-web-reactive, Spring Web Reactive>> for a
|
||||
discussion of mock objects.
|
||||
|
||||
<<testing.adoc#webtestclient, `WebTestClient`>> builds on these mock request and
|
||||
response objects to provide support for testing WebFlux applications without an HTTP
|
||||
server. You can use the `WebTestClient` for end-to-end integration tests, too.
|
||||
|
||||
|
||||
|
||||
include::rsocket.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
|
||||
[[webflux-reactive-libraries]]
|
||||
== Reactive Libraries
|
||||
|
||||
`spring-webflux` depends on `reactor-core` and uses it internally to compose asynchronous
|
||||
logic and to provide Reactive Streams support. Generally, WebFlux APIs return `Flux` or
|
||||
`Mono` (since those are used internally) and leniently accept any Reactive Streams
|
||||
`Publisher` implementation as input. The use of `Flux` versus `Mono` is important, because
|
||||
it helps to express cardinality -- for example, whether a single or multiple asynchronous
|
||||
values are expected, and that can be essential for making decisions (for example, when
|
||||
encoding or decoding HTTP messages).
|
||||
|
||||
For annotated controllers, WebFlux transparently adapts to the reactive library chosen by
|
||||
the application. This is done with the help of the
|
||||
{api-spring-framework}/core/ReactiveAdapterRegistry.html[`ReactiveAdapterRegistry`], which
|
||||
provides pluggable support for reactive library and other asynchronous types. The registry
|
||||
has built-in support for RxJava 2/3, RxJava 1 (via RxJava Reactive Streams bridge), and
|
||||
`CompletableFuture`, but you can register others, too.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of Spring Framework 5.3, support for RxJava 1 is deprecated.
|
||||
====
|
||||
|
||||
|
||||
For functional APIs (such as <<webflux-fn>>, the `WebClient`, and others), the general rules
|
||||
for WebFlux APIs apply -- `Flux` and `Mono` as return values and a Reactive Streams
|
||||
`Publisher` as input. When a `Publisher`, whether custom or from another reactive library,
|
||||
is provided, it can be treated only as a stream with unknown semantics (0..N). If, however,
|
||||
the semantics are known, you can wrap it with `Flux` or `Mono.from(Publisher)` instead
|
||||
of passing the raw `Publisher`.
|
||||
|
||||
For example, given a `Publisher` that is not a `Mono`, the Jackson JSON message writer
|
||||
expects multiple values. If the media type implies an infinite stream (for example,
|
||||
`application/json+stream`), values are written and flushed individually. Otherwise,
|
||||
values are buffered into a list and rendered as a JSON array.
|
@ -0,0 +1,23 @@
|
||||
[[spring-web]]
|
||||
= Web on Servlet Stack
|
||||
:doc-root: https://docs.spring.io
|
||||
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
|
||||
:toc: left
|
||||
:toclevels: 4
|
||||
:tabsize: 4
|
||||
:docinfo1:
|
||||
|
||||
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 <<mvc, Spring MVC>>,
|
||||
<<mvc-view,View Technologies>>, <<mvc-cors,CORS Support>>, and <<websocket, WebSocket Support>>.
|
||||
For reactive-stack web applications, see <<web-reactive.adoc#spring-web-reactive, Web on Reactive Stack>>.
|
||||
|
||||
include::web/webmvc.adoc[leveloffset=+1]
|
||||
|
||||
include::web/webmvc-client.adoc[leveloffset=+1]
|
||||
|
||||
include::web/webmvc-test.adoc[leveloffset=+1]
|
||||
|
||||
include::web/websocket.adoc[leveloffset=+1]
|
||||
|
||||
include::web/integration.adoc[leveloffset=+1]
|
@ -0,0 +1,205 @@
|
||||
[[web-integration]]
|
||||
= Other Web Frameworks
|
||||
|
||||
This chapter details Spring's integration with third-party web frameworks.
|
||||
|
||||
One of the core value propositions of the Spring Framework is that of enabling
|
||||
_choice_. In a general sense, Spring does not force you to use or buy into any
|
||||
particular architecture, technology, or methodology (although it certainly recommends
|
||||
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
|
||||
(<<mvc, Spring MVC>> and <<webflux.adoc#webflux, Spring WebFlux>>) while, at the same time,
|
||||
supporting integration with a number of popular third-party web frameworks.
|
||||
|
||||
|
||||
|
||||
|
||||
[[web-integration-common]]
|
||||
== Common Configuration
|
||||
|
||||
Before diving into the integration specifics of each supported web framework, let us
|
||||
first take a look at common Spring configuration that is not specific to any one web
|
||||
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`"
|
||||
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
|
||||
`WebApplicationContext`) that contains all of the 'business beans' in your application.
|
||||
|
||||
Moving on to specifics, all you one need to do is declare a
|
||||
{api-spring-framework}/web/context/ContextLoaderListener.html[`ContextLoaderListener`]
|
||||
in the standard Java EE servlet `web.xml` file of your web application and add a
|
||||
`contextConfigLocation`<context-param/> section (in the same file) that defines which
|
||||
set of Spring XML configuration files to load.
|
||||
|
||||
Consider the following `<listener/>` configuration:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
----
|
||||
|
||||
Further consider the following `<context-param/>` configuration:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>/WEB-INF/applicationContext*.xml</param-value>
|
||||
</context-param>
|
||||
----
|
||||
|
||||
If you do not specify the `contextConfigLocation` context parameter, the
|
||||
`ContextLoaderListener` looks for a file called `/WEB-INF/applicationContext.xml` to
|
||||
load. Once the context files are loaded, Spring creates a
|
||||
{api-spring-framework}/web/context/WebApplicationContext.html[`WebApplicationContext`]
|
||||
object based on the bean definitions and stores it in the `ServletContext` of the web
|
||||
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`
|
||||
created by the `ContextLoaderListener`.
|
||||
|
||||
The following example shows how to get the `WebApplicationContext`:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
|
||||
----
|
||||
|
||||
The
|
||||
{api-spring-framework}/web/context/support/WebApplicationContextUtils.html[`WebApplicationContextUtils`]
|
||||
class is for convenience, so you need not remember the name of the `ServletContext`
|
||||
attribute. Its `getWebApplicationContext()` method returns `null` if an object
|
||||
does not exist under the `WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE`
|
||||
key. Rather than risk getting `NullPointerExceptions` in your application, it is better
|
||||
to use the `getRequiredWebApplicationContext()` method. This method throws an exception
|
||||
when the `ApplicationContext` is missing.
|
||||
|
||||
Once you have a reference to the `WebApplicationContext`, you can retrieve beans by their
|
||||
name or type. Most developers retrieve beans by name and then cast them to one of their
|
||||
implemented interfaces.
|
||||
|
||||
Fortunately, most of the frameworks in this section have simpler ways of looking up beans.
|
||||
Not only do they make it easy to get beans from a Spring container, but they also let you
|
||||
use dependency injection on their controllers. Each web framework section has more detail
|
||||
on its specific integration strategies.
|
||||
|
||||
|
||||
|
||||
|
||||
[[jsf]]
|
||||
== JSF
|
||||
|
||||
JavaServer Faces (JSF) is the JCP's standard component-based, event-driven web
|
||||
user interface framework. It is an official part of the Java EE umbrella but also
|
||||
individually usable, e.g. through embedding Mojarra or MyFaces within Tomcat.
|
||||
|
||||
Please note that recent versions of JSF became closely tied to CDI infrastructure
|
||||
in application servers, with some new JSF functionality only working in such an
|
||||
environment. Spring's JSF support is not actively evolved anymore and primarily
|
||||
exists for migration purposes when modernizing older JSF-based applications.
|
||||
|
||||
The key element in Spring's JSF integration is the JSF `ELResolver` mechanism.
|
||||
|
||||
|
||||
|
||||
[[jsf-springbeanfaceselresolver]]
|
||||
=== Spring Bean Resolver
|
||||
|
||||
`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
|
||||
default resolver of the underlying JSF implementation.
|
||||
|
||||
Configuration-wise, you can define `SpringBeanFacesELResolver` in your JSF
|
||||
`faces-context.xml` file, as the following example shows:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<faces-config>
|
||||
<application>
|
||||
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
|
||||
...
|
||||
</application>
|
||||
</faces-config>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[jsf-facescontextutils]]
|
||||
=== Using `FacesContextUtils`
|
||||
|
||||
A custom `ELResolver` works well when mapping your properties to beans in
|
||||
`faces-config.xml`, but, at times, you may need to explicitly grab a bean.
|
||||
The {api-spring-framework}/web/jsf/FacesContextUtils.html[`FacesContextUtils`]
|
||||
class makes this easy. It is similar to `WebApplicationContextUtils`, except that
|
||||
it takes a `FacesContext` parameter rather than a `ServletContext` parameter.
|
||||
|
||||
The following example shows how to use `FacesContextUtils`:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
[[struts]]
|
||||
== Apache Struts 2.x
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
||||
[[tapestry]]
|
||||
== Apache Tapestry 5.x
|
||||
|
||||
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 <<mvc, powerful web layer>>, there are a number of unique
|
||||
advantages to building an enterprise Java application by using a combination of Tapestry
|
||||
for the web user interface and the Spring container for the lower layers.
|
||||
|
||||
For more information, see Tapestry's dedicated
|
||||
https://tapestry.apache.org/integrating-with-spring-framework.html[integration module for Spring].
|
||||
|
||||
|
||||
|
||||
|
||||
[[web-integration-resources]]
|
||||
== Further Resources
|
||||
|
||||
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://struts.apache.org/[Struts] homepage
|
||||
* The https://tapestry.apache.org/[Tapestry] homepage
|
@ -0,0 +1,330 @@
|
||||
[[web-uricomponents]]
|
||||
= UriComponents
|
||||
[.small]#Spring MVC and Spring WebFlux#
|
||||
|
||||
`UriComponentsBuilder` helps to build URI's from URI templates with variables, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
UriComponents uriComponents = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}") // <1>
|
||||
.queryParam("q", "{q}") // <2>
|
||||
.encode() // <3>
|
||||
.build(); // <4>
|
||||
|
||||
URI uri = uriComponents.expand("Westin", "123").toUri(); // <5>
|
||||
----
|
||||
<1> Static factory method with a URI template.
|
||||
<2> Add or replace URI components.
|
||||
<3> Request to have the URI template and URI variables encoded.
|
||||
<4> Build a `UriComponents`.
|
||||
<5> Expand variables and obtain the `URI`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uriComponents = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}") // <1>
|
||||
.queryParam("q", "{q}") // <2>
|
||||
.encode() // <3>
|
||||
.build() // <4>
|
||||
|
||||
val uri = uriComponents.expand("Westin", "123").toUri() // <5>
|
||||
----
|
||||
<1> Static factory method with a URI template.
|
||||
<2> Add or replace URI components.
|
||||
<3> Request to have the URI template and URI variables encoded.
|
||||
<4> Build a `UriComponents`.
|
||||
<5> Expand variables and obtain the `URI`.
|
||||
|
||||
The preceding example can be consolidated into one chain and shortened with `buildAndExpand`,
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.encode()
|
||||
.buildAndExpand("Westin", "123")
|
||||
.toUri();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.encode()
|
||||
.buildAndExpand("Westin", "123")
|
||||
.toUri()
|
||||
----
|
||||
|
||||
You can shorten it further by going directly to a URI (which implies encoding),
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("Westin", "123");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("Westin", "123")
|
||||
----
|
||||
|
||||
You shorter it further still with a full URI template, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
|
||||
.build("Westin", "123");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
|
||||
.build("Westin", "123")
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
[[web-uribuilder]]
|
||||
= UriBuilder
|
||||
[.small]#Spring MVC and Spring WebFlux#
|
||||
|
||||
<<web-uricomponents, `UriComponentsBuilder`>> implements `UriBuilder`. You can create a
|
||||
`UriBuilder`, in turn, with a `UriBuilderFactory`. Together, `UriBuilderFactory` and
|
||||
`UriBuilder` provide a pluggable mechanism to build URIs from URI templates, based on
|
||||
shared configuration, such as a base URL, encoding preferences, and other details.
|
||||
|
||||
You can configure `RestTemplate` and `WebClient` with a `UriBuilderFactory`
|
||||
to customize the preparation of URIs. `DefaultUriBuilderFactory` is a default
|
||||
implementation of `UriBuilderFactory` that uses `UriComponentsBuilder` internally and
|
||||
exposes shared configuration options.
|
||||
|
||||
The following example shows how to configure a `RestTemplate`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
|
||||
|
||||
String baseUrl = "https://example.org";
|
||||
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
|
||||
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setUriTemplateHandler(factory);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode
|
||||
|
||||
val baseUrl = "https://example.org"
|
||||
val factory = DefaultUriBuilderFactory(baseUrl)
|
||||
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES
|
||||
|
||||
val restTemplate = RestTemplate()
|
||||
restTemplate.uriTemplateHandler = factory
|
||||
----
|
||||
|
||||
The following example configures a `WebClient`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
|
||||
|
||||
String baseUrl = "https://example.org";
|
||||
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
|
||||
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
|
||||
|
||||
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode
|
||||
|
||||
val baseUrl = "https://example.org"
|
||||
val factory = DefaultUriBuilderFactory(baseUrl)
|
||||
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES
|
||||
|
||||
val client = WebClient.builder().uriBuilderFactory(factory).build()
|
||||
----
|
||||
|
||||
In addition, you can also use `DefaultUriBuilderFactory` directly. It is similar to using
|
||||
`UriComponentsBuilder` but, instead of static factory methods, it is an actual instance
|
||||
that holds configuration and preferences, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
String baseUrl = "https://example.com";
|
||||
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
|
||||
|
||||
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("Westin", "123");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val baseUrl = "https://example.com"
|
||||
val uriBuilderFactory = DefaultUriBuilderFactory(baseUrl)
|
||||
|
||||
val uri = uriBuilderFactory.uriString("/hotels/{hotel}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("Westin", "123")
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[web-uri-encoding]]
|
||||
= URI Encoding
|
||||
[.small]#Spring MVC and Spring WebFlux#
|
||||
|
||||
`UriComponentsBuilder` exposes encoding options at two levels:
|
||||
|
||||
* {api-spring-framework}/web/util/UriComponentsBuilder.html#encode--[UriComponentsBuilder#encode()]:
|
||||
Pre-encodes the URI template first and then strictly encodes URI variables when expanded.
|
||||
* {api-spring-framework}/web/util/UriComponents.html#encode--[UriComponents#encode()]:
|
||||
Encodes URI components _after_ URI variables are expanded.
|
||||
|
||||
Both options replace non-ASCII and illegal characters with escaped octets. However, the first option
|
||||
also replaces characters with reserved meaning that appear in URI variables.
|
||||
|
||||
TIP: Consider ";", which is legal in a path but has reserved meaning. The first option replaces
|
||||
";" with "%3B" in URI variables but not in the URI template. By contrast, the second option never
|
||||
replaces ";", since it is a legal character in a path.
|
||||
|
||||
For most cases, the first option is likely to give the expected result, because it treats URI
|
||||
variables as opaque data to be fully encoded, while option 2 is useful only if
|
||||
URI variables intentionally contain reserved characters.
|
||||
|
||||
The following example uses the first option:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
|
||||
.queryParam("q", "{q}")
|
||||
.encode()
|
||||
.buildAndExpand("New York", "foo+bar")
|
||||
.toUri();
|
||||
|
||||
// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
|
||||
.queryParam("q", "{q}")
|
||||
.encode()
|
||||
.buildAndExpand("New York", "foo+bar")
|
||||
.toUri()
|
||||
|
||||
// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
|
||||
----
|
||||
|
||||
You can shorten the preceding example by going directly to the URI (which implies encoding),
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("New York", "foo+bar")
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
|
||||
.queryParam("q", "{q}")
|
||||
.build("New York", "foo+bar")
|
||||
----
|
||||
|
||||
You can shorten it further still with a full URI template, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}")
|
||||
.build("New York", "foo+bar")
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}")
|
||||
.build("New York", "foo+bar")
|
||||
----
|
||||
|
||||
The `WebClient` and the `RestTemplate` expand and encode URI templates internally through
|
||||
the `UriBuilderFactory` strategy. Both can be configured with a custom strategy.
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
String baseUrl = "https://example.com";
|
||||
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
|
||||
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
|
||||
|
||||
// Customize the RestTemplate..
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setUriTemplateHandler(factory);
|
||||
|
||||
// Customize the WebClient..
|
||||
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val baseUrl = "https://example.com"
|
||||
val factory = DefaultUriBuilderFactory(baseUrl).apply {
|
||||
encodingMode = EncodingMode.TEMPLATE_AND_VALUES
|
||||
}
|
||||
|
||||
// Customize the RestTemplate..
|
||||
val restTemplate = RestTemplate().apply {
|
||||
uriTemplateHandler = factory
|
||||
}
|
||||
|
||||
// Customize the WebClient..
|
||||
val client = WebClient.builder().uriBuilderFactory(factory).build()
|
||||
----
|
||||
|
||||
The `DefaultUriBuilderFactory` implementation uses `UriComponentsBuilder` internally to
|
||||
expand and encode URI templates. As a factory, it provides a single place to configure
|
||||
the approach to encoding, based on one of the below encoding modes:
|
||||
|
||||
* `TEMPLATE_AND_VALUES`: Uses `UriComponentsBuilder#encode()`, corresponding to
|
||||
the first option in the earlier list, to pre-encode the URI template and strictly encode URI variables when
|
||||
expanded.
|
||||
* `VALUES_ONLY`: Does not encode the URI template and, instead, applies strict encoding
|
||||
to URI variables through `UriUtils#encodeUriUriVariables` prior to expanding them into the
|
||||
template.
|
||||
* `URI_COMPONENT`: Uses `UriComponents#encode()`, corresponding to the second option in the earlier list, to
|
||||
encode URI component value _after_ URI variables are expanded.
|
||||
* `NONE`: No encoding is applied.
|
||||
|
||||
The `RestTemplate` is set to `EncodingMode.URI_COMPONENT` for historic
|
||||
reasons and for backwards compatibility. The `WebClient` relies on the default value
|
||||
in `DefaultUriBuilderFactory`, which was changed from `EncodingMode.URI_COMPONENT` in
|
||||
5.0.x to `EncodingMode.TEMPLATE_AND_VALUES` in 5.1.
|
@ -0,0 +1,362 @@
|
||||
[[webflux-cors]]
|
||||
= CORS
|
||||
[.small]#<<web.adoc#mvc-cors, Web MVC>>#
|
||||
|
||||
Spring WebFlux lets you handle CORS (Cross-Origin Resource Sharing). This section
|
||||
describes how to do so.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-cors-intro]]
|
||||
== Introduction
|
||||
[.small]#<<web.adoc#mvc-cors-intro, Web MVC>>#
|
||||
|
||||
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
|
||||
from evil.com should not be able to make AJAX requests to your bank API with your
|
||||
credentials -- for example, withdrawing money from your account!
|
||||
|
||||
Cross-Origin Resource Sharing (CORS) is a https://www.w3.org/TR/cors/[W3C specification]
|
||||
implemented by https://caniuse.com/#feat=cors[most browsers] that lets you specify
|
||||
what kind of cross-domain requests are authorized, rather than using less secure and less
|
||||
powerful workarounds based on IFRAME or JSONP.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-cors-processing]]
|
||||
== Processing
|
||||
[.small]#<<web.adoc#mvc-cors-processing, Web MVC>>#
|
||||
|
||||
The CORS specification distinguishes between preflight, simple, and actual requests.
|
||||
To learn how CORS works, you can read
|
||||
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS[this article], among
|
||||
many others, or see the specification for more details.
|
||||
|
||||
Spring WebFlux `HandlerMapping` implementations provide built-in support for CORS. After successfully
|
||||
mapping a request to a handler, a `HandlerMapping` checks the CORS configuration for the
|
||||
given request and handler and takes further actions. Preflight requests are handled
|
||||
directly, while simple and actual CORS requests are intercepted, validated, and have the
|
||||
required CORS response headers set.
|
||||
|
||||
In order to enable cross-origin requests (that is, the `Origin` header is present and
|
||||
differs from the host of the request), you need to have some explicitly declared CORS
|
||||
configuration. If no matching CORS configuration is found, preflight requests are
|
||||
rejected. No CORS headers are added to the responses of simple and actual CORS requests
|
||||
and, consequently, browsers reject them.
|
||||
|
||||
Each `HandlerMapping` can be
|
||||
{api-spring-framework}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
|
||||
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
|
||||
use the WebFlux Java configuration to declare such mappings, which results in a single,
|
||||
global map passed to all `HandlerMapping` implementations.
|
||||
|
||||
You can combine global CORS configuration at the `HandlerMapping` level with more
|
||||
fine-grained, handler-level CORS configuration. For example, annotated controllers can use
|
||||
class- or method-level `@CrossOrigin` annotations (other handlers can implement
|
||||
`CorsConfigurationSource`).
|
||||
|
||||
The rules for combining global and local configuration are generally additive -- for example,
|
||||
all global and all local origins. For those attributes where only a single value can be
|
||||
accepted, such as `allowCredentials` and `maxAge`, the local overrides the global value. See
|
||||
{api-spring-framework}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
|
||||
for more details.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
To learn more from the source or to make advanced customizations, see:
|
||||
|
||||
* `CorsConfiguration`
|
||||
* `CorsProcessor` and `DefaultCorsProcessor`
|
||||
* `AbstractHandlerMapping`
|
||||
====
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-cors-controller]]
|
||||
== `@CrossOrigin`
|
||||
[.small]#<<web.adoc#mvc-cors-controller, Web MVC>>#
|
||||
|
||||
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
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
public class AccountController {
|
||||
|
||||
@CrossOrigin
|
||||
@GetMapping("/{id}")
|
||||
public Mono<Account> retrieve(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public Mono<Void> remove(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
class AccountController {
|
||||
|
||||
@CrossOrigin
|
||||
@GetMapping("/{id}")
|
||||
suspend fun retrieve(@PathVariable id: Long): Account {
|
||||
// ...
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
suspend fun remove(@PathVariable id: Long) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
By default, `@CrossOrigin` allows:
|
||||
|
||||
* All origins.
|
||||
* All headers.
|
||||
* All HTTP methods to which the controller method is mapped.
|
||||
|
||||
`allowCredentials` is not enabled by default, since that establishes a trust level
|
||||
that exposes sensitive user-specific information (such as cookies and CSRF tokens) and
|
||||
should be used only where appropriate. When it is enabled either `allowOrigins` must be
|
||||
set to one or more specific domain (but not the special value `"*"`) or alternatively
|
||||
the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
|
||||
|
||||
`maxAge` is set to 30 minutes.
|
||||
|
||||
`@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
|
||||
----
|
||||
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
public class AccountController {
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public Mono<Account> retrieve(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public Mono<Void> remove(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@CrossOrigin("https://domain2.com", maxAge = 3600)
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
class AccountController {
|
||||
|
||||
@GetMapping("/{id}")
|
||||
suspend fun retrieve(@PathVariable id: Long): Account {
|
||||
// ...
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
suspend fun remove(@PathVariable id: Long) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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
|
||||
----
|
||||
@CrossOrigin(maxAge = 3600) // <1>
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
public class AccountController {
|
||||
|
||||
@CrossOrigin("https://domain2.com") // <2>
|
||||
@GetMapping("/{id}")
|
||||
public Mono<Account> retrieve(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public Mono<Void> remove(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Using `@CrossOrigin` at the class level.
|
||||
<2> Using `@CrossOrigin` at the method level.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@CrossOrigin(maxAge = 3600) // <1>
|
||||
@RestController
|
||||
@RequestMapping("/account")
|
||||
class AccountController {
|
||||
|
||||
@CrossOrigin("https://domain2.com") // <2>
|
||||
@GetMapping("/{id}")
|
||||
suspend fun retrieve(@PathVariable id: Long): Account {
|
||||
// ...
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
suspend fun remove(@PathVariable id: Long) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Using `@CrossOrigin` at the class level.
|
||||
<2> Using `@CrossOrigin` at the method level.
|
||||
|
||||
|
||||
|
||||
[[webflux-cors-global]]
|
||||
== Global Configuration
|
||||
[.small]#<<web.adoc#mvc-cors-global, Web MVC>>#
|
||||
|
||||
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`
|
||||
mappings individually on any `HandlerMapping`. Most applications, however, use the
|
||||
WebFlux Java configuration to do that.
|
||||
|
||||
By default global configuration enables the following:
|
||||
|
||||
* All origins.
|
||||
* All headers.
|
||||
* `GET`, `HEAD`, and `POST` methods.
|
||||
|
||||
`allowedCredentials` is not enabled by default, since that establishes a trust level
|
||||
that exposes sensitive user-specific information( such as cookies and CSRF tokens) and
|
||||
should be used only where appropriate. When it is enabled either `allowOrigins` must be
|
||||
set to one or more specific domain (but not the special value `"*"`) or alternatively
|
||||
the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
|
||||
|
||||
`maxAge` is set to 30 minutes.
|
||||
|
||||
To enable CORS in the WebFlux Java configuration, you can use the `CorsRegistry` callback,
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins("https://domain2.com")
|
||||
.allowedMethods("PUT", "DELETE")
|
||||
.allowedHeaders("header1", "header2", "header3")
|
||||
.exposedHeaders("header1", "header2")
|
||||
.allowCredentials(true).maxAge(3600);
|
||||
|
||||
// Add more mappings...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun addCorsMappings(registry: CorsRegistry) {
|
||||
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins("https://domain2.com")
|
||||
.allowedMethods("PUT", "DELETE")
|
||||
.allowedHeaders("header1", "header2", "header3")
|
||||
.exposedHeaders("header1", "header2")
|
||||
.allowCredentials(true).maxAge(3600)
|
||||
|
||||
// Add more mappings...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-cors-webfilter]]
|
||||
== CORS `WebFilter`
|
||||
[.small]#<<web.adoc#mvc-cors-filter, Web MVC>>#
|
||||
|
||||
You can apply CORS support through the built-in
|
||||
{api-spring-framework}/web/cors/reactive/CorsWebFilter.html[`CorsWebFilter`], which is a
|
||||
good fit with <<webflux-fn, functional endpoints>>.
|
||||
|
||||
NOTE: If you try to use the `CorsFilter` with Spring Security, keep in mind that Spring
|
||||
Security has
|
||||
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#cors[built-in support]
|
||||
for CORS.
|
||||
|
||||
To configure the filter, you can declare a `CorsWebFilter` bean and pass a
|
||||
`CorsConfigurationSource` to its constructor, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Bean
|
||||
CorsWebFilter corsFilter() {
|
||||
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
|
||||
// Possibly...
|
||||
// config.applyPermitDefaultValues()
|
||||
|
||||
config.setAllowCredentials(true);
|
||||
config.addAllowedOrigin("https://domain1.com");
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
|
||||
return new CorsWebFilter(source);
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Bean
|
||||
fun corsFilter(): CorsWebFilter {
|
||||
|
||||
val config = CorsConfiguration()
|
||||
|
||||
// Possibly...
|
||||
// config.applyPermitDefaultValues()
|
||||
|
||||
config.allowCredentials = true
|
||||
config.addAllowedOrigin("https://domain1.com")
|
||||
config.addAllowedHeader("*")
|
||||
config.addAllowedMethod("*")
|
||||
|
||||
val source = UrlBasedCorsConfigurationSource().apply {
|
||||
registerCorsConfiguration("/**", config)
|
||||
}
|
||||
return CorsWebFilter(source)
|
||||
}
|
||||
----
|
@ -0,0 +1,856 @@
|
||||
[[webflux-fn]]
|
||||
= Functional Endpoints
|
||||
[.small]#<<web.adoc#webmvc-fn, Web MVC>>#
|
||||
|
||||
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.
|
||||
It is an alternative to the annotation-based programming model but otherwise runs on
|
||||
the same <<web-reactive.adoc#webflux-reactive-spring-web>> foundation.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-overview]]
|
||||
== Overview
|
||||
[.small]#<<web.adoc#webmvc-fn-overview, Web MVC>>#
|
||||
|
||||
In WebFlux.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes
|
||||
`ServerRequest` and returns a delayed `ServerResponse` (i.e. `Mono<ServerResponse>`).
|
||||
Both the request and the response object have immutable contracts that offer JDK 8-friendly
|
||||
access to the HTTP request and response.
|
||||
`HandlerFunction` is the equivalent of the body of a `@RequestMapping` method in the
|
||||
annotation-based programming model.
|
||||
|
||||
Incoming requests are routed to a handler function with a `RouterFunction`: a function that
|
||||
takes `ServerRequest` and returns a delayed `HandlerFunction` (i.e. `Mono<HandlerFunction>`).
|
||||
When the router function matches, a handler function is returned; otherwise an empty Mono.
|
||||
`RouterFunction` is the equivalent of a `@RequestMapping` annotation, but with the major
|
||||
difference that router functions provide not just data, but also behavior.
|
||||
|
||||
`RouterFunctions.route()` provides a router builder that facilitates the creation of routers,
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
|
||||
PersonRepository repository = ...
|
||||
PersonHandler handler = new PersonHandler(repository);
|
||||
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
|
||||
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
|
||||
.POST("/person", handler::createPerson)
|
||||
.build();
|
||||
|
||||
|
||||
public class PersonHandler {
|
||||
|
||||
// ...
|
||||
|
||||
public Mono<ServerResponse> listPeople(ServerRequest request) {
|
||||
// ...
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> createPerson(ServerRequest request) {
|
||||
// ...
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> getPerson(ServerRequest request) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val repository: PersonRepository = ...
|
||||
val handler = PersonHandler(repository)
|
||||
|
||||
val route = coRouter { // <1>
|
||||
accept(APPLICATION_JSON).nest {
|
||||
GET("/person/{id}", handler::getPerson)
|
||||
GET("/person", handler::listPeople)
|
||||
}
|
||||
POST("/person", handler::createPerson)
|
||||
}
|
||||
|
||||
|
||||
class PersonHandler(private val repository: PersonRepository) {
|
||||
|
||||
// ...
|
||||
|
||||
suspend fun listPeople(request: ServerRequest): ServerResponse {
|
||||
// ...
|
||||
}
|
||||
|
||||
suspend fun createPerson(request: ServerRequest): ServerResponse {
|
||||
// ...
|
||||
}
|
||||
|
||||
suspend fun getPerson(request: ServerRequest): ServerResponse {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<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 <<web-reactive.adoc#webflux-httphandler, server adapters>>:
|
||||
|
||||
* `RouterFunctions.toHttpHandler(RouterFunction)`
|
||||
* `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)`
|
||||
|
||||
Most applications can run through the WebFlux Java configuration, see <<webflux-fn-running>>.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-handler-functions]]
|
||||
== HandlerFunction
|
||||
[.small]#<<web.adoc#webmvc-fn-handler-functions, Web MVC>>#
|
||||
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
|
||||
access to the HTTP request and response.
|
||||
Both request and response provide https://www.reactive-streams.org[Reactive Streams] back pressure
|
||||
against the body streams.
|
||||
The request body is represented with a Reactor `Flux` or `Mono`.
|
||||
The response body is represented with any Reactive Streams `Publisher`, including `Flux` and `Mono`.
|
||||
For more on that, see <<web-reactive.adoc#webflux-reactive-libraries, Reactive Libraries>>.
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-request]]
|
||||
=== ServerRequest
|
||||
|
||||
`ServerRequest` provides access to the HTTP method, URI, headers, and query parameters,
|
||||
while access to the body is provided through the `body` methods.
|
||||
|
||||
The following example extracts the request body to a `Mono<String>`:
|
||||
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<String> string = request.bodyToMono(String.class);
|
||||
----
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val string = request.awaitBody<String>()
|
||||
----
|
||||
|
||||
|
||||
The following example extracts the body to a `Flux<Person>` (or a `Flow<Person>` in Kotlin),
|
||||
where `Person` objects are decoded from someserialized form, such as JSON or XML:
|
||||
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Flux<Person> people = request.bodyToFlux(Person.class);
|
||||
----
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val people = request.bodyToFlow<Person>()
|
||||
----
|
||||
|
||||
The preceding examples are shortcuts that use the more general `ServerRequest.body(BodyExtractor)`,
|
||||
which accepts the `BodyExtractor` functional strategy interface. The utility class
|
||||
`BodyExtractors` provides access to a number of instances. For example, the preceding examples can
|
||||
also be written as follows:
|
||||
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
|
||||
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
|
||||
----
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val string = request.body(BodyExtractors.toMono(String::class.java)).awaitSingle()
|
||||
val people = request.body(BodyExtractors.toFlux(Person::class.java)).asFlow()
|
||||
----
|
||||
|
||||
The following example shows how to access form data:
|
||||
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<MultiValueMap<String, String> map = request.formData();
|
||||
----
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val map = request.awaitFormData()
|
||||
----
|
||||
|
||||
The following example shows how to access multipart data as a map:
|
||||
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<MultiValueMap<String, Part> map = request.multipartData();
|
||||
----
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val map = request.awaitMultipartData()
|
||||
----
|
||||
|
||||
The following example shows how to access multiparts, one at a time, in streaming fashion:
|
||||
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Flux<Part> parts = request.body(BodyExtractors.toParts());
|
||||
----
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val parts = request.body(BodyExtractors.toParts()).asFlow()
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-response]]
|
||||
=== ServerResponse
|
||||
|
||||
`ServerResponse` provides access to the HTTP response and, since it is immutable, you can use
|
||||
a `build` method to create it. You can use the builder to set the response status, to add response
|
||||
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
|
||||
content:
|
||||
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Person> person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
|
||||
----
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val person: Person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)
|
||||
----
|
||||
|
||||
The following example shows how to build a 201 (CREATED) response with a `Location` header and no body:
|
||||
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
URI location = ...
|
||||
ServerResponse.created(location).build();
|
||||
----
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val location: URI = ...
|
||||
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
|
||||
----
|
||||
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
|
||||
----
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
[[webflux-fn-handler-classes]]
|
||||
=== Handler Classes
|
||||
|
||||
We can write a handler function as a lambda, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
HandlerFunction<ServerResponse> helloWorld =
|
||||
request -> ServerResponse.ok().bodyValue("Hello World");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") }
|
||||
----
|
||||
|
||||
That is convenient, but in an application we need multiple functions, and multiple inline
|
||||
lambda's can get messy.
|
||||
Therefore, it is useful to group related handler functions together into a handler class, which
|
||||
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
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
|
||||
|
||||
public class PersonHandler {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
public PersonHandler(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> listPeople(ServerRequest request) { // <1>
|
||||
Flux<Person> people = repository.allPeople();
|
||||
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> createPerson(ServerRequest request) { // <2>
|
||||
Mono<Person> person = request.bodyToMono(Person.class);
|
||||
return ok().build(repository.savePerson(person));
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> getPerson(ServerRequest request) { // <3>
|
||||
int personId = Integer.valueOf(request.pathVariable("id"));
|
||||
return repository.getPerson(personId)
|
||||
.flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> `listPeople` is a handler function that returns all `Person` objects found in the repository as
|
||||
JSON.
|
||||
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
|
||||
Note that `PersonRepository.savePerson(Person)` returns `Mono<Void>`: an empty `Mono` that emits
|
||||
a completion signal when the person has been read from the request and stored. So we use the
|
||||
`build(Publisher<Void>)` method to send a response when that completion signal is received (that is,
|
||||
when the `Person` has been saved).
|
||||
<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 use `switchIfEmpty(Mono<T>)` to return a 404 Not Found response.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class PersonHandler(private val repository: PersonRepository) {
|
||||
|
||||
suspend fun listPeople(request: ServerRequest): ServerResponse { // <1>
|
||||
val people: Flow<Person> = repository.allPeople()
|
||||
return ok().contentType(APPLICATION_JSON).bodyAndAwait(people);
|
||||
}
|
||||
|
||||
suspend fun createPerson(request: ServerRequest): ServerResponse { // <2>
|
||||
val person = request.awaitBody<Person>()
|
||||
repository.savePerson(person)
|
||||
return ok().buildAndAwait()
|
||||
}
|
||||
|
||||
suspend fun getPerson(request: ServerRequest): ServerResponse { // <3>
|
||||
val personId = request.pathVariable("id").toInt()
|
||||
return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).bodyValueAndAwait(it) }
|
||||
?: ServerResponse.notFound().buildAndAwait()
|
||||
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> `listPeople` is a handler function that returns all `Person` objects found in the repository as
|
||||
JSON.
|
||||
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
|
||||
Note that `PersonRepository.savePerson(Person)` is a suspending function with no return type.
|
||||
<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]]
|
||||
=== Validation
|
||||
|
||||
A functional endpoint can use Spring's <<core.adoc#validation, validation facilities>> to
|
||||
apply validation to the request body. For example, given a custom Spring
|
||||
<<core.adoc#validation, Validator>> implementation for a `Person`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class PersonHandler {
|
||||
|
||||
private final Validator validator = new PersonValidator(); // <1>
|
||||
|
||||
// ...
|
||||
|
||||
public Mono<ServerResponse> createPerson(ServerRequest request) {
|
||||
Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); // <2>
|
||||
return ok().build(repository.savePerson(person));
|
||||
}
|
||||
|
||||
private void validate(Person person) {
|
||||
Errors errors = new BeanPropertyBindingResult(person, "person");
|
||||
validator.validate(person, errors);
|
||||
if (errors.hasErrors()) {
|
||||
throw new ServerWebInputException(errors.toString()); // <3>
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class PersonHandler(private val repository: PersonRepository) {
|
||||
|
||||
private val validator = PersonValidator() // <1>
|
||||
|
||||
// ...
|
||||
|
||||
suspend fun createPerson(request: ServerRequest): ServerResponse {
|
||||
val person = request.awaitBody<Person>()
|
||||
validate(person) // <2>
|
||||
repository.savePerson(person)
|
||||
return ok().buildAndAwait()
|
||||
}
|
||||
|
||||
private fun validate(person: Person) {
|
||||
val errors: Errors = BeanPropertyBindingResult(person, "person");
|
||||
validator.validate(person, errors);
|
||||
if (errors.hasErrors()) {
|
||||
throw ServerWebInputException(errors.toString()) // <3>
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
|
||||
Handlers can also use the standard bean validation API (JSR-303) by creating and injecting
|
||||
a global `Validator` instance based on `LocalValidatorFactoryBean`.
|
||||
See <<core.adoc#validation-beanvalidation, Spring Validation>>.
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-router-functions]]
|
||||
== `RouterFunction`
|
||||
[.small]#<<web.adoc#webmvc-fn-router-functions, Web MVC>>#
|
||||
|
||||
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
|
||||
`RouterFunctions` utility class to create one.
|
||||
`RouterFunctions.route()` (no parameters) provides you with a fluent builder for creating a router
|
||||
function, whereas `RouterFunctions.route(RequestPredicate, HandlerFunction)` offers a direct way
|
||||
to create a router.
|
||||
|
||||
Generally, it is recommended to use the `route()` builder, as it provides
|
||||
convenient short-cuts for typical mapping scenarios without requiring hard-to-discover
|
||||
static imports.
|
||||
For instance, the router function builder offers the method `GET(String, HandlerFunction)` to create a mapping for GET requests; and `POST(String, HandlerFunction)` for POSTs.
|
||||
|
||||
Besides HTTP method-based mapping, the route builder offers a way to introduce additional
|
||||
predicates when mapping to requests.
|
||||
For each HTTP method there is an overloaded variant that takes a `RequestPredicate` as a
|
||||
parameter, though which additional constraints can be expressed.
|
||||
|
||||
|
||||
[[webflux-fn-predicates]]
|
||||
=== Predicates
|
||||
|
||||
You can write your own `RequestPredicate`, but the `RequestPredicates` utility class
|
||||
offers commonly used implementations, based on the request path, HTTP method, content-type,
|
||||
and so on.
|
||||
The following example uses a request predicate to create a constraint based on the `Accept`
|
||||
header:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = RouterFunctions.route()
|
||||
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
|
||||
request -> ServerResponse.ok().bodyValue("Hello World")).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val route = coRouter {
|
||||
GET("/hello-world", accept(TEXT_PLAIN)) {
|
||||
ServerResponse.ok().bodyValueAndAwait("Hello World")
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
You can compose multiple request predicates together by using:
|
||||
|
||||
* `RequestPredicate.and(RequestPredicate)` -- both must match.
|
||||
* `RequestPredicate.or(RequestPredicate)` -- either can match.
|
||||
|
||||
Many of the predicates from `RequestPredicates` are composed.
|
||||
For example, `RequestPredicates.GET(String)` is composed from `RequestPredicates.method(HttpMethod)`
|
||||
and `RequestPredicates.path(String)`.
|
||||
The example shown above also uses two request predicates, as the builder uses
|
||||
`RequestPredicates.GET` internally, and composes that with the `accept` predicate.
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-routes]]
|
||||
=== Routes
|
||||
|
||||
Router functions are evaluated in order: if the first route does not match, the
|
||||
second is evaluated, and so on.
|
||||
Therefore, it makes sense to declare more specific routes before general ones.
|
||||
Note that this behavior is different from the annotation-based programming model, where the
|
||||
"most specific" controller method is picked automatically.
|
||||
|
||||
When using the router function builder, all defined routes are composed into one
|
||||
`RouterFunction` that is returned from `build()`.
|
||||
There are also other ways to compose multiple router functions together:
|
||||
|
||||
* `add(RouterFunction)` on the `RouterFunctions.route()` builder
|
||||
* `RouterFunction.and(RouterFunction)`
|
||||
* `RouterFunction.andRoute(RequestPredicate, HandlerFunction)` -- shortcut for
|
||||
`RouterFunction.and()` with nested `RouterFunctions.route()`.
|
||||
|
||||
The following example shows the composition of four routes:
|
||||
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
|
||||
|
||||
PersonRepository repository = ...
|
||||
PersonHandler handler = new PersonHandler(repository);
|
||||
|
||||
RouterFunction<ServerResponse> otherRoute = ...
|
||||
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // <1>
|
||||
.GET("/person", accept(APPLICATION_JSON), handler::listPeople) // <2>
|
||||
.POST("/person", handler::createPerson) // <3>
|
||||
.add(otherRoute) // <4>
|
||||
.build();
|
||||
----
|
||||
<1> pass:q[`GET /person/{id}`] with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.getPerson`
|
||||
<2> `GET /person` with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.listPeople`
|
||||
<3> `POST /person` with no additional predicates is mapped to
|
||||
`PersonHandler.createPerson`, and
|
||||
<4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.http.MediaType.APPLICATION_JSON
|
||||
|
||||
val repository: PersonRepository = ...
|
||||
val handler = PersonHandler(repository);
|
||||
|
||||
val otherRoute: RouterFunction<ServerResponse> = coRouter { }
|
||||
|
||||
val route = coRouter {
|
||||
GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // <1>
|
||||
GET("/person", accept(APPLICATION_JSON), handler::listPeople) // <2>
|
||||
POST("/person", handler::createPerson) // <3>
|
||||
}.and(otherRoute) // <4>
|
||||
----
|
||||
<1> pass:q[`GET /person/{id}`] with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.getPerson`
|
||||
<2> `GET /person` with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.listPeople`
|
||||
<3> `POST /person` with no additional predicates is mapped to
|
||||
`PersonHandler.createPerson`, and
|
||||
<4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
|
||||
|
||||
|
||||
=== Nested Routes
|
||||
|
||||
It is common for a group of router functions to have a shared predicate, for instance a
|
||||
shared path. In the example above, the shared predicate would be a path predicate that
|
||||
matches `/person`, used by three of the routes. When using annotations, you would remove
|
||||
this duplication by using a type-level `@RequestMapping` annotation that maps to
|
||||
`/person`. In WebFlux.fn, path predicates can be shared through the `path` method on the
|
||||
router function builder. For instance, the last few lines of the example above can be
|
||||
improved in the following way by using nested routes:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.path("/person", builder -> builder // <1>
|
||||
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
|
||||
.GET(accept(APPLICATION_JSON), handler::listPeople)
|
||||
.POST("/person", handler::createPerson))
|
||||
.build();
|
||||
----
|
||||
<1> Note that second parameter of `path` is a consumer that takes the a router builder.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val route = coRouter {
|
||||
"/person".nest {
|
||||
GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
|
||||
GET(accept(APPLICATION_JSON), handler::listPeople)
|
||||
POST("/person", handler::createPerson)
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Though path-based nesting is the most common, you can nest on any kind of predicate by using
|
||||
the `nest` method on the builder.
|
||||
The above still contains some duplication in the form of the shared `Accept`-header predicate.
|
||||
We can further improve by using the `nest` method together with `accept`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.path("/person", b1 -> b1
|
||||
.nest(accept(APPLICATION_JSON), b2 -> b2
|
||||
.GET("/{id}", handler::getPerson)
|
||||
.GET(handler::listPeople))
|
||||
.POST("/person", handler::createPerson))
|
||||
.build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val route = coRouter {
|
||||
"/person".nest {
|
||||
accept(APPLICATION_JSON).nest {
|
||||
GET("/{id}", handler::getPerson)
|
||||
GET(handler::listPeople)
|
||||
POST("/person", handler::createPerson)
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[webflux-fn-running]]
|
||||
== Running a Server
|
||||
[.small]#<<web.adoc#webmvc-fn-running, Web MVC>>#
|
||||
|
||||
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:
|
||||
|
||||
* `RouterFunctions.toHttpHandler(RouterFunction)`
|
||||
* `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)`
|
||||
|
||||
You can then use the returned `HttpHandler` with a number of server adapters by following
|
||||
<<web-reactive.adoc#webflux-httphandler, HttpHandler>> for server-specific instructions.
|
||||
|
||||
A more typical option, also used by Spring Boot, is to run with a
|
||||
<<web-reactive.adoc#webflux-dispatcher-handler, `DispatcherHandler`>>-based setup through the
|
||||
<<web-reactive.adoc#webflux-config>>, which uses Spring configuration to declare the
|
||||
components required to process requests. The WebFlux Java configuration declares the following
|
||||
infrastructure components to support functional endpoints:
|
||||
|
||||
* `RouterFunctionMapping`: Detects one or more `RouterFunction<?>` beans in the Spring
|
||||
configuration, combines them through `RouterFunction.andOther`, and routes requests to the
|
||||
resulting composed `RouterFunction`.
|
||||
* `HandlerFunctionAdapter`: Simple adapter that lets `DispatcherHandler` invoke
|
||||
a `HandlerFunction` that was mapped to a request.
|
||||
* `ServerResponseResultHandler`: Handles the result from the invocation of a
|
||||
`HandlerFunction` by invoking the `writeTo` method of the `ServerResponse`.
|
||||
|
||||
The preceding components let functional endpoints fit within the `DispatcherHandler` request
|
||||
processing lifecycle and also (potentially) run side by side with annotated controllers, if
|
||||
any are declared. It is also how functional endpoints are enabled by the Spring Boot WebFlux
|
||||
starter.
|
||||
|
||||
The following example shows a WebFlux Java configuration (see
|
||||
<<web-reactive.adoc#webflux-dispatcher-handler, DispatcherHandler>> for how to run it):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<?> routerFunctionA() {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<?> routerFunctionB() {
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
@Override
|
||||
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
|
||||
// configure message conversion...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
// configure CORS...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
// configure view resolution for HTML rendering...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
@Bean
|
||||
fun routerFunctionA(): RouterFunction<*> {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun routerFunctionB(): RouterFunction<*> {
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
|
||||
// configure message conversion...
|
||||
}
|
||||
|
||||
override fun addCorsMappings(registry: CorsRegistry) {
|
||||
// configure CORS...
|
||||
}
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
// configure view resolution for HTML rendering...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-fn-handler-filter-function]]
|
||||
== Filtering Handler Functions
|
||||
[.small]#<<web.adoc#webmvc-fn-handler-filter-function, Web MVC>>#
|
||||
|
||||
You can filter handler functions by using the `before`, `after`, or `filter` methods on the routing
|
||||
function builder.
|
||||
With annotations, you can achieve similar functionality by using `@ControllerAdvice`, a `ServletFilter`, or both.
|
||||
The filter will apply to all routes that are built by the builder.
|
||||
This means that filters defined in nested routes do not apply to "top-level" routes.
|
||||
For instance, consider the following example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.path("/person", b1 -> b1
|
||||
.nest(accept(APPLICATION_JSON), b2 -> b2
|
||||
.GET("/{id}", handler::getPerson)
|
||||
.GET(handler::listPeople)
|
||||
.before(request -> ServerRequest.from(request) // <1>
|
||||
.header("X-RequestHeader", "Value")
|
||||
.build()))
|
||||
.POST("/person", handler::createPerson))
|
||||
.after((request, response) -> logResponse(response)) // <2>
|
||||
.build();
|
||||
----
|
||||
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
|
||||
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val route = router {
|
||||
"/person".nest {
|
||||
GET("/{id}", handler::getPerson)
|
||||
GET("", handler::listPeople)
|
||||
before { // <1>
|
||||
ServerRequest.from(it)
|
||||
.header("X-RequestHeader", "Value").build()
|
||||
}
|
||||
POST("/person", handler::createPerson)
|
||||
after { _, response -> // <2>
|
||||
logResponse(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> The `before` filter that adds a custom request header is only applied to the two GET routes.
|
||||
<2> The `after` filter that logs the response is applied to all routes, including the nested ones.
|
||||
|
||||
|
||||
The `filter` method on the router builder takes a `HandlerFilterFunction`: a
|
||||
function that takes a `ServerRequest` and `HandlerFunction` and returns a `ServerResponse`.
|
||||
The handler function parameter represents the next element in the chain.
|
||||
This is typically the handler that is routed to, but it can also be another
|
||||
filter if multiple are applied.
|
||||
|
||||
Now we can add a simple security filter to our route, assuming that we have a `SecurityManager` that
|
||||
can determine whether a particular path is allowed.
|
||||
The following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
SecurityManager securityManager = ...
|
||||
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.path("/person", b1 -> b1
|
||||
.nest(accept(APPLICATION_JSON), b2 -> b2
|
||||
.GET("/{id}", handler::getPerson)
|
||||
.GET(handler::listPeople))
|
||||
.POST("/person", handler::createPerson))
|
||||
.filter((request, next) -> {
|
||||
if (securityManager.allowAccessTo(request.path())) {
|
||||
return next.handle(request);
|
||||
}
|
||||
else {
|
||||
return ServerResponse.status(UNAUTHORIZED).build();
|
||||
}
|
||||
})
|
||||
.build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val securityManager: SecurityManager = ...
|
||||
|
||||
val route = router {
|
||||
("/person" and accept(APPLICATION_JSON)).nest {
|
||||
GET("/{id}", handler::getPerson)
|
||||
GET("", handler::listPeople)
|
||||
POST("/person", handler::createPerson)
|
||||
filter { request, next ->
|
||||
if (securityManager.allowAccessTo(request.path())) {
|
||||
next(request)
|
||||
}
|
||||
else {
|
||||
status(UNAUTHORIZED).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional.
|
||||
We only let the handler function be run when access is allowed.
|
||||
|
||||
Besides using the `filter` method on the router function builder, it is possible to apply a
|
||||
filter to an existing router function via `RouterFunction.filter(HandlerFilterFunction)`.
|
||||
|
||||
NOTE: CORS support for functional endpoints is provided through a dedicated
|
||||
<<webflux-cors.adoc#webflux-cors-webfilter, `CorsWebFilter`>>.
|
@ -0,0 +1,408 @@
|
||||
[[webflux-view]]
|
||||
= View Technologies
|
||||
[.small]#<<web.adoc#mvc-view, Web MVC>>#
|
||||
|
||||
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
|
||||
configuration change. This chapter covers the view technologies integrated with Spring
|
||||
WebFlux. We assume you are already familiar with <<webflux-viewresolution>>.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-view-thymeleaf]]
|
||||
== Thymeleaf
|
||||
[.small]#<<web.adoc#mvc-view-thymeleaf, Web MVC>>#
|
||||
|
||||
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 for independent work on UI templates (for example, by a designer) without the need for a
|
||||
running server. Thymeleaf offers an extensive set of features, and it is actively developed
|
||||
and maintained. For a more complete introduction, see the
|
||||
https://www.thymeleaf.org/[Thymeleaf] project home page.
|
||||
|
||||
The Thymeleaf integration with Spring WebFlux is managed by the Thymeleaf project. The
|
||||
configuration involves a few bean declarations, such as
|
||||
`SpringResourceTemplateResolver`, `SpringWebFluxTemplateEngine`, and
|
||||
`ThymeleafReactiveViewResolver`. For more details, see
|
||||
https://www.thymeleaf.org/documentation.html[Thymeleaf+Spring] and the WebFlux integration
|
||||
http://forum.thymeleaf.org/Thymeleaf-3-0-8-JUST-PUBLISHED-td4030687.html[announcement].
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-view-freemarker]]
|
||||
== FreeMarker
|
||||
[.small]#<<web.adoc#mvc-view-freemarker, Web MVC>>#
|
||||
|
||||
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
|
||||
integration for using Spring WebFlux with FreeMarker templates.
|
||||
|
||||
|
||||
|
||||
[[webflux-view-freemarker-contextconfig]]
|
||||
=== View Configuration
|
||||
[.small]#<<web.adoc#mvc-view-freemarker-contextconfig, Web MVC>>#
|
||||
|
||||
The following example shows how to configure FreeMarker as a view technology:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
registry.freeMarker();
|
||||
}
|
||||
|
||||
// Configure FreeMarker...
|
||||
|
||||
@Bean
|
||||
public FreeMarkerConfigurer freeMarkerConfigurer() {
|
||||
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
|
||||
configurer.setTemplateLoaderPath("classpath:/templates/freemarker");
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
registry.freeMarker()
|
||||
}
|
||||
|
||||
// Configure FreeMarker...
|
||||
|
||||
@Bean
|
||||
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
|
||||
setTemplateLoaderPath("classpath:/templates/freemarker")
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Your templates need to be stored in the directory specified by the `FreeMarkerConfigurer`,
|
||||
shown in the preceding example. Given the preceding configuration, if your controller
|
||||
returns the view name, `welcome`, the resolver looks for the
|
||||
`classpath:/templates/freemarker/welcome.ftl` template.
|
||||
|
||||
|
||||
|
||||
[[webflux-views-freemarker]]
|
||||
=== FreeMarker Configuration
|
||||
[.small]#<<web.adoc#mvc-views-freemarker, Web MVC>>#
|
||||
|
||||
You can pass FreeMarker 'Settings' and 'SharedVariables' directly to the FreeMarker
|
||||
`Configuration` object (which is managed by Spring) by setting the appropriate bean
|
||||
properties on the `FreeMarkerConfigurer` bean. The `freemarkerSettings` property requires
|
||||
a `java.util.Properties` object, and the `freemarkerVariables` property requires a
|
||||
`java.util.Map`. The following example shows how to use a `FreeMarkerConfigurer`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
// ...
|
||||
|
||||
@Bean
|
||||
public FreeMarkerConfigurer freeMarkerConfigurer() {
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
variables.put("xml_escape", new XmlEscape());
|
||||
|
||||
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
|
||||
configurer.setTemplateLoaderPath("classpath:/templates");
|
||||
configurer.setFreemarkerVariables(variables);
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
// ...
|
||||
|
||||
@Bean
|
||||
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
|
||||
setTemplateLoaderPath("classpath:/templates")
|
||||
setFreemarkerVariables(mapOf("xml_escape" to XmlEscape()))
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
See the FreeMarker documentation for details of settings and variables as they apply to
|
||||
the `Configuration` object.
|
||||
|
||||
|
||||
|
||||
[[webflux-view-freemarker-forms]]
|
||||
=== Form Handling
|
||||
[.small]#<<web.adoc#mvc-view-freemarker-forms, Web MVC>>#
|
||||
|
||||
Spring provides a tag library for use in JSPs that contains, among others, a
|
||||
`<spring:bind/>` element. This element primarily lets forms display values from
|
||||
form-backing objects and show the results of failed validations from a `Validator` in the
|
||||
web or business tier. Spring also has support for the same functionality in FreeMarker,
|
||||
with additional convenience macros for generating form input elements themselves.
|
||||
|
||||
|
||||
[[webflux-view-bind-macros]]
|
||||
==== The Bind Macros
|
||||
[.small]#<<web.adoc#mvc-view-bind-macros, Web MVC>>#
|
||||
|
||||
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.
|
||||
|
||||
Some of the macros defined in the Spring templating libraries are considered internal
|
||||
(private), but no such scoping exists in the macro definitions, making all macros visible
|
||||
to calling code and user templates. The following sections concentrate only on the macros
|
||||
you need to directly call from within your templates. If you wish to view the macro code
|
||||
directly, the file is called `spring.ftl` and is in the
|
||||
`org.springframework.web.reactive.result.view.freemarker` package.
|
||||
|
||||
For additional details on binding support, see <<web.adoc#mvc-view-simple-binding, Simple
|
||||
Binding>> for Spring MVC.
|
||||
|
||||
|
||||
[[webflux-views-form-macros]]
|
||||
==== Form Macros
|
||||
|
||||
For details on Spring's form macro support for FreeMarker templates, consult the following
|
||||
sections of the Spring MVC documentation.
|
||||
|
||||
* <<web.adoc#mvc-views-form-macros, Input Macros>>
|
||||
* <<web.adoc#mvc-views-form-macros-input, Input Fields>>
|
||||
* <<web.adoc#mvc-views-form-macros-select, Selection Fields>>
|
||||
* <<web.adoc#mvc-views-form-macros-html-escaping, HTML Escaping>>
|
||||
|
||||
|
||||
|
||||
[[webflux-view-script]]
|
||||
== Script Views
|
||||
[.small]#<<web.adoc#mvc-view-script, Web MVC>>#
|
||||
|
||||
The Spring Framework has a built-in integration for using Spring WebFlux with any
|
||||
templating library that can run on top of the
|
||||
https://www.jcp.org/en/jsr/detail?id=223[JSR-223] Java scripting engine.
|
||||
The following table shows the templating libraries that we have tested on different script engines:
|
||||
|
||||
[%header]
|
||||
|===
|
||||
|Scripting Library |Scripting Engine
|
||||
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby]
|
||||
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|
||||
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |https://kotlinlang.org/[Kotlin]
|
||||
|===
|
||||
|
||||
TIP: The basic rule for integrating any other script engine is that it must implement the
|
||||
`ScriptEngine` and `Invocable` interfaces.
|
||||
|
||||
|
||||
|
||||
[[webflux-view-script-dependencies]]
|
||||
=== Requirements
|
||||
[.small]#<<web.adoc#mvc-view-script-dependencies, Web MVC>>#
|
||||
|
||||
You need to have the script engine on your classpath, the details of which vary by script engine:
|
||||
|
||||
* The https://openjdk.java.net/projects/nashorn/[Nashorn] JavaScript engine is provided with
|
||||
Java 8+. Using the latest update release available is highly recommended.
|
||||
* https://www.jruby.org[JRuby] should be added as a dependency for Ruby support.
|
||||
* https://www.jython.org[Jython] should be added as a dependency for Python support.
|
||||
* `org.jetbrains.kotlin:kotlin-script-util` dependency and a `META-INF/services/javax.script.ScriptEngineFactory`
|
||||
file containing a `org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory`
|
||||
line should be added for Kotlin script support. See
|
||||
https://github.com/sdeleuze/kotlin-script-templating[this example] for more detail.
|
||||
|
||||
You need to have the script templating library. One way to do that for Javascript is
|
||||
through https://www.webjars.org/[WebJars].
|
||||
|
||||
|
||||
|
||||
[[webflux-view-script-integrate]]
|
||||
=== Script Templates
|
||||
[.small]#<<web.adoc#mvc-view-script-integrate, Web MVC>>#
|
||||
|
||||
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.
|
||||
The following example uses Mustache templates and the Nashorn JavaScript engine:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
registry.scriptTemplate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer configurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngineName("nashorn");
|
||||
configurer.setScripts("mustache.js");
|
||||
configurer.setRenderObject("Mustache");
|
||||
configurer.setRenderFunction("render");
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
registry.scriptTemplate()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun configurer() = ScriptTemplateConfigurer().apply {
|
||||
engineName = "nashorn"
|
||||
setScripts("mustache.js")
|
||||
renderObject = "Mustache"
|
||||
renderFunction = "render"
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The `render` function is called with the following parameters:
|
||||
|
||||
* `String template`: The template content
|
||||
* `Map model`: The view model
|
||||
* `RenderingContext renderingContext`: The
|
||||
{api-spring-framework}/web/servlet/view/script/RenderingContext.html[`RenderingContext`]
|
||||
that gives access to the application context, the locale, the template loader, and the
|
||||
URL (since 5.0)
|
||||
|
||||
`Mustache.render()` is natively compatible with this signature, so you can call it directly.
|
||||
|
||||
If your templating technology requires some customization, you can provide a script that
|
||||
implements a custom render function. For example, https://handlebarsjs.com[Handlerbars]
|
||||
needs to compile templates before using them and requires a
|
||||
https://en.wikipedia.org/wiki/Polyfill[polyfill] in order to emulate some
|
||||
browser facilities not available in the server-side script engine.
|
||||
The following example shows how to set a custom render function:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
registry.scriptTemplate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer configurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngineName("nashorn");
|
||||
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
|
||||
configurer.setRenderFunction("render");
|
||||
configurer.setSharedEngine(false);
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
registry.scriptTemplate()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun configurer() = ScriptTemplateConfigurer().apply {
|
||||
engineName = "nashorn"
|
||||
setScripts("polyfill.js", "handlebars.js", "render.js")
|
||||
renderFunction = "render"
|
||||
isSharedEngine = false
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: Setting the `sharedEngine` property to `false` is required when using non-thread-safe
|
||||
script engines with templating libraries not designed for concurrency, such as Handlebars or
|
||||
React running on Nashorn. In that case, Java SE 8 update 60 is required, due to
|
||||
https://bugs.openjdk.java.net/browse/JDK-8076099[this bug], but it is generally
|
||||
recommended to use a recent Java SE patch release in any case.
|
||||
|
||||
`polyfill.js` defines only the `window` object needed by Handlebars to run properly,
|
||||
as the following snippet shows:
|
||||
|
||||
[source,javascript,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var window = {};
|
||||
----
|
||||
|
||||
This basic `render.js` implementation compiles the template before using it. A production
|
||||
ready implementation should also store and reused cached templates or pre-compiled templates.
|
||||
This can be done on the script side, as well as any customization you need (managing
|
||||
template engine configuration for example).
|
||||
The following example shows how compile a template:
|
||||
|
||||
[source,javascript,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
function render(template, model) {
|
||||
var compiledTemplate = Handlebars.compile(template);
|
||||
return compiledTemplate(model);
|
||||
}
|
||||
----
|
||||
|
||||
Check out the Spring Framework unit tests,
|
||||
https://github.com/spring-projects/spring-framework/tree/master/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script[Java], and
|
||||
https://github.com/spring-projects/spring-framework/tree/master/spring-webflux/src/test/resources/org/springframework/web/reactive/result/view/script[resources],
|
||||
for more configuration examples.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-view-httpmessagewriter]]
|
||||
== JSON and XML
|
||||
[.small]#<<web.adoc#mvc-view-jackson, Web MVC>>#
|
||||
|
||||
For <<webflux-multiple-representations>> 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),
|
||||
depending on the content type requested by the client. To support doing so, Spring WebFlux
|
||||
provides the `HttpMessageWriterView`, which you can use to plug in any of the available
|
||||
<<webflux-codecs>> from `spring-web`, such as `Jackson2JsonEncoder`, `Jackson2SmileEncoder`,
|
||||
or `Jaxb2XmlEncoder`.
|
||||
|
||||
Unlike other view technologies, `HttpMessageWriterView` does not require a `ViewResolver`
|
||||
but is instead <<webflux-config-view-resolvers, configured>> as a default view. You can
|
||||
configure one or more such default views, wrapping different `HttpMessageWriter` instances
|
||||
or `Encoder` instances. The one that matches the requested content type is used at runtime.
|
||||
|
||||
In most cases, a model contains multiple attributes. To determine which one to serialize,
|
||||
you can configure `HttpMessageWriterView` with the name of the model attribute to use for
|
||||
rendering. If the model contains only one attribute, that one is used.
|