Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed3d91b929 | |||
| 731ceb8aca | |||
| 6f213fb6a0 | |||
| 131ae59724 | |||
| dd3b3a7c69 | |||
| cbe79a9241 | |||
| ef4a5fb54c | |||
| 8ffaec5d72 | |||
| 578cdb5f7e | |||
| 305a512a55 | |||
| cf036eeaa3 | |||
| fa8a30f753 | |||
| f63ae425bf | |||
| fa20dd820d | |||
| 01cebc85b4 | |||
| 6cf89a1e58 | |||
| e5e8574466 | |||
| a2cabcbb90 | |||
| 9b3625b39e | |||
| 7232e83807 | |||
| 91afab7723 | |||
| e19a8e58fc | |||
| 5f2707a7e3 | |||
| 92ecef74ec | |||
| 11c9373439 | |||
| 6f8db2cfd3 | |||
| bff8cff03b | |||
| c55979b8a9 | |||
| 097c773b91 | |||
| 431cce0b1e | |||
| 0ed87a350c | |||
| 1a43d698d1 | |||
| 94c5481e9e | |||
| 41675587ea | |||
| b14a20161f | |||
| 10e5029e88 | |||
| 4b2552f498 | |||
| 554f15262d | |||
| 9fc8046728 | |||
| 9b60075085 | |||
| aa2ea2055d | |||
| f9c1e6053c | |||
| 2362a142c1 | |||
| b656bb4855 | |||
| c6eb4c0eae | |||
| 57c6cc83da | |||
| 1943aad216 | |||
| 57b9a57452 | |||
| 35f3e5d664 | |||
| d88eccc837 | |||
| 3f8db79e1c | |||
| e30e453bb4 | |||
| edc01f4957 | |||
| a53a17c01a | |||
| 6c95ee2334 | |||
| 6236ed56e4 | |||
| 6455960cf1 | |||
| 3771743082 | |||
| 1e3b4cdc34 | |||
| 889c9c7de4 | |||
| da97ab1dea | |||
| 9ac9cd15fb | |||
| 621f0a6568 | |||
| 6fb9e4a9ea | |||
| 3881954125 | |||
| 42a4409c73 | |||
| 6c5ef6db1e | |||
| f4ccf68458 | |||
| 4aa824d456 | |||
| 6a68b4d299 | |||
| 28cc8b7a08 | |||
| 317eee83ac | |||
| 81e9a8a26d | |||
| a2337b73e0 | |||
| 8422f0f5b7 | |||
| dc3c016712 |
@@ -1,10 +0,0 @@
|
||||
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
|
||||
@@ -1,16 +0,0 @@
|
||||
# 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
|
||||
@@ -1,17 +0,0 @@
|
||||
<!--
|
||||
!!! For Security Vulnerabilities, please go to https://spring.io/security-policy !!!
|
||||
-->
|
||||
**Affects:** \<Spring Framework version>
|
||||
|
||||
---
|
||||
<!--
|
||||
Thanks for taking the time to create an issue. Please read the following:
|
||||
|
||||
- Questions should be asked on Stack Overflow.
|
||||
- For bugs, specify affected versions and explain what you are trying to do.
|
||||
- For enhancements, provide context and describe the problem.
|
||||
|
||||
Issue or Pull Request? Create only one, not both. GitHub treats them as the same.
|
||||
If unsure, start with an issue, and if you submit a pull request later, the
|
||||
issue will be closed as superseded.
|
||||
-->
|
||||
@@ -1,32 +0,0 @@
|
||||
name: Backport Bot
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
push:
|
||||
branches:
|
||||
- '*.x'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Download BackportBot
|
||||
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
|
||||
- name: Backport
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_EVENT: ${{ toJSON(github.event) }}
|
||||
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
|
||||
@@ -1,32 +1,57 @@
|
||||
name: Deploy Docs
|
||||
name: Build and Deploy Documentation
|
||||
run-name: ${{ format('{0} ({1})', github.workflow, github.event.inputs.build-refname || 'all') }}
|
||||
on:
|
||||
push:
|
||||
branches-ignore: [ gh-pages ]
|
||||
tags: '**'
|
||||
repository_dispatch:
|
||||
types: request-build-reference # legacy
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
actions: write
|
||||
inputs:
|
||||
build-refname:
|
||||
description: Enter git refname to build (e.g., 5.7.x).
|
||||
required: false
|
||||
push:
|
||||
branches: docs-build
|
||||
env:
|
||||
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
permissions: read-all
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
build-and-deploy-docs:
|
||||
name: Build and Deploy Documentation
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Check Out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: docs-build
|
||||
fetch-depth: 1
|
||||
- name: Dispatch (partial build)
|
||||
if: github.ref_type == 'branch'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
|
||||
- name: Dispatch (full build)
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD)
|
||||
fetch-depth: 5
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: |
|
||||
25
|
||||
17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Set up refname build
|
||||
if: github.event.inputs.build-refname
|
||||
run: |
|
||||
git fetch --depth 1 https://github.com/$GITHUB_REPOSITORY ${{ github.event.inputs.build-refname }}
|
||||
echo BUILD_REFNAME=${{ github.event.inputs.build-refname }} >> $GITHUB_ENV
|
||||
echo BUILD_VERSION=$(git cat-file --textconv FETCH_HEAD:gradle.properties | sed -n '/^version=/ { s/^version=//;p }') >> $GITHUB_ENV
|
||||
- name: Run Antora
|
||||
run: ./gradlew antora
|
||||
- name: Copy the cache to be included in the site
|
||||
run: cp -rf build/antora/inject-collector-cache-config-extension/.cache build/site/
|
||||
- name: Publish Docs
|
||||
uses: spring-io/spring-doc-actions/rsync-antora-reference@v0.0.22
|
||||
with:
|
||||
docs-username: ${{ secrets.DOCS_USERNAME }}
|
||||
docs-host: ${{ secrets.DOCS_HOST }}
|
||||
docs-ssh-key: ${{ secrets.DOCS_SSH_KEY }}
|
||||
docs-ssh-host-key: ${{ secrets.DOCS_SSH_HOST_KEY }}
|
||||
- name: Bust Cloudflare Cache
|
||||
uses: spring-io/spring-doc-actions/bust-cloudflare-antora-cache@v0.0.22
|
||||
with:
|
||||
context-root: spring-framework
|
||||
cloudflare-zone-id: ${{ secrets.CLOUDFLARE_ZONE_ID }}
|
||||
cloudflare-cache-token: ${{ secrets.CLOUDFLARE_CACHE_TOKEN }}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
@@ -21,7 +21,8 @@ derby.log
|
||||
/build
|
||||
buildSrc/build
|
||||
/spring-*/build
|
||||
/framework-*/build
|
||||
/framework-bom/build
|
||||
/framework-docs/build
|
||||
/integration-tests/build
|
||||
/src/asciidoc/build
|
||||
spring-test/test-output/
|
||||
@@ -51,3 +52,5 @@ atlassian-ide-plugin.xml
|
||||
.vscode/
|
||||
|
||||
cached-antora-playbook.yml
|
||||
|
||||
node_modules
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
Juergen Hoeller <jhoeller@vmware.com>
|
||||
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@pivotal.io>
|
||||
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@gopivotal.com>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@pivotal.io>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@gopivotal.com>
|
||||
Phillip Webb <pwebb@vmware.com>
|
||||
Phillip Webb <pwebb@vmware.com> <pwebb@pivotal.io>
|
||||
Phillip Webb <pwebb@vmware.com> <pwebb@gopivotal.com>
|
||||
Chris Beams <cbeams@vmware.com>
|
||||
Chris Beams <cbeams@vmware.com> <cbeams@pivotal.io>
|
||||
Chris Beams <cbeams@vmware.com> <cbeams@gopivotal.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@pivotal.io>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@gopivotal.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <poutsma@mac.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@pivotal.io>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@gopivotal.com>
|
||||
Dave Syer <dsyer@vmware.com>
|
||||
Dave Syer <dsyer@vmware.com> <dsyer@pivotal.io>
|
||||
Dave Syer <dsyer@vmware.com> <dsyer@gopivotal.com>
|
||||
Dave Syer <dsyer@vmware.com> <david_syer@hotmail.com>
|
||||
Andy Clement <aclement@vmware.com>
|
||||
Andy Clement <aclement@vmware.com> <aclement@pivotal.io>
|
||||
Andy Clement <aclement@vmware.com> <aclement@gopivotal.com>
|
||||
Andy Clement <aclement@vmware.com> <andrew.clement@gmail.com>
|
||||
Sam Brannen <sbrannen@vmware.com>
|
||||
Sam Brannen <sbrannen@vmware.com> <sbrannen@pivotal.io>
|
||||
Sam Brannen <sbrannen@vmware.com> <sam@sambrannen.com>
|
||||
Simon Basle <sbasle@vmware.com>
|
||||
Simon Baslé <sbasle@vmware.com>
|
||||
<dmitry.katsubo@gmail.com> <dmitry.katsubo@gmai.com>
|
||||
Nick Williams <nicholas@nicholaswilliams.net>
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.8.1-librca
|
||||
java=17.0.12-librca
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
= 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/]
|
||||
@@ -1,135 +0,0 @@
|
||||
# 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)
|
||||
* [Ask questions](#ask-questions)
|
||||
* [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
|
||||
|
||||
#### Ask questions
|
||||
|
||||
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 [ask and research](#ask-questions) first.
|
||||
|
||||
If you create 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).
|
||||
|
||||
Many issues are caused by subtle behavior, typos, and unintended configuration.
|
||||
Creating a [Minimal Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example)
|
||||
(starting with https://start.spring.io for example) of the problem helps the team
|
||||
quickly triage your issue and get to the core of the problem.
|
||||
|
||||
#### 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.spring.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 `main` 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 [framework-docs/src/docs/asciidoc](framework-docs/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 :framework-docs:asciidoctor` and then browse the result under
|
||||
`framework-docs/build/docs/ref-docs/html5/index.html`.
|
||||
|
||||
Asciidoctor also supports live editing. For more details see
|
||||
[AsciiDoc Tooling](https://docs.asciidoctor.org/asciidoctor/latest/tooling/).
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
|
||||
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,140 @@
|
||||
= Spring Framework Docs Build
|
||||
|
||||
You're currently viewing the Antora playbook branch.
|
||||
The playbook branch hosts the docs build that is used to build and publish the production docs site.
|
||||
|
||||
The Spring Framework reference docs are built using https://antora.org[Antora].
|
||||
This README covers how to build the docs in a software branch as well as how to build the production docs site locally.
|
||||
|
||||
== Overview
|
||||
|
||||
To prepare your system for building the documentation, <<prerequisites,install the prerequisites>> and then <<build-main,create your workspace and build the main branch documentation>>.
|
||||
Once you've completed those steps, follow the instructions in <<build-branch,Build the 6.0.x branch documentation>> to learn how to build the documentation for a version branch you haven't previously checked out.
|
||||
|
||||
To build the production site documentation on your computer, follow the instructions in <<prerequisites,Prerequisites>>, <<build-main,Build the main branch documentation>>, and then <<build-production,Build the production documentation site>>.
|
||||
|
||||
.Branch checkout instead of worktrees
|
||||
[NOTE]
|
||||
====
|
||||
If you prefer to set up your workspace without worktrees, complete the steps in <<prerequisites,Prerequisites>> and clone the project repository onto your computer.
|
||||
Then follow the instructions in each section starting from the `sdk env || sdk env install` step once you've checked out the desired branch.
|
||||
====
|
||||
|
||||
[#prerequisites]
|
||||
== Prerequisites (everyone)
|
||||
|
||||
These instructions assume you already have basic tools on your system, including bash, zip, unzip, git, and curl.
|
||||
In addition to these basic tools, you need https://sdkman.io/install[SDKMAN!] installed so that the correct JDK is set for each branch.
|
||||
|
||||
. Open your terminal and enter the following command:
|
||||
+
|
||||
--
|
||||
$ curl -s "https://get.sdkman.io" | bash
|
||||
|
||||
This command downloads and installs SDKMAN!
|
||||
Once installation is complete, you should see a command displayed in your terminal that will initiate SDKMAN.
|
||||
--
|
||||
|
||||
. Copy the command displayed in your terminal and run it.
|
||||
`$HOME` is the path unique to your computer (e.g., _home/my-jam/.sdkman/bin/sdkman-init.sh_).
|
||||
|
||||
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||
|
||||
You'll use SDKMAN in the next sections to install and switch to the JDK required for each branch.
|
||||
Now you're ready to <<build-main,create your workspace>>.
|
||||
|
||||
[#build-main]
|
||||
== Build the main branch documentation (writers)
|
||||
|
||||
Your workspace will be the folder that contains the git worktrees of the project.
|
||||
|
||||
. In your terminal, create a directory for the project and then change into that directory.
|
||||
|
||||
$ mkdir spring-framework
|
||||
$ cd spring-framework
|
||||
|
||||
. Clone the project repository and create the primary worktree for the main branch.
|
||||
Then change into the new _main_ folder.
|
||||
|
||||
$ git clone https://github.com/spring-projects/spring-framework main
|
||||
$ cd main
|
||||
|
||||
. Switch to the required JDK using SDKMAN by running the following command:
|
||||
+
|
||||
--
|
||||
$ sdk env || sdk env install
|
||||
|
||||
SDKMAN will switch to the required JDK or install it if it isn't present.
|
||||
--
|
||||
|
||||
. Generate the documentation with Antora using the following command:
|
||||
+
|
||||
--
|
||||
$ ./gradlew -PbuildSrc.skipTests=true :framework-docs:antora
|
||||
|
||||
This command will build the documentation, including any generated attributes, for the main branch.
|
||||
--
|
||||
|
||||
. Navigate to _$HOME/spring-framework/main/framework-docs/build/site/index.html_ to view the generated documentation.
|
||||
|
||||
[#build-branch]
|
||||
== Build the 6.0.x branch documentation (writers)
|
||||
|
||||
NOTE: The instructions in this section assume you've completed the steps in the <<build-main,previous section>>.
|
||||
|
||||
After creating the worktree for the main branch, you can set up a worktree for any other branches you'll work on in the future.
|
||||
In this section, you'll create a worktree for the 6.0.x branch in your project workspace.
|
||||
|
||||
. To add a worktree, you have to be in a worktree.
|
||||
In your terminal, change to the _main_ folder if you aren't already in it, e.g., _$HOME/spring-framework/main_.
|
||||
Set up a worktree for the 6.0.x branch and then change into the new directory by running the following commands:
|
||||
|
||||
$ git worktree add ../6.0.x 6.0.x --track
|
||||
$ cd ../6.0.x
|
||||
|
||||
. Switch to the required JDK or install it.
|
||||
|
||||
$ sdk env || sdk env install
|
||||
|
||||
. Generate the documentation with the following command:
|
||||
+
|
||||
--
|
||||
$ ./gradlew -PbuildSrc.skipTests=true :framework-docs:antora
|
||||
|
||||
This command will build the documentation, including any generated attributes, for the 6.0.x branch.
|
||||
--
|
||||
|
||||
. Navigate to _$HOME/spring-framework/6.0.x/docs/build/site/index.html_ to view the generated documentation.
|
||||
|
||||
[#build-production]
|
||||
== Build the production documentation site (docs manager)
|
||||
|
||||
NOTE: The instructions in this section assume you've <<build-main,prepared your workspace and created the worktree for the main branch>>.
|
||||
|
||||
To build the project's production site, you'll set up a worktree for the docs-build branch of the repository.
|
||||
|
||||
. To add a worktree, you have to be in a worktree.
|
||||
In your terminal, change to the _main_ folder if you aren't already in it, e.g., _$HOME/spring-framework/main_.
|
||||
Run the following command to set up the worktree for the _docs-build_ branch.
|
||||
Then change into the new _docs-build_ directory.
|
||||
|
||||
$ git worktree add ../docs-build docs-build --track
|
||||
$ cd ../docs-build
|
||||
|
||||
. Switch to the required JDK or install it.
|
||||
|
||||
$ sdk env || sdk env install
|
||||
|
||||
. Generate the documentation for the project's production site using the following command:
|
||||
+
|
||||
--
|
||||
$ ./gradlew antora
|
||||
|
||||
This command will build all of the documentation included in the project's production site from the repository on GitHub.
|
||||
|
||||
To build the documentation from the current clone, using any worktrees that are available, use the following command instead:
|
||||
|
||||
$ ./gradlew antora --playbook local-antora-playbook.yml
|
||||
--
|
||||
|
||||
. Navigate to _$HOME/spring-framework/docs-site/build/site/index.html_ to view the generated documentation.
|
||||
@@ -1,38 +0,0 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
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](framework-docs/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.
|
||||
|
||||
## Continuous Integration Builds
|
||||
|
||||
Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation.
|
||||
|
||||
## 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).
|
||||
@@ -1,16 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## JAR signing
|
||||
|
||||
Spring Framework JARs released on Maven Central are signed.
|
||||
You'll find more information about the key here: https://spring.io/GPG-KEY-spring.txt
|
||||
|
||||
## 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://spring.io/security-policy.
|
||||
@@ -0,0 +1,41 @@
|
||||
antora:
|
||||
extensions:
|
||||
- require: '@springio/antora-extensions'
|
||||
root_component_name: 'framework'
|
||||
site:
|
||||
title: Spring Framework
|
||||
url: https://docs.spring.io/spring-framework/reference
|
||||
robots: allow
|
||||
git:
|
||||
ensure_git_suffix: false
|
||||
content:
|
||||
sources:
|
||||
- url: https://github.com/spring-projects/spring-framework
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
|
||||
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
|
||||
# tags: include all releases from 6.2.0 to 9.*.*.
|
||||
tags: ['v6.2.+({0..9})', 'v{7..9}.+({0..9}).+({0..9})?(-{RC,M}*)']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
extensions:
|
||||
- '@asciidoctor/tabs'
|
||||
- '@springio/asciidoctor-extensions'
|
||||
- '@springio/asciidoctor-extensions/include-code-extension'
|
||||
attributes:
|
||||
page-stackoverflow-url: https://stackoverflow.com/questions/tagged/spring
|
||||
page-pagination: ''
|
||||
hide-uri-scheme: '@'
|
||||
tabs-sync-option: '@'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
urls:
|
||||
latest_version_segment_strategy: redirect:to
|
||||
latest_version_segment: ''
|
||||
redirect_facility: httpd
|
||||
runtime:
|
||||
log:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.26/ui-bundle.zip
|
||||
@@ -1,171 +1,12 @@
|
||||
plugins {
|
||||
id 'io.spring.nohttp' version '0.0.11'
|
||||
id 'io.freefair.aspectj' version '8.0.1' apply false
|
||||
// kotlinVersion is managed in gradle.properties
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka' version '1.8.10'
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.49.0'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'me.champeau.jmh' version '0.7.1' apply false
|
||||
id 'base'
|
||||
id 'org.antora' version '1.0.0'
|
||||
}
|
||||
|
||||
ext {
|
||||
moduleProjects = subprojects.findAll { it.name.startsWith("spring-") }
|
||||
javaProjects = subprojects - project(":framework-bom") - project(":framework-platform")
|
||||
antora {
|
||||
options = [clean: true, fetch: true, stacktrace: true]
|
||||
}
|
||||
|
||||
configure(allprojects) { project ->
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://repo.spring.io/milestone"
|
||||
content {
|
||||
// Netty 5 optional support
|
||||
includeGroup 'io.projectreactor.netty'
|
||||
}
|
||||
}
|
||||
if (version.contains('-')) {
|
||||
maven { url "https://repo.spring.io/milestone" }
|
||||
}
|
||||
if (version.endsWith('-SNAPSHOT')) {
|
||||
maven { url "https://repo.spring.io/snapshot" }
|
||||
}
|
||||
}
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
cacheChangingModulesFor 0, "seconds"
|
||||
cacheDynamicVersionsFor 0, "seconds"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configure([rootProject] + javaProjects) { project ->
|
||||
group = "org.springframework"
|
||||
|
||||
apply plugin: "java"
|
||||
apply plugin: "java-test-fixtures"
|
||||
apply plugin: "checkstyle"
|
||||
apply plugin: 'org.springframework.build.conventions'
|
||||
apply from: "${rootDir}/gradle/toolchains.gradle"
|
||||
apply from: "${rootDir}/gradle/ide.gradle"
|
||||
|
||||
configurations {
|
||||
dependencyManagement {
|
||||
canBeConsumed = false
|
||||
canBeResolved = false
|
||||
visible = false
|
||||
}
|
||||
matching { it.name.endsWith("Classpath") }.all { it.extendsFrom(dependencyManagement) }
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
include(["**/*Tests.class", "**/*Test.class"])
|
||||
systemProperty("java.awt.headless", "true")
|
||||
systemProperty("testGroups", project.properties.get("testGroups"))
|
||||
systemProperty("io.netty.leakDetection.level", "paranoid")
|
||||
systemProperty("io.netty5.leakDetectionLevel", "paranoid")
|
||||
systemProperty("io.netty5.leakDetection.targetRecords", "32")
|
||||
systemProperty("io.netty5.buffer.lifecycleTracingEnabled", "true")
|
||||
systemProperty("io.netty5.buffer.leakDetectionEnabled", "true")
|
||||
jvmArgs(["--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED"])
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.12.4"
|
||||
configDirectory.set(rootProject.file("src/checkstyle"))
|
||||
}
|
||||
|
||||
tasks.named("checkstyleMain").configure {
|
||||
maxHeapSize = "1g"
|
||||
}
|
||||
|
||||
tasks.named("checkstyleTest").configure {
|
||||
maxHeapSize = "1g"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependencyManagement(enforcedPlatform(dependencies.project(path: ":framework-platform")))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params")
|
||||
testImplementation("org.junit.platform:junit-platform-suite-api")
|
||||
testImplementation("org.mockito:mockito-core")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter")
|
||||
testImplementation("io.mockk:mockk")
|
||||
testImplementation("org.assertj:assertj-core")
|
||||
// Pull in the latest JUnit 5 Launcher API to ensure proper support in IDEs.
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-suite-engine")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-core")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-jul")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl")
|
||||
// JSR-305 only used for non-required meta-annotations
|
||||
compileOnly("com.google.code.findbugs:jsr305")
|
||||
testCompileOnly("com.google.code.findbugs:jsr305")
|
||||
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.39")
|
||||
}
|
||||
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ and weblogic.* packages
|
||||
"https://www.ibm.com/docs/api/v1/content/SSEQTP_8.5.5/com.ibm.websphere.javadoc.doc/web/apidocs/", // com.ibm.*
|
||||
"https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/", // org.jboss.resource.*
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.14.1/",
|
||||
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/2.14.1/",
|
||||
"https://www.javadoc.io/doc/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.14.1/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.2.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.9.3/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
// Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
|
||||
// but since 6.0 JSR 250 annotations such as @Resource and @PostConstruct have been replaced by their
|
||||
// JakartaEE equivalents in the jakarta.annotation package.
|
||||
"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/"
|
||||
] as String[]
|
||||
}
|
||||
|
||||
configure(moduleProjects) { project ->
|
||||
apply from: "${rootDir}/gradle/spring-module.gradle"
|
||||
}
|
||||
|
||||
configure(rootProject) {
|
||||
description = "Spring Framework"
|
||||
|
||||
apply plugin: "io.spring.nohttp"
|
||||
apply plugin: 'org.springframework.build.api-diff'
|
||||
|
||||
nohttp {
|
||||
source.exclude "**/test-output/**"
|
||||
source.exclude "**/.gradle/**"
|
||||
allowlistFile = project.file("src/nohttp/allowlist.lines")
|
||||
def rootPath = file(rootDir).toPath()
|
||||
def projectDirs = allprojects.collect { it.projectDir } + "${rootDir}/buildSrc"
|
||||
projectDirs.forEach { dir ->
|
||||
[ 'bin', 'build', 'out', '.settings' ]
|
||||
.collect { rootPath.relativize(new File(dir, it).toPath()) }
|
||||
.forEach { source.exclude "$it/**" }
|
||||
[ '.classpath', '.project' ]
|
||||
.collect { rootPath.relativize(new File(dir, it).toPath()) }
|
||||
.forEach { source.exclude "$it" }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("checkstyleNohttp").configure {
|
||||
maxHeapSize = "1g"
|
||||
}
|
||||
|
||||
node {
|
||||
version = '24.15.0'
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
# Spring Framework Build
|
||||
|
||||
This folder contains the custom plugins and conventions for the Spring Framework build.
|
||||
They are declared in the `build.gradle` file in this folder.
|
||||
|
||||
## Build Conventions
|
||||
|
||||
The `org.springframework.build.conventions` plugin applies all conventions to the Framework build:
|
||||
|
||||
* Configuring the Java compiler, see `JavaConventions`
|
||||
* Configuring the Kotlin compiler, see `KotlinConventions`
|
||||
* Configuring testing in the build with `TestConventions`
|
||||
|
||||
|
||||
## Build Plugins
|
||||
|
||||
### Optional dependencies
|
||||
|
||||
The `org.springframework.build.optional-dependencies` plugin creates a new `optional`
|
||||
Gradle configuration - it adds the dependencies to the project's compile and runtime classpath
|
||||
but doesn't affect the classpath of dependent projects.
|
||||
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
|
||||
configurations are preferred.
|
||||
|
||||
### API Diff
|
||||
|
||||
This plugin uses the [Gradle JApiCmp](https://github.com/melix/japicmp-gradle-plugin) plugin
|
||||
to generate API Diff reports for each Spring Framework module. This plugin is applied once on the root
|
||||
project and creates tasks in each framework module. Unlike previous versions of this part of the build,
|
||||
there is no need for checking out a specific tag. The plugin will fetch the JARs we want to compare the
|
||||
current working version with. You can generate the reports for all modules or a single module:
|
||||
|
||||
```
|
||||
./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE
|
||||
./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE
|
||||
```
|
||||
|
||||
The reports are located under `build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/`.
|
||||
|
||||
|
||||
### RuntimeHints Java Agent
|
||||
|
||||
The `spring-core-test` project module contributes the `RuntimeHintsAgent` Java agent.
|
||||
|
||||
The `RuntimeHintsAgentPlugin` Gradle plugin creates a dedicated `"runtimeHintsTest"` test task for each project.
|
||||
This task will detect and execute [tests tagged](https://junit.org/junit5/docs/current/user-guide/#running-tests-build-gradle)
|
||||
with the `"RuntimeHintsTests"` [JUnit tag](https://junit.org/junit5/docs/current/user-guide/#running-tests-tags).
|
||||
In the Spring Framework test suite, those are usually annotated with the `@EnabledIfRuntimeHintsAgent` annotation.
|
||||
|
||||
By default, the agent will instrument all classes located in the `"org.springframework"` package, as they are loaded.
|
||||
The `RuntimeHintsAgentExtension` allows to customize this using a DSL:
|
||||
|
||||
```groovy
|
||||
// this applies the `RuntimeHintsAgentPlugin` to the project
|
||||
plugins {
|
||||
id 'org.springframework.build.runtimehints-agent'
|
||||
}
|
||||
|
||||
// You can configure the agent to include and exclude packages from the instrumentation process.
|
||||
runtimeHintsAgent {
|
||||
includedPackages = ["org.springframework", "io.spring"]
|
||||
excludedPackages = ["org.example"]
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// to use the test infrastructure, the project should also depend on the "spring-core-test" module
|
||||
testImplementation(project(":spring-core-test"))
|
||||
}
|
||||
```
|
||||
|
||||
With this configuration, `./gradlew runtimeHintsTest` will run all tests instrumented by this java agent.
|
||||
The global `./gradlew check` task depends on `runtimeHintsTest`.
|
||||
|
||||
NOTE: the "spring-core-test" module doesn't shade "spring-core" by design, so the agent should never instrument
|
||||
code that doesn't have "spring-core" on its classpath.
|
||||
@@ -1,45 +0,0 @@
|
||||
plugins {
|
||||
id 'java-gradle-plugin'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
ext {
|
||||
def propertiesFile = new File(new File("$projectDir").parentFile, "gradle.properties")
|
||||
propertiesFile.withInputStream {
|
||||
def properties = new Properties()
|
||||
properties.load(it)
|
||||
set("kotlinVersion", properties["kotlinVersion"])
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
|
||||
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}")
|
||||
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
apiDiffPlugin {
|
||||
id = "org.springframework.build.api-diff"
|
||||
implementationClass = "org.springframework.build.api.ApiDiffPlugin"
|
||||
}
|
||||
conventionsPlugin {
|
||||
id = "org.springframework.build.conventions"
|
||||
implementationClass = "org.springframework.build.ConventionsPlugin"
|
||||
}
|
||||
optionalDependenciesPlugin {
|
||||
id = "org.springframework.build.optional-dependencies"
|
||||
implementationClass = "org.springframework.build.optional.OptionalDependenciesPlugin"
|
||||
}
|
||||
runtimeHintsAgentPlugin {
|
||||
id = "org.springframework.build.runtimehints-agent"
|
||||
implementationClass = "org.springframework.build.hint.RuntimeHintsAgentPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
org.gradle.caching=true
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin;
|
||||
|
||||
/**
|
||||
* Plugin to apply conventions to projects that are part of Spring Framework's build.
|
||||
* Conventions are applied in response to various plugins being applied.
|
||||
*
|
||||
* When the {@link JavaBasePlugin} is applied, the conventions in {@link TestConventions}
|
||||
* are applied.
|
||||
* When the {@link JavaBasePlugin} is applied, the conventions in {@link JavaConventions}
|
||||
* are applied.
|
||||
* When the {@link KotlinBasePlugin} is applied, the conventions in {@link KotlinConventions}
|
||||
* are applied.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ConventionsPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
new JavaConventions().apply(project);
|
||||
new KotlinConventions().apply(project);
|
||||
new TestConventions().apply(project);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies conventions for compiling Java sources in Spring Framework.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sam Brannen
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class JavaConventions {
|
||||
|
||||
private static final List<String> COMPILER_ARGS;
|
||||
|
||||
private static final List<String> TEST_COMPILER_ARGS;
|
||||
|
||||
static {
|
||||
List<String> commonCompilerArgs = Arrays.asList(
|
||||
"-Xlint:serial", "-Xlint:cast", "-Xlint:classfile", "-Xlint:dep-ann",
|
||||
"-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides",
|
||||
"-Xlint:path", "-Xlint:processing", "-Xlint:static", "-Xlint:try", "-Xlint:-options",
|
||||
"-parameters"
|
||||
);
|
||||
COMPILER_ARGS = new ArrayList<>();
|
||||
COMPILER_ARGS.addAll(commonCompilerArgs);
|
||||
COMPILER_ARGS.addAll(Arrays.asList(
|
||||
"-Xlint:varargs", "-Xlint:fallthrough", "-Xlint:rawtypes", "-Xlint:deprecation",
|
||||
"-Xlint:unchecked", "-Werror"
|
||||
));
|
||||
TEST_COMPILER_ARGS = new ArrayList<>();
|
||||
TEST_COMPILER_ARGS.addAll(commonCompilerArgs);
|
||||
TEST_COMPILER_ARGS.addAll(Arrays.asList("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes",
|
||||
"-Xlint:-deprecation", "-Xlint:-unchecked"));
|
||||
}
|
||||
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> applyJavaCompileConventions(project));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the common Java compiler options for main sources, test fixture sources, and
|
||||
* test sources.
|
||||
* @param project the current project
|
||||
*/
|
||||
private void applyJavaCompileConventions(Project project) {
|
||||
project.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
});
|
||||
project.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
|
||||
|| compileTask.getName().equals("compileTestFixturesJava"))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions;
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
|
||||
|
||||
/**
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class KotlinConventions {
|
||||
|
||||
void apply(Project project) {
|
||||
project.getPlugins().withId("org.jetbrains.kotlin.jvm",
|
||||
(plugin) -> project.getTasks().withType(KotlinCompile.class, this::configure));
|
||||
}
|
||||
|
||||
private void configure(KotlinCompile compile) {
|
||||
KotlinJvmOptions kotlinOptions = compile.getKotlinOptions();
|
||||
kotlinOptions.setApiVersion("1.7");
|
||||
kotlinOptions.setLanguageVersion("1.7");
|
||||
kotlinOptions.setJvmTarget("17");
|
||||
kotlinOptions.setJavaParameters(true);
|
||||
kotlinOptions.setAllWarningsAsErrors(true);
|
||||
List<String> freeCompilerArgs = new ArrayList<>(compile.getKotlinOptions().getFreeCompilerArgs());
|
||||
freeCompilerArgs.addAll(List.of("-Xsuppress-version-warnings", "-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn"));
|
||||
compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.testretry.TestRetryPlugin;
|
||||
import org.gradle.testretry.TestRetryTaskExtension;
|
||||
|
||||
/**
|
||||
* Conventions that are applied in the presence of the {@link JavaBasePlugin}. When the
|
||||
* plugin is applied:
|
||||
* <ul>
|
||||
* <li>The {@link TestRetryPlugin Test Retry} plugin is applied so that flaky tests
|
||||
* are retried 3 times when running on the CI server.
|
||||
* </ul>
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class TestConventions {
|
||||
|
||||
void apply(Project project) {
|
||||
project.getPlugins().withType(JavaBasePlugin.class, (java) -> configureTestConventions(project));
|
||||
}
|
||||
|
||||
private void configureTestConventions(Project project) {
|
||||
project.getTasks().withType(Test.class,
|
||||
test -> project.getPlugins().withType(TestRetryPlugin.class, testRetryPlugin -> {
|
||||
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
|
||||
testRetry.getFailOnPassedAfterRetry().set(true);
|
||||
testRetry.getMaxRetries().set(isCi() ? 3 : 0);
|
||||
}));
|
||||
}
|
||||
|
||||
private boolean isCi() {
|
||||
return Boolean.parseBoolean(System.getenv("CI"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.build.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.champeau.gradle.japicmp.JapicmpPlugin;
|
||||
import me.champeau.gradle.japicmp.JapicmpTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies the {@code "japicmp-gradle-plugin"}
|
||||
* and create tasks for all subprojects named {@code "spring-*"}, diffing the public API one by one
|
||||
* and creating the reports in {@code "build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/"}.
|
||||
* <p>{@code "./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE"} will output the
|
||||
* reports for the API diff between the baseline version and the current one for all modules.
|
||||
* You can limit the report to a single module with
|
||||
* {@code "./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE"}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ApiDiffPlugin implements Plugin<Project> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApiDiffPlugin.class);
|
||||
|
||||
public static final String TASK_NAME = "apiDiff";
|
||||
|
||||
private static final String BASELINE_VERSION_PROPERTY = "baselineVersion";
|
||||
|
||||
private static final List<String> PACKAGE_INCLUDES = Collections.singletonList("org.springframework.*");
|
||||
|
||||
private static final URI SPRING_MILESTONE_REPOSITORY = URI.create("https://repo.spring.io/milestone");
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
if (project.hasProperty(BASELINE_VERSION_PROPERTY) && project.equals(project.getRootProject())) {
|
||||
project.getPluginManager().apply(JapicmpPlugin.class);
|
||||
project.getPlugins().withType(JapicmpPlugin.class,
|
||||
plugin -> applyApiDiffConventions(project));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyApiDiffConventions(Project project) {
|
||||
String baselineVersion = project.property(BASELINE_VERSION_PROPERTY).toString();
|
||||
project.subprojects(subProject -> {
|
||||
if (subProject.getName().startsWith("spring-")) {
|
||||
createApiDiffTask(baselineVersion, subProject);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createApiDiffTask(String baselineVersion, Project project) {
|
||||
if (isProjectEligible(project)) {
|
||||
// Add Spring Milestone repository for generating diffs against previous milestones
|
||||
project.getRootProject()
|
||||
.getRepositories()
|
||||
.maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(SPRING_MILESTONE_REPOSITORY));
|
||||
JapicmpTask apiDiff = project.getTasks().create(TASK_NAME, JapicmpTask.class);
|
||||
apiDiff.setDescription("Generates an API diff report with japicmp");
|
||||
apiDiff.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
|
||||
|
||||
apiDiff.setOldClasspath(createBaselineConfiguration(baselineVersion, project));
|
||||
TaskProvider<Jar> jar = project.getTasks().withType(Jar.class).named("jar");
|
||||
apiDiff.setNewArchives(project.getLayout().files(jar.get().getArchiveFile().get().getAsFile()));
|
||||
apiDiff.setNewClasspath(getRuntimeClassPath(project));
|
||||
apiDiff.setPackageIncludes(PACKAGE_INCLUDES);
|
||||
apiDiff.setOnlyModified(true);
|
||||
apiDiff.setIgnoreMissingClasses(true);
|
||||
// Ignore Kotlin metadata annotations since they contain
|
||||
// illegal HTML characters and fail the report generation
|
||||
apiDiff.setAnnotationExcludes(Collections.singletonList("@kotlin.Metadata"));
|
||||
|
||||
apiDiff.setHtmlOutputFile(getOutputFile(baselineVersion, project));
|
||||
|
||||
apiDiff.dependsOn(project.getTasks().getByName("jar"));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isProjectEligible(Project project) {
|
||||
return project.getPlugins().hasPlugin(JavaPlugin.class)
|
||||
&& project.getPlugins().hasPlugin(MavenPublishPlugin.class);
|
||||
}
|
||||
|
||||
private Configuration createBaselineConfiguration(String baselineVersion, Project project) {
|
||||
String baseline = String.join(":",
|
||||
project.getGroup().toString(), project.getName(), baselineVersion);
|
||||
Dependency baselineDependency = project.getDependencies().create(baseline + "@jar");
|
||||
Configuration baselineConfiguration = project.getRootProject().getConfigurations().detachedConfiguration(baselineDependency);
|
||||
try {
|
||||
// eagerly resolve the baseline configuration to check whether this is a new Spring module
|
||||
baselineConfiguration.resolve();
|
||||
return baselineConfiguration;
|
||||
}
|
||||
catch (GradleException exception) {
|
||||
logger.warn("Could not resolve {} - assuming this is a new Spring module.", baseline);
|
||||
}
|
||||
return project.getRootProject().getConfigurations().detachedConfiguration();
|
||||
}
|
||||
|
||||
private Configuration getRuntimeClassPath(Project project) {
|
||||
return project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
|
||||
}
|
||||
|
||||
private File getOutputFile(String baseLineVersion, Project project) {
|
||||
Path outDir = Paths.get(project.getRootProject().getBuildDir().getAbsolutePath(),
|
||||
"reports", "api-diff",
|
||||
baseLineVersion + "_to_" + project.getRootProject().getVersion());
|
||||
return project.file(outDir.resolve(project.getName() + ".html").toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.hint;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.provider.SetProperty;
|
||||
|
||||
/**
|
||||
* Entry point to the DSL extension for the {@link RuntimeHintsAgentPlugin} Gradle plugin.
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class RuntimeHintsAgentExtension {
|
||||
|
||||
private final SetProperty<String> includedPackages;
|
||||
|
||||
private final SetProperty<String> excludedPackages;
|
||||
|
||||
public RuntimeHintsAgentExtension(ObjectFactory objectFactory) {
|
||||
this.includedPackages = objectFactory.setProperty(String.class).convention(Collections.singleton("org.springframework"));
|
||||
this.excludedPackages = objectFactory.setProperty(String.class).convention(Collections.emptySet());
|
||||
}
|
||||
|
||||
public SetProperty<String> getIncludedPackages() {
|
||||
return this.includedPackages;
|
||||
}
|
||||
|
||||
public SetProperty<String> getExcludedPackages() {
|
||||
return this.excludedPackages;
|
||||
}
|
||||
|
||||
String asJavaAgentArgument() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
this.includedPackages.get().forEach(packageName -> builder.append('+').append(packageName).append(','));
|
||||
this.excludedPackages.get().forEach(packageName -> builder.append('-').append(packageName).append(','));
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.hint;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that configures the {@code RuntimeHints} Java agent to test tasks.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
|
||||
public static final String RUNTIMEHINTS_TEST_TASK = "runtimeHintsTest";
|
||||
private static final String EXTENSION_NAME = "runtimeHintsAgent";
|
||||
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
|
||||
project.getPlugins().withType(JavaPlugin.class, javaPlugin -> {
|
||||
RuntimeHintsAgentExtension agentExtension = project.getExtensions().create(EXTENSION_NAME,
|
||||
RuntimeHintsAgentExtension.class, project.getObjects());
|
||||
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
|
||||
test.useJUnitPlatform(options -> {
|
||||
options.includeTags("RuntimeHintsTests");
|
||||
});
|
||||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.systemProperty("java.awt.headless", "true");
|
||||
});
|
||||
project.afterEvaluate(p -> {
|
||||
Jar jar = project.getRootProject().project("spring-core-test").getTasks().withType(Jar.class).named("jar").get();
|
||||
agentTest.jvmArgs("-javaagent:" + jar.getArchiveFile().get().getAsFile() + "=" + agentExtension.asJavaAgentArgument());
|
||||
});
|
||||
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.optional;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
|
||||
/**
|
||||
* A {@code Plugin} that adds support for Maven-style optional dependencies. Creates a new
|
||||
* {@code optional} configuration. The {@code optional} configuration is part of the
|
||||
* project's compile and runtime classpaths but does not affect the classpath of
|
||||
* dependent projects.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class OptionalDependenciesPlugin implements Plugin<Project> {
|
||||
|
||||
/**
|
||||
* Name of the {@code optional} configuration.
|
||||
*/
|
||||
public static final String OPTIONAL_CONFIGURATION_NAME = "optional";
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
Configuration optional = project.getConfigurations().create("optional");
|
||||
optional.setCanBeConsumed(false);
|
||||
optional.setCanBeResolved(false);
|
||||
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
|
||||
SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class)
|
||||
.getSourceSets();
|
||||
sourceSets.all((sourceSet) -> {
|
||||
project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).extendsFrom(optional);
|
||||
project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()).extendsFrom(optional);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
package org.springframework.build.shadow;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.component.ModuleComponentSelector;
|
||||
import org.gradle.api.artifacts.query.ArtifactResolutionQuery;
|
||||
import org.gradle.api.artifacts.result.ArtifactResolutionResult;
|
||||
import org.gradle.api.artifacts.result.ComponentArtifactsResult;
|
||||
import org.gradle.api.artifacts.result.DependencyResult;
|
||||
import org.gradle.api.artifacts.result.ResolutionResult;
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.jvm.JvmLibrary;
|
||||
import org.gradle.language.base.artifact.SourcesArtifact;
|
||||
|
||||
/**
|
||||
* Gradle task to add source from shadowed jars into our own source jars.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ShadowSource extends DefaultTask {
|
||||
|
||||
private final DirectoryProperty outputDirectory = getProject().getObjects().directoryProperty();
|
||||
|
||||
private List<Configuration> configurations = new ArrayList<>();
|
||||
|
||||
private final List<Relocation> relocations = new ArrayList<>();
|
||||
|
||||
|
||||
@Classpath
|
||||
@Optional
|
||||
public List<Configuration> getConfigurations() {
|
||||
return this.configurations;
|
||||
}
|
||||
|
||||
public void setConfigurations(List<Configuration> configurations) {
|
||||
this.configurations = configurations;
|
||||
}
|
||||
|
||||
@Nested
|
||||
public List<Relocation> getRelocations() {
|
||||
return this.relocations;
|
||||
}
|
||||
|
||||
public void relocate(String pattern, String destination) {
|
||||
this.relocations.add(new Relocation(pattern, destination));
|
||||
}
|
||||
|
||||
@OutputDirectory
|
||||
DirectoryProperty getOutputDirectory() {
|
||||
return this.outputDirectory;
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void syncSourceJarFiles() {
|
||||
sync(getSourceJarFiles());
|
||||
}
|
||||
|
||||
private List<File> getSourceJarFiles() {
|
||||
List<File> sourceJarFiles = new ArrayList<>();
|
||||
for (Configuration configuration : this.configurations) {
|
||||
ResolutionResult resolutionResult = configuration.getIncoming().getResolutionResult();
|
||||
resolutionResult.getRootComponent().get().getDependencies().forEach(dependency -> {
|
||||
Set<ComponentArtifactsResult> artifactsResults = resolveSourceArtifacts(dependency);
|
||||
for (ComponentArtifactsResult artifactResult : artifactsResults) {
|
||||
artifactResult.getArtifacts(SourcesArtifact.class).forEach(sourceArtifact -> {
|
||||
sourceJarFiles.add(((ResolvedArtifactResult) sourceArtifact).getFile());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return Collections.unmodifiableList(sourceJarFiles);
|
||||
}
|
||||
|
||||
private Set<ComponentArtifactsResult> resolveSourceArtifacts(DependencyResult dependency) {
|
||||
ModuleComponentSelector componentSelector = (ModuleComponentSelector) dependency.getRequested();
|
||||
ArtifactResolutionQuery query = getProject().getDependencies().createArtifactResolutionQuery()
|
||||
.forModule(componentSelector.getGroup(), componentSelector.getModule(), componentSelector.getVersion());
|
||||
return executeQuery(query).getResolvedComponents();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ArtifactResolutionResult executeQuery(ArtifactResolutionQuery query) {
|
||||
return query.withArtifacts(JvmLibrary.class, SourcesArtifact.class).execute();
|
||||
}
|
||||
|
||||
private void sync(List<File> sourceJarFiles) {
|
||||
getProject().sync(spec -> {
|
||||
spec.into(this.outputDirectory);
|
||||
spec.eachFile(this::relocateFile);
|
||||
spec.filter(this::transformContent);
|
||||
spec.exclude("META-INF/**");
|
||||
spec.setIncludeEmptyDirs(false);
|
||||
sourceJarFiles.forEach(sourceJar -> spec.from(zipTree(sourceJar)));
|
||||
});
|
||||
}
|
||||
|
||||
private void relocateFile(FileCopyDetails details) {
|
||||
String path = details.getPath();
|
||||
for (Relocation relocation : this.relocations) {
|
||||
path = relocation.relocatePath(path);
|
||||
}
|
||||
details.setPath(path);
|
||||
}
|
||||
|
||||
private String transformContent(String content) {
|
||||
for (Relocation relocation : this.relocations) {
|
||||
content = relocation.transformContent(content);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private FileTree zipTree(File sourceJar) {
|
||||
return getProject().zipTree(sourceJar);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A single relocation.
|
||||
*/
|
||||
static class Relocation {
|
||||
|
||||
private final String pattern;
|
||||
|
||||
private final String pathPattern;
|
||||
|
||||
private final String destination;
|
||||
|
||||
private final String pathDestination;
|
||||
|
||||
|
||||
Relocation(String pattern, String destination) {
|
||||
this.pattern = pattern;
|
||||
this.pathPattern = pattern.replace('.', '/');
|
||||
this.destination = destination;
|
||||
this.pathDestination = destination.replace('.', '/');
|
||||
}
|
||||
|
||||
|
||||
@Input
|
||||
public String getPattern() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
@Input
|
||||
public String getDestination() {
|
||||
return this.destination;
|
||||
}
|
||||
|
||||
String relocatePath(String path) {
|
||||
return path.replace(this.pathPattern, this.pathDestination);
|
||||
}
|
||||
|
||||
public String transformContent(String content) {
|
||||
return content.replaceAll("\\b" + this.pattern, this.destination);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
== Spring Framework Concourse pipeline
|
||||
|
||||
The Spring Framework uses 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 with a build pipeline
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x[Spring Framework 6.0.x].
|
||||
|
||||
=== 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-6.0.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
----
|
||||
|
||||
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
|
||||
@@ -1,20 +0,0 @@
|
||||
changelog:
|
||||
repository: spring-projects/spring-framework
|
||||
sections:
|
||||
- title: ":star: New Features"
|
||||
labels:
|
||||
- "type: enhancement"
|
||||
- title: ":lady_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"
|
||||
contributors:
|
||||
exclude:
|
||||
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll", "simonbasle"]
|
||||
@@ -1,10 +0,0 @@
|
||||
logging:
|
||||
level:
|
||||
io.spring.concourse: DEBUG
|
||||
spring:
|
||||
main:
|
||||
banner-mode: off
|
||||
sonatype:
|
||||
exclude:
|
||||
- 'build-info\.json'
|
||||
- '.*\.zip'
|
||||
@@ -1,21 +0,0 @@
|
||||
== 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>
|
||||
----
|
||||
@@ -1,11 +0,0 @@
|
||||
FROM ubuntu:jammy-20230816
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
RUN ./setup.sh
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk/java17
|
||||
ENV JDK17 /opt/openjdk/java17
|
||||
ENV JDK20 /opt/openjdk/java20
|
||||
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
java17)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz"
|
||||
;;
|
||||
java20)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
###########################################################
|
||||
# UTILS
|
||||
###########################################################
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install --no-install-recommends -y tzdata ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
|
||||
ln -fs /usr/share/zoneinfo/UTC /etc/localtime
|
||||
dpkg-reconfigure --frontend noninteractive tzdata
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/concourse-java.sh > /opt/concourse-java.sh
|
||||
|
||||
###########################################################
|
||||
# JAVA
|
||||
###########################################################
|
||||
|
||||
mkdir -p /opt/openjdk
|
||||
pushd /opt/openjdk > /dev/null
|
||||
for jdk in java17 java20
|
||||
do
|
||||
JDK_URL=$( /get-jdk-url.sh $jdk )
|
||||
mkdir $jdk
|
||||
pushd $jdk > /dev/null
|
||||
curl -L ${JDK_URL} | tar zx --strip-components=1
|
||||
test -f bin/java
|
||||
test -f bin/javac
|
||||
popd > /dev/null
|
||||
done
|
||||
popd
|
||||
|
||||
###########################################################
|
||||
# GRADLE ENTERPRISE
|
||||
###########################################################
|
||||
cd /
|
||||
mkdir ~/.gradle
|
||||
echo 'systemProp.user.name=concourse' > ~/.gradle/gradle.properties
|
||||
@@ -1,11 +0,0 @@
|
||||
github-repo: "https://github.com/spring-projects/spring-framework.git"
|
||||
github-repo-name: "spring-projects/spring-framework"
|
||||
sonatype-staging-profile: "org.springframework"
|
||||
docker-hub-organization: "springci"
|
||||
artifactory-server: "https://repo.spring.io"
|
||||
branch: "6.0.x"
|
||||
milestone: "6.0.x"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
task-timeout: 1h00m
|
||||
@@ -1,403 +0,0 @@
|
||||
anchors:
|
||||
git-repo-resource-source: &git-repo-resource-source
|
||||
uri: ((github-repo))
|
||||
username: ((github-username))
|
||||
password: ((github-ci-release-token))
|
||||
branch: ((branch))
|
||||
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))
|
||||
sonatype-task-params: &sonatype-task-params
|
||||
SONATYPE_USERNAME: ((sonatype-username))
|
||||
SONATYPE_PASSWORD: ((sonatype-password))
|
||||
SONATYPE_URL: ((sonatype-url))
|
||||
SONATYPE_STAGING_PROFILE: ((sonatype-staging-profile))
|
||||
artifactory-task-params: &artifactory-task-params
|
||||
ARTIFACTORY_SERVER: ((artifactory-server))
|
||||
ARTIFACTORY_USERNAME: ((artifactory-username))
|
||||
ARTIFACTORY_PASSWORD: ((artifactory-password))
|
||||
build-project-task-params: &build-project-task-params
|
||||
BRANCH: ((branch))
|
||||
<<: *gradle-enterprise-task-params
|
||||
docker-resource-source: &docker-resource-source
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-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
|
||||
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: registry-image
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/registry-image-resource
|
||||
tag: 1.5.0
|
||||
- name: artifactory-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: springio/artifactory-resource
|
||||
tag: 0.0.18
|
||||
- name: github-release
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/github-release-resource
|
||||
tag: 1.5.5
|
||||
- name: github-status-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: dpb587/github-status-resource
|
||||
tag: master
|
||||
- name: slack-notification
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: cfcommunity/slack-notification-resource
|
||||
tag: latest
|
||||
resources:
|
||||
- name: git-repo
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
<<: *git-repo-resource-source
|
||||
- name: ci-images-git-repo
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
uri: ((github-repo))
|
||||
branch: ((branch))
|
||||
paths: ["ci/images/*"]
|
||||
- name: ci-image
|
||||
type: registry-image
|
||||
icon: docker
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-ci
|
||||
tag: ((milestone))
|
||||
- name: every-morning
|
||||
type: time
|
||||
icon: alarm
|
||||
source:
|
||||
start: 8:00 AM
|
||||
stop: 9:00 AM
|
||||
location: Europe/Vienna
|
||||
- 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-jdk20-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk20-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-ci-images
|
||||
plan:
|
||||
- get: git-repo
|
||||
- get: ci-images-git-repo
|
||||
trigger: true
|
||||
- task: build-ci-image
|
||||
privileged: true
|
||||
file: git-repo/ci/tasks/build-ci-image.yml
|
||||
output_mapping:
|
||||
image: ci-image
|
||||
vars:
|
||||
ci-image-name: ci-image
|
||||
<<: *docker-resource-source
|
||||
- put: ci-image
|
||||
params:
|
||||
image: ci-image/image.tar
|
||||
- name: build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: true
|
||||
- put: repo-status-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: build-project
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/build-project.yml
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
<<: *build-project-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
|
||||
signing_key: ((signing-key))
|
||||
signing_passphrase: ((signing-passphrase))
|
||||
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:
|
||||
- "/**/framework-docs-*.zip"
|
||||
properties:
|
||||
"zip.name": "spring-framework"
|
||||
"zip.displayname": "Spring Framework"
|
||||
"zip.deployed": "false"
|
||||
- include:
|
||||
- "/**/framework-docs-*-docs.zip"
|
||||
properties:
|
||||
"zip.type": "docs"
|
||||
- include:
|
||||
- "/**/framework-docs-*-dist.zip"
|
||||
properties:
|
||||
"zip.type": "dist"
|
||||
- include:
|
||||
- "/**/framework-docs-*-schema.zip"
|
||||
properties:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- name: jdk20-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- get: every-morning
|
||||
trigger: true
|
||||
- put: repo-status-jdk20-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/check-project.yml
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
TEST_TOOLCHAIN: 20
|
||||
<<: *build-project-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk20-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk20-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: stage-milestone
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: 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: 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
|
||||
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
|
||||
<<: *docker-resource-source
|
||||
- put: github-pre-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
- name: stage-rc
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: 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: 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
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *docker-resource-source
|
||||
<<: *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: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: 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: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-release]
|
||||
params:
|
||||
download_artifacts: true
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *docker-resource-source
|
||||
<<: *artifactory-task-params
|
||||
<<: *sonatype-task-params
|
||||
- name: create-github-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- get: artifactory-repo
|
||||
trigger: true
|
||||
passed: [promote-release]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *docker-resource-source
|
||||
<<: *github-task-params
|
||||
- put: github-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
|
||||
groups:
|
||||
- name: "builds"
|
||||
jobs: ["build", "jdk20-build"]
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
|
||||
- name: "ci-images"
|
||||
jobs: ["build-ci-images"]
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK20 \
|
||||
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check antora
|
||||
popd > /dev/null
|
||||
@@ -1,2 +0,0 @@
|
||||
source /opt/concourse-java.sh
|
||||
setup_symlinks
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CONFIG_DIR=git-repo/ci/config
|
||||
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
|
||||
|
||||
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
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
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 /concourse-release-scripts.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
|
||||
publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; }
|
||||
|
||||
java -jar /concourse-release-scripts.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
|
||||
promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
|
||||
|
||||
echo "Promotion complete"
|
||||
echo $version > version/version
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/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 Builds" > /dev/null
|
||||
git config user.email "spring-builds@users.noreply.github.com" > /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
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: concourse/oci-build-task
|
||||
tag: 0.10.0
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: ci-images-git-repo
|
||||
outputs:
|
||||
- name: image
|
||||
caches:
|
||||
- path: ci-image-cache
|
||||
params:
|
||||
CONTEXT: ci-images-git-repo/ci/images
|
||||
DOCKERFILE: ci-images-git-repo/ci/images/ci-image/Dockerfile
|
||||
DOCKER_HUB_AUTH: ((docker-hub-auth))
|
||||
run:
|
||||
path: /bin/sh
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
mkdir -p /root/.docker
|
||||
cat > /root/.docker/config.json <<EOF
|
||||
{ "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_AUTH" }}}
|
||||
EOF
|
||||
build
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- 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-pr.sh
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
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
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
outputs:
|
||||
- name: distribution-repository
|
||||
- name: git-repo
|
||||
caches:
|
||||
- path: gradle
|
||||
params:
|
||||
BRANCH:
|
||||
CI: true
|
||||
MAIN_TOOLCHAIN:
|
||||
TEST_TOOLCHAIN:
|
||||
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
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/github-changelog-generator
|
||||
tag: '0.0.8'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
outputs:
|
||||
- name: generated-changelog
|
||||
params:
|
||||
GITHUB_ORGANIZATION:
|
||||
GITHUB_REPO:
|
||||
GITHUB_USERNAME:
|
||||
GITHUB_TOKEN:
|
||||
RELEASE_TYPE:
|
||||
run:
|
||||
path: git-repo/ci/scripts/generate-changelog.sh
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/concourse-release-scripts
|
||||
tag: '0.4.0-SNAPSHOT'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
outputs:
|
||||
- name: version
|
||||
params:
|
||||
RELEASE_TYPE:
|
||||
ARTIFACTORY_SERVER:
|
||||
ARTIFACTORY_USERNAME:
|
||||
ARTIFACTORY_PASSWORD:
|
||||
SONATYPE_USER:
|
||||
SONATYPE_PASSWORD:
|
||||
SONATYPE_URL:
|
||||
SONATYPE_STAGING_PROFILE:
|
||||
run:
|
||||
path: git-repo/ci/scripts/promote-version.sh
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
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
|
||||
@@ -1,23 +0,0 @@
|
||||
description = "Spring Framework (Bill of Materials)"
|
||||
|
||||
apply plugin: 'java-platform'
|
||||
apply from: "$rootDir/gradle/publications.gradle"
|
||||
|
||||
group = "org.springframework"
|
||||
|
||||
dependencies {
|
||||
constraints {
|
||||
parent.moduleProjects.sort { "$it.name" }.each {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifactId = 'spring-framework-bom'
|
||||
from components.javaPlatform
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
name: framework
|
||||
version: true
|
||||
title: Spring Framework
|
||||
nav:
|
||||
- modules/ROOT/nav.adoc
|
||||
ext:
|
||||
collector:
|
||||
run:
|
||||
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :framework-docs:generateAntoraResources
|
||||
local: true
|
||||
scan:
|
||||
dir: ./build/generated-antora-resources
|
||||
|
||||
asciidoc:
|
||||
attributes:
|
||||
attribute-missing: 'warn'
|
||||
# FIXME: the copyright is not removed
|
||||
# FIXME: The package is not renamed
|
||||
chomp: 'all'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
spring-framework-main-code: 'https://github.com/spring-projects/spring-framework/tree/main'
|
||||
docs-site: 'https://docs.spring.io'
|
||||
docs-spring: "{docs-site}/spring-framework/docs/{spring-version}"
|
||||
docs-spring-framework: '{docs-site}/spring-framework/docs/{spring-version}'
|
||||
api-spring-framework: '{docs-spring-framework}/javadoc-api/org/springframework'
|
||||
docs-graalvm: 'https://www.graalvm.org/22.3/reference-manual'
|
||||
docs-spring-boot: '{docs-site}/spring-boot/docs/current/reference'
|
||||
docs-spring-gemfire: '{docs-site}/spring-gemfire/docs/current/reference'
|
||||
docs-spring-security: '{docs-site}/spring-security/reference'
|
||||
gh-rsocket: 'https://github.com/rsocket'
|
||||
gh-rsocket-extensions: '{gh-rsocket}/rsocket/blob/master/Extensions'
|
||||
gh-rsocket-java: '{gh-rsocket}/rsocket-java{gh-rsocket}/rsocket-java'
|
||||
@@ -1,232 +0,0 @@
|
||||
plugins {
|
||||
id 'kotlin'
|
||||
id 'io.spring.antora.generate-antora-yml' version '0.0.1'
|
||||
id 'org.antora' version '1.0.0'
|
||||
}
|
||||
|
||||
description = "Spring Framework Docs"
|
||||
|
||||
apply from: "${rootDir}/gradle/publications.gradle"
|
||||
|
||||
|
||||
antora {
|
||||
version = '3.2.0-alpha.2'
|
||||
playbook = 'cached-antora-playbook.yml'
|
||||
playbookProvider {
|
||||
repository = 'spring-projects/spring-framework'
|
||||
branch = 'docs-build'
|
||||
path = 'lib/antora/templates/per-branch-antora-playbook.yml'
|
||||
checkLocalBranch = true
|
||||
}
|
||||
options = ['--clean', '--stacktrace']
|
||||
environment = [
|
||||
'ALGOLIA_API_KEY': '82c7ead946afbac3cf98c32446154691',
|
||||
'ALGOLIA_APP_ID': '244V8V9FGG',
|
||||
'ALGOLIA_INDEX_NAME': 'framework-docs'
|
||||
]
|
||||
dependencies = [
|
||||
'@antora/atlas-extension': '1.0.0-alpha.1',
|
||||
'@antora/collector-extension': '1.0.0-alpha.3',
|
||||
'@asciidoctor/tabs': '1.0.0-beta.3',
|
||||
'@opendevise/antora-release-line-extension': '1.0.0-alpha.2',
|
||||
'@springio/antora-extensions': '1.3.0',
|
||||
'@springio/asciidoctor-extensions': '1.0.0-alpha.9'
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
tasks.named("generateAntoraYml") {
|
||||
asciidocAttributes = project.provider( {
|
||||
return ["spring-version": project.version ]
|
||||
} )
|
||||
}
|
||||
|
||||
tasks.create("generateAntoraResources") {
|
||||
dependsOn 'generateAntoraYml'
|
||||
}
|
||||
|
||||
// Commented out for now:
|
||||
// https://github.com/spring-projects/spring-framework/issues/30481
|
||||
// tasks.named("check") {
|
||||
// dependsOn 'antora'
|
||||
// }
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
javadoc {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://repo.spring.io/release"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-web"))
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
|
||||
implementation(project(":spring-core-test"))
|
||||
implementation("org.assertj:assertj-core")
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = "framework-docs/src/docs/api/overview.html"
|
||||
splitIndex = true
|
||||
links(project.ext.javadocLinks)
|
||||
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
|
||||
addBooleanOption('Werror', true) // fail build on Javadoc warnings
|
||||
}
|
||||
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"
|
||||
*/
|
||||
rootProject.tasks.dokkaHtmlMultiModule.configure {
|
||||
dependsOn {
|
||||
tasks.getByName("api")
|
||||
}
|
||||
moduleName.set("spring-framework")
|
||||
outputDirectory.set(project.file("$buildDir/docs/kdoc"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip all Java docs (javadoc & kdoc) into a single archive
|
||||
*/
|
||||
task docsZip(type: Zip, dependsOn: ['api', rootProject.tasks.dokkaHtmlMultiModule]) {
|
||||
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 (rootProject.tasks.dokkaHtmlMultiModule.outputDirectory) {
|
||||
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.archiveFile)) {
|
||||
into "${baseDir}/docs"
|
||||
}
|
||||
|
||||
from(zipTree(schemaZip.archiveFile)) {
|
||||
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
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifact docsZip
|
||||
artifact schemaZip
|
||||
artifact distZip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 63 KiB |
@@ -1,612 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generated by Microsoft Visio 11.0, SVG Export, v1.0 spring-overview.svg Page-1 -->
|
||||
|
||||
<svg
|
||||
xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="6.4728541in"
|
||||
height="5.9522467in"
|
||||
viewBox="0 0 466.04561 428.56238"
|
||||
xml:space="preserve"
|
||||
class="st5"
|
||||
id="svg5499"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="mvc-splitted-contexts.svg"
|
||||
style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
|
||||
inkscape:export-filename="/Users/seb/Workspace/spring-framework/src/asciidoc/images/mvc-splitted-contexts.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90"><metadata
|
||||
id="metadata5713"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="https://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs5711"><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker9162"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path9164" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker8834"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8836" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker8518"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8520" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker8214"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path8216" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker7922"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7924" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker7641"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7644" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker7373"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7375" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker7117"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path7119" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker6873"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6875" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker6641"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6643" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker6421"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6423" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker6213"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6215" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker6017"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path6019" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5833"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5835" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5661"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5663" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5501"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5503" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5353"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5355" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5217"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5219" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker5093"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path5095" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker4981"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
id="path4983" /></marker><marker
|
||||
inkscape:stockid="Arrow2Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow2Mend"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"><path
|
||||
id="path7394"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(0.6) rotate(180) translate(0,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker8121"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path8123"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker8033"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path8035"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker7957"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7959"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow1Mend"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7376"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow2Lend"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7388"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow1Lend"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7370"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)" /></marker></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="1005"
|
||||
id="namedview5709"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1640492"
|
||||
inkscape:cx="134.86698"
|
||||
inkscape:cy="203.59898"
|
||||
inkscape:window-x="84"
|
||||
inkscape:window-y="176"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="g5503"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" /><v:documentProperties
|
||||
v:langID="1033"
|
||||
v:viewMarkup="false" /><style
|
||||
type="text/css"
|
||||
id="style5501"><![CDATA[
|
||||
.st1 {fill:#969696;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st2 {fill:#dde2cd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st3 {fill:#000000;font-family:Arial;font-size:2.50001em;font-weight:bold}
|
||||
.st4 {font-size:0.333333em;font-weight:normal}
|
||||
.st5 {fill:none;fill-rule:evenodd;font-size:12;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
|
||||
]]></style><g
|
||||
v:mID="0"
|
||||
v:index="1"
|
||||
v:groupContext="foregroundPage"
|
||||
id="g5503"
|
||||
transform="matrix(0.99998201,0,0,1.0824094,-40.812382,-98.908648)"><rect
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6599"
|
||||
width="382.68423"
|
||||
height="146.09897"
|
||||
x="87.884865"
|
||||
y="148.26482" /><v:userDefs><v:ud
|
||||
v:nameU="SchemeName"
|
||||
v:val="VT4(Default)" /></v:userDefs><rect
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5725"
|
||||
width="464.31128"
|
||||
height="374.11411"
|
||||
x="41.684383"
|
||||
y="112.3262" /><title
|
||||
id="title5505">Page-1</title><v:pageProperties
|
||||
v:drawingScale="0.0393701"
|
||||
v:pageScale="0.0393701"
|
||||
v:drawingUnits="24"
|
||||
v:shadowOffsetX="8.50394"
|
||||
v:shadowOffsetY="-8.50394" /><v:layer
|
||||
v:name="Connector"
|
||||
v:index="0" /><rect
|
||||
style="fill:#dde2cd;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.53790233;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5715"
|
||||
width="322.8194"
|
||||
height="43.63184"
|
||||
x="119.95335"
|
||||
y="-135.66222"
|
||||
transform="scale(1,-1)" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
id="text5717"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5719"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">DispatcherServlet</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
id="text6589"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6591"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">Servlet WebApplicationContext</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
id="text6593"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6595"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle">(containing controllers, view resolvers,</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="260.00443"
|
||||
y="212.83057"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
id="tspan6597">and other web-related beans)</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6620"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="114.52653"
|
||||
y="-259.43161"
|
||||
transform="scale(1,-1)" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.89166164;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6622"
|
||||
width="87.843979"
|
||||
height="36.697304"
|
||||
x="223.39864"
|
||||
y="-287.19809"
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-264.81918"
|
||||
x="117.92834"
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6614"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
id="text6616"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6618"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
style="font-size:11.53426838px">Controllers</tspan></text>
|
||||
<text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6624"
|
||||
y="282.70709"
|
||||
x="219.61203"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px"
|
||||
y="282.70709"
|
||||
x="219.61203"
|
||||
id="tspan6626"
|
||||
sodipodi:role="line">ViewResolver</tspan></text>
|
||||
<rect
|
||||
transform="scale(1,-1)"
|
||||
y="-259.30249"
|
||||
x="332.5405"
|
||||
height="36.577778"
|
||||
width="114.4539"
|
||||
id="rect6628"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.0161339;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
id="text6630"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6632"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
style="font-size:11.53426838px">HandlerMapping</tspan></text>
|
||||
<rect
|
||||
y="338.69724"
|
||||
x="87.803261"
|
||||
height="121.5683"
|
||||
width="382.84744"
|
||||
id="rect6634"
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6636"
|
||||
y="376.61673"
|
||||
x="108.61351"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif"
|
||||
y="376.61673"
|
||||
x="108.61351"
|
||||
id="tspan6638"
|
||||
sodipodi:role="line">Root WebApplicationContext</tspan></text>
|
||||
<text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6640"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan6644"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
sodipodi:role="line">(containing middle-tier services, datasources, etc.)</tspan></text>
|
||||
<rect
|
||||
transform="scale(1,-1)"
|
||||
y="-440.06805"
|
||||
x="164.32933"
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6648"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6650"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="167.73116"
|
||||
y="-445.45563"
|
||||
transform="scale(1,-1)" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6652"
|
||||
y="448.55054"
|
||||
x="175.87148"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px"
|
||||
y="448.55054"
|
||||
x="175.87148"
|
||||
id="tspan6654"
|
||||
sodipodi:role="line">Services</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88435173;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6656"
|
||||
width="86.393044"
|
||||
height="36.704323"
|
||||
x="306.86328"
|
||||
y="-439.60837"
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-444.99475"
|
||||
x="310.26624"
|
||||
height="36.701977"
|
||||
width="86.876686"
|
||||
id="rect6658"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88679528;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
id="text6660"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6662"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
style="font-size:11.53426838px">Repositories</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 265.33234,295.60379 c 0,42.65169 0,42.65169 0,0 z"
|
||||
id="path7643"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
|
||||
d="m 270.43505,294.70585 c 0,39.4721 0,39.87903 0,39.87903"
|
||||
id="path7645"
|
||||
inkscape:connector-curvature="0" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
id="text4963"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
id="tspan4965">Delegates if no bean found</tspan></text>
|
||||
</g></svg>
|
||||
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 47 KiB |
@@ -1 +0,0 @@
|
||||
../../../src
|
||||
@@ -1,434 +0,0 @@
|
||||
* xref:overview.adoc[Overview]
|
||||
* xref:core.adoc[]
|
||||
** xref:core/beans.adoc[]
|
||||
*** xref:core/beans/introduction.adoc[]
|
||||
*** xref:core/beans/basics.adoc[]
|
||||
*** xref:core/beans/definition.adoc[]
|
||||
*** xref:core/beans/dependencies.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-collaborators.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-properties-detailed.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-dependson.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-lazy-init.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-autowire.adoc[]
|
||||
**** xref:core/beans/dependencies/factory-method-injection.adoc[]
|
||||
*** xref:core/beans/factory-scopes.adoc[]
|
||||
*** xref:core/beans/factory-nature.adoc[]
|
||||
*** xref:core/beans/child-bean-definitions.adoc[]
|
||||
*** xref:core/beans/factory-extension.adoc[]
|
||||
*** xref:core/beans/annotation-config.adoc[]
|
||||
**** xref:core/beans/annotation-config/autowired.adoc[]
|
||||
**** xref:core/beans/annotation-config/autowired-primary.adoc[]
|
||||
**** xref:core/beans/annotation-config/autowired-qualifiers.adoc[]
|
||||
**** xref:core/beans/annotation-config/generics-as-qualifiers.adoc[]
|
||||
**** xref:core/beans/annotation-config/custom-autowire-configurer.adoc[]
|
||||
**** xref:core/beans/annotation-config/resource.adoc[]
|
||||
**** xref:core/beans/annotation-config/value-annotations.adoc[]
|
||||
**** xref:core/beans/annotation-config/postconstruct-and-predestroy-annotations.adoc[]
|
||||
*** xref:core/beans/classpath-scanning.adoc[]
|
||||
*** xref:core/beans/standard-annotations.adoc[]
|
||||
*** xref:core/beans/java.adoc[]
|
||||
**** xref:core/beans/java/basic-concepts.adoc[]
|
||||
**** xref:core/beans/java/instantiating-container.adoc[]
|
||||
**** xref:core/beans/java/bean-annotation.adoc[]
|
||||
**** xref:core/beans/java/configuration-annotation.adoc[]
|
||||
**** xref:core/beans/java/composing-configuration-classes.adoc[]
|
||||
*** xref:core/beans/environment.adoc[]
|
||||
*** xref:core/beans/context-load-time-weaver.adoc[]
|
||||
*** xref:core/beans/context-introduction.adoc[]
|
||||
*** xref:core/beans/beanfactory.adoc[]
|
||||
** xref:core/resources.adoc[]
|
||||
** xref:core/validation.adoc[]
|
||||
*** xref:core/validation/validator.adoc[]
|
||||
*** xref:core/validation/conversion.adoc[]
|
||||
*** xref:core/validation/beans-beans.adoc[]
|
||||
*** xref:core/validation/convert.adoc[]
|
||||
*** xref:core/validation/format.adoc[]
|
||||
*** xref:core/validation/format-configuring-formatting-globaldatetimeformat.adoc[]
|
||||
*** xref:core/validation/beanvalidation.adoc[]
|
||||
** xref:core/expressions.adoc[]
|
||||
*** xref:core/expressions/evaluation.adoc[]
|
||||
*** xref:core/expressions/beandef.adoc[]
|
||||
*** xref:core/expressions/language-ref.adoc[]
|
||||
**** xref:core/expressions/language-ref/literal.adoc[]
|
||||
**** xref:core/expressions/language-ref/properties-arrays.adoc[]
|
||||
**** xref:core/expressions/language-ref/inline-lists.adoc[]
|
||||
**** xref:core/expressions/language-ref/inline-maps.adoc[]
|
||||
**** xref:core/expressions/language-ref/array-construction.adoc[]
|
||||
**** xref:core/expressions/language-ref/methods.adoc[]
|
||||
**** xref:core/expressions/language-ref/operators.adoc[]
|
||||
**** xref:core/expressions/language-ref/types.adoc[]
|
||||
**** xref:core/expressions/language-ref/constructors.adoc[]
|
||||
**** xref:core/expressions/language-ref/variables.adoc[]
|
||||
**** xref:core/expressions/language-ref/functions.adoc[]
|
||||
**** xref:core/expressions/language-ref/bean-references.adoc[]
|
||||
**** xref:core/expressions/language-ref/operator-ternary.adoc[]
|
||||
**** xref:core/expressions/language-ref/operator-elvis.adoc[]
|
||||
**** xref:core/expressions/language-ref/operator-safe-navigation.adoc[]
|
||||
**** xref:core/expressions/language-ref/collection-selection.adoc[]
|
||||
**** xref:core/expressions/language-ref/collection-projection.adoc[]
|
||||
**** xref:core/expressions/language-ref/templating.adoc[]
|
||||
*** xref:core/expressions/example-classes.adoc[]
|
||||
** xref:core/aop.adoc[]
|
||||
*** xref:core/aop/introduction-defn.adoc[]
|
||||
*** xref:core/aop/introduction-spring-defn.adoc[]
|
||||
*** xref:core/aop/introduction-proxies.adoc[]
|
||||
*** xref:core/aop/ataspectj.adoc[]
|
||||
**** xref:core/aop/ataspectj/aspectj-support.adoc[]
|
||||
**** xref:core/aop/ataspectj/at-aspectj.adoc[]
|
||||
**** xref:core/aop/ataspectj/pointcuts.adoc[]
|
||||
**** xref:core/aop/ataspectj/advice.adoc[]
|
||||
**** xref:core/aop/ataspectj/introductions.adoc[]
|
||||
**** xref:core/aop/ataspectj/instantiation-models.adoc[]
|
||||
**** xref:core/aop/ataspectj/example.adoc[]
|
||||
*** xref:core/aop/schema.adoc[]
|
||||
*** xref:core/aop/choosing.adoc[]
|
||||
*** xref:core/aop/mixing-styles.adoc[]
|
||||
*** xref:core/aop/proxying.adoc[]
|
||||
*** xref:core/aop/aspectj-programmatic.adoc[]
|
||||
*** xref:core/aop/using-aspectj.adoc[]
|
||||
*** xref:core/aop/resources.adoc[]
|
||||
** xref:core/aop-api.adoc[]
|
||||
*** xref:core/aop-api/pointcuts.adoc[]
|
||||
*** xref:core/aop-api/advice.adoc[]
|
||||
*** xref:core/aop-api/advisor.adoc[]
|
||||
*** xref:core/aop-api/pfb.adoc[]
|
||||
*** xref:core/aop-api/concise-proxy.adoc[]
|
||||
*** xref:core/aop-api/prog.adoc[]
|
||||
*** xref:core/aop-api/advised.adoc[]
|
||||
*** xref:core/aop-api/autoproxy.adoc[]
|
||||
*** xref:core/aop-api/targetsource.adoc[]
|
||||
*** xref:core/aop-api/extensibility.adoc[]
|
||||
** xref:core/null-safety.adoc[]
|
||||
** xref:core/databuffer-codec.adoc[]
|
||||
** xref:core/spring-jcl.adoc[]
|
||||
** xref:core/aot.adoc[]
|
||||
** xref:core/appendix.adoc[]
|
||||
*** xref:core/appendix/xsd-schemas.adoc[]
|
||||
*** xref:core/appendix/xml-custom.adoc[]
|
||||
*** xref:core/appendix/application-startup-steps.adoc[]
|
||||
* xref:testing.adoc[]
|
||||
** xref:testing/introduction.adoc[]
|
||||
** xref:testing/unit.adoc[]
|
||||
** xref:testing/integration.adoc[]
|
||||
** xref:testing/support-jdbc.adoc[]
|
||||
** xref:testing/testcontext-framework.adoc[]
|
||||
*** xref:testing/testcontext-framework/key-abstractions.adoc[]
|
||||
*** xref:testing/testcontext-framework/bootstrapping.adoc[]
|
||||
*** xref:testing/testcontext-framework/tel-config.adoc[]
|
||||
*** xref:testing/testcontext-framework/application-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/test-execution-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/ctx-management.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/inheritance.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/property-sources.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/web.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/web-mocks.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/caching.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
|
||||
*** xref:testing/testcontext-framework/tx.adoc[]
|
||||
*** xref:testing/testcontext-framework/executing-sql.adoc[]
|
||||
*** xref:testing/testcontext-framework/parallel-test-execution.adoc[]
|
||||
*** xref:testing/testcontext-framework/support-classes.adoc[]
|
||||
*** xref:testing/testcontext-framework/aot.adoc[]
|
||||
** xref:testing/webtestclient.adoc[]
|
||||
** xref:testing/spring-mvc-test-framework.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-static-imports.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-setup-options.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-setup-steps.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-performing-requests.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-defining-expectations.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/async-requests.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/vs-streaming-response.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-filters.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/vs-end-to-end-integration-tests.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-resources.adoc[]
|
||||
*** xref:testing/spring-mvc-test-framework/server-htmlunit.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/why.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/mah.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/webdriver.adoc[]
|
||||
**** xref:testing/spring-mvc-test-framework/server-htmlunit/geb.adoc[]
|
||||
** xref:testing/spring-mvc-test-client.adoc[]
|
||||
** xref:testing/appendix.adoc[]
|
||||
*** xref:testing/annotations.adoc[]
|
||||
**** xref:testing/annotations/integration-standard.adoc[]
|
||||
**** xref:testing/annotations/integration-spring.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-bootstrapwith.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-commit.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-rollback.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-beforetransaction.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-aftertransaction.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sql.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
|
||||
**** xref:testing/annotations/integration-junit4.adoc[]
|
||||
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
|
||||
**** xref:testing/annotations/integration-meta.adoc[]
|
||||
*** xref:testing/resources.adoc[]
|
||||
* xref:data-access.adoc[]
|
||||
** xref:data-access/transaction.adoc[]
|
||||
*** xref:data-access/transaction/motivation.adoc[]
|
||||
*** xref:data-access/transaction/strategies.adoc[]
|
||||
*** xref:data-access/transaction/tx-resource-synchronization.adoc[]
|
||||
*** xref:data-access/transaction/declarative.adoc[]
|
||||
**** xref:data-access/transaction/declarative/tx-decl-explained.adoc[]
|
||||
**** xref:data-access/transaction/declarative/first-example.adoc[]
|
||||
**** xref:data-access/transaction/declarative/rolling-back.adoc[]
|
||||
**** xref:data-access/transaction/declarative/diff-tx.adoc[]
|
||||
**** xref:data-access/transaction/declarative/txadvice-settings.adoc[]
|
||||
**** xref:data-access/transaction/declarative/annotations.adoc[]
|
||||
**** xref:data-access/transaction/declarative/tx-propagation.adoc[]
|
||||
**** xref:data-access/transaction/declarative/applying-more-than-just-tx-advice.adoc[]
|
||||
**** xref:data-access/transaction/declarative/aspectj.adoc[]
|
||||
*** xref:data-access/transaction/programmatic.adoc[]
|
||||
*** xref:data-access/transaction/tx-decl-vs-prog.adoc[]
|
||||
*** xref:data-access/transaction/event.adoc[]
|
||||
*** xref:data-access/transaction/application-server-integration.adoc[]
|
||||
*** xref:data-access/transaction/solutions-to-common-problems.adoc[]
|
||||
*** xref:data-access/transaction/resources.adoc[]
|
||||
** xref:data-access/dao.adoc[]
|
||||
** xref:data-access/jdbc.adoc[]
|
||||
*** xref:data-access/jdbc/choose-style.adoc[]
|
||||
*** xref:data-access/jdbc/packages.adoc[]
|
||||
*** xref:data-access/jdbc/core.adoc[]
|
||||
*** xref:data-access/jdbc/connections.adoc[]
|
||||
*** xref:data-access/jdbc/advanced.adoc[]
|
||||
*** xref:data-access/jdbc/simple.adoc[]
|
||||
*** xref:data-access/jdbc/object.adoc[]
|
||||
*** xref:data-access/jdbc/parameter-handling.adoc[]
|
||||
*** xref:data-access/jdbc/embedded-database-support.adoc[]
|
||||
*** xref:data-access/jdbc/initializing-datasource.adoc[]
|
||||
** xref:data-access/r2dbc.adoc[]
|
||||
** xref:data-access/orm.adoc[]
|
||||
*** xref:data-access/orm/introduction.adoc[]
|
||||
*** xref:data-access/orm/general.adoc[]
|
||||
*** xref:data-access/orm/hibernate.adoc[]
|
||||
*** xref:data-access/orm/jpa.adoc[]
|
||||
** xref:data-access/oxm.adoc[]
|
||||
** xref:data-access/appendix.adoc[]
|
||||
* xref:web.adoc[]
|
||||
** xref:web/webmvc.adoc[]
|
||||
*** xref:web/webmvc/mvc-servlet.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/context-hierarchy.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/special-bean-types.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/config.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/container-config.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/sequence.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/handlermapping-path.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/handlermapping-interceptor.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/exceptionhandlers.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/viewresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/localeresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/themeresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/multipart.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/logging.adoc[]
|
||||
*** xref:web/webmvc/filters.adoc[]
|
||||
*** xref:web/webmvc/mvc-controller.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-requestmapping.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-methods.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/arguments.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/return-types.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/typeconversion.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/matrix-variables.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/requestparam.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/requestheader.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/cookievalue.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/sessionattributes.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/sessionattribute.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/requestattrib.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/redirecting-passing-data.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/flash-attributes.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/httpentity.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/responsebody.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/responseentity.adoc[]
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/jackson.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-modelattrib-methods.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-initbinder.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-exceptionhandler.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-advice.adoc[]
|
||||
*** xref:web/webmvc-functional.adoc[]
|
||||
*** xref:web/webmvc/mvc-uri-building.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-async.adoc[]
|
||||
*** xref:web/webmvc-cors.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webmvc/mvc-security.adoc[]
|
||||
*** xref:web/webmvc/mvc-caching.adoc[]
|
||||
*** xref:web/webmvc-view.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-thymeleaf.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-freemarker.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-groovymarkup.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-script.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-jsp.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-feeds.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-document.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-jackson.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-xml-marshalling.adoc[]
|
||||
**** xref:web/webmvc-view/mvc-xslt.adoc[]
|
||||
*** xref:web/webmvc/mvc-config.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/enable.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/customize.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/conversion.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/validation.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/interceptors.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/content-negotiation.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/message-converters.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/view-controller.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/view-resolvers.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/static-resources.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/default-servlet-handler.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/path-matching.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-java.adoc[]
|
||||
**** xref:web/webmvc/mvc-config/advanced-xml.adoc[]
|
||||
*** xref:web/webmvc/mvc-http2.adoc[]
|
||||
** xref:web/webmvc-client.adoc[]
|
||||
** xref:web/webmvc-test.adoc[]
|
||||
** xref:web/websocket.adoc[]
|
||||
*** xref:web/websocket/server.adoc[]
|
||||
*** xref:web/websocket/fallback.adoc[]
|
||||
*** xref:web/websocket/stomp.adoc[]
|
||||
**** xref:web/websocket/stomp/overview.adoc[]
|
||||
**** xref:web/websocket/stomp/benefits.adoc[]
|
||||
**** xref:web/websocket/stomp/enable.adoc[]
|
||||
**** xref:web/websocket/stomp/server-config.adoc[]
|
||||
**** xref:web/websocket/stomp/message-flow.adoc[]
|
||||
**** xref:web/websocket/stomp/handle-annotations.adoc[]
|
||||
**** xref:web/websocket/stomp/handle-send.adoc[]
|
||||
**** xref:web/websocket/stomp/handle-simple-broker.adoc[]
|
||||
**** xref:web/websocket/stomp/handle-broker-relay.adoc[]
|
||||
**** xref:web/websocket/stomp/handle-broker-relay-configure.adoc[]
|
||||
**** xref:web/websocket/stomp/destination-separator.adoc[]
|
||||
**** xref:web/websocket/stomp/authentication.adoc[]
|
||||
**** xref:web/websocket/stomp/authentication-token-based.adoc[]
|
||||
**** xref:web/websocket/stomp/authorization.adoc[]
|
||||
**** xref:web/websocket/stomp/user-destination.adoc[]
|
||||
**** xref:web/websocket/stomp/ordered-messages.adoc[]
|
||||
**** xref:web/websocket/stomp/application-context-events.adoc[]
|
||||
**** xref:web/websocket/stomp/interceptors.adoc[]
|
||||
**** xref:web/websocket/stomp/client.adoc[]
|
||||
**** xref:web/websocket/stomp/scope.adoc[]
|
||||
**** xref:web/websocket/stomp/configuration-performance.adoc[]
|
||||
**** xref:web/websocket/stomp/stats.adoc[]
|
||||
**** xref:web/websocket/stomp/testing.adoc[]
|
||||
** xref:web/integration.adoc[]
|
||||
* xref:web-reactive.adoc[]
|
||||
** xref:web/webflux.adoc[]
|
||||
*** xref:web/webflux/new-framework.adoc[]
|
||||
*** xref:web/webflux/reactive-spring.adoc[]
|
||||
*** xref:web/webflux/dispatcher-handler.adoc[]
|
||||
*** xref:web/webflux/controller.adoc[]
|
||||
**** xref:web/webflux/controller/ann.adoc[]
|
||||
**** xref:web/webflux/controller/ann-requestmapping.adoc[]
|
||||
**** xref:web/webflux/controller/ann-methods.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/arguments.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/return-types.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/typeconversion.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/matrix-variables.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/requestparam.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/requestheader.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/cookievalue.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/sessionattributes.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/sessionattribute.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/requestattrib.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/multipart-forms.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/requestbody.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/httpentity.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/responsebody.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/responseentity.adoc[]
|
||||
***** xref:web/webflux/controller/ann-methods/jackson.adoc[]
|
||||
**** xref:web/webflux/controller/ann-modelattrib-methods.adoc[]
|
||||
**** xref:web/webflux/controller/ann-initbinder.adoc[]
|
||||
**** xref:web/webflux/controller/ann-exceptions.adoc[]
|
||||
**** xref:web/webflux/controller/ann-advice.adoc[]
|
||||
*** xref:web/webflux-functional.adoc[]
|
||||
*** xref:web/webflux/uri-building.adoc[]
|
||||
*** xref:web/webflux-cors.adoc[]
|
||||
*** xref:web/webflux/ann-rest-exceptions.adoc[]
|
||||
*** xref:web/webflux/security.adoc[]
|
||||
*** xref:web/webflux/caching.adoc[]
|
||||
*** xref:web/webflux-view.adoc[]
|
||||
*** xref:web/webflux/config.adoc[]
|
||||
*** xref:web/webflux/http2.adoc[]
|
||||
** xref:web/webflux-webclient.adoc[]
|
||||
*** xref:web/webflux-webclient/client-builder.adoc[]
|
||||
*** xref:web/webflux-webclient/client-retrieve.adoc[]
|
||||
*** xref:web/webflux-webclient/client-exchange.adoc[]
|
||||
*** xref:web/webflux-webclient/client-body.adoc[]
|
||||
*** xref:web/webflux-webclient/client-filter.adoc[]
|
||||
*** xref:web/webflux-webclient/client-attributes.adoc[]
|
||||
*** xref:web/webflux-webclient/client-context.adoc[]
|
||||
*** xref:web/webflux-webclient/client-synchronous.adoc[]
|
||||
*** xref:web/webflux-webclient/client-testing.adoc[]
|
||||
** xref:web/webflux-http-interface-client.adoc[]
|
||||
** xref:web/webflux-websocket.adoc[]
|
||||
** xref:web/webflux-test.adoc[]
|
||||
** xref:rsocket.adoc[]
|
||||
** xref:web/webflux-reactive-libraries.adoc[]
|
||||
* xref:integration.adoc[]
|
||||
** xref:integration/rest-clients.adoc[]
|
||||
** xref:integration/jms.adoc[]
|
||||
*** xref:integration/jms/using.adoc[]
|
||||
*** xref:integration/jms/sending.adoc[]
|
||||
*** xref:integration/jms/receiving.adoc[]
|
||||
*** xref:integration/jms/jca-message-endpoint-manager.adoc[]
|
||||
*** xref:integration/jms/annotated.adoc[]
|
||||
*** xref:integration/jms/namespace.adoc[]
|
||||
** xref:integration/jmx.adoc[]
|
||||
*** xref:integration/jmx/exporting.adoc[]
|
||||
*** xref:integration/jmx/interface.adoc[]
|
||||
*** xref:integration/jmx/naming.adoc[]
|
||||
*** xref:integration/jmx/jsr160.adoc[]
|
||||
*** xref:integration/jmx/proxy.adoc[]
|
||||
*** xref:integration/jmx/notifications.adoc[]
|
||||
*** xref:integration/jmx/resources.adoc[]
|
||||
** xref:integration/email.adoc[]
|
||||
** xref:integration/scheduling.adoc[]
|
||||
** xref:integration/cache.adoc[]
|
||||
*** xref:integration/cache/strategies.adoc[]
|
||||
*** xref:integration/cache/annotations.adoc[]
|
||||
*** xref:integration/cache/jsr-107.adoc[]
|
||||
*** xref:integration/cache/declarative-xml.adoc[]
|
||||
*** xref:integration/cache/store-configuration.adoc[]
|
||||
*** xref:integration/cache/plug.adoc[]
|
||||
*** xref:integration/cache/specific-config.adoc[]
|
||||
** xref:integration/observability.adoc[]
|
||||
** xref:integration/appendix.adoc[]
|
||||
* xref:languages.adoc[]
|
||||
** xref:languages/kotlin.adoc[]
|
||||
*** xref:languages/kotlin/requirements.adoc[]
|
||||
*** xref:languages/kotlin/extensions.adoc[]
|
||||
*** xref:languages/kotlin/null-safety.adoc[]
|
||||
*** xref:languages/kotlin/classes-interfaces.adoc[]
|
||||
*** xref:languages/kotlin/annotations.adoc[]
|
||||
*** xref:languages/kotlin/bean-definition-dsl.adoc[]
|
||||
*** xref:languages/kotlin/web.adoc[]
|
||||
*** xref:languages/kotlin/coroutines.adoc[]
|
||||
*** xref:languages/kotlin/spring-projects-in.adoc[]
|
||||
*** xref:languages/kotlin/getting-started.adoc[]
|
||||
*** xref:languages/kotlin/resources.adoc[]
|
||||
** xref:languages/groovy.adoc[]
|
||||
** xref:languages/dynamic.adoc[]
|
||||
* xref:appendix.adoc[]
|
||||
* https://github.com/spring-projects/spring-framework/wiki[Wiki]
|
||||
@@ -1,78 +0,0 @@
|
||||
[[appendix]]
|
||||
= Appendix
|
||||
|
||||
This part of the reference documentation covers topics that apply to multiple modules
|
||||
within the core Spring Framework.
|
||||
|
||||
|
||||
[[appendix-spring-properties]]
|
||||
== Spring Properties
|
||||
|
||||
{api-spring-framework}/core/SpringProperties.html[`SpringProperties`] is a static holder
|
||||
for properties that control certain low-level aspects of the Spring Framework. Users can
|
||||
configure these properties via JVM system properties or programmatically via the
|
||||
`SpringProperties.setProperty(String key, String value)` method. The latter may be
|
||||
necessary if the deployment environment disallows custom JVM system properties. As an
|
||||
alternative, these properties may be configured in a `spring.properties` file in the root
|
||||
of the classpath -- for example, deployed within the application's JAR file.
|
||||
|
||||
The following table lists all currently supported Spring properties.
|
||||
|
||||
.Supported Spring Properties
|
||||
|===
|
||||
| Name | Description
|
||||
|
||||
| `spring.beaninfo.ignore`
|
||||
| Instructs Spring to use the `Introspector.IGNORE_ALL_BEANINFO` mode when calling the
|
||||
JavaBeans `Introspector`. See
|
||||
{api-spring-framework}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
|
||||
for details.
|
||||
|
||||
| `spring.expression.compiler.mode`
|
||||
| The mode to use when compiling expressions for the
|
||||
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
|
||||
|
||||
| `spring.getenv.ignore`
|
||||
| Instructs Spring to ignore operating system environment variables if a Spring
|
||||
`Environment` property -- for example, a placeholder in a configuration String -- isn't
|
||||
resolvable otherwise. See
|
||||
{api-spring-framework}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
|
||||
for details.
|
||||
|
||||
| `spring.index.ignore`
|
||||
| Instructs Spring to ignore the components index located in
|
||||
`META-INF/spring.components`. See xref:core/beans/classpath-scanning.adoc#beans-scanning-index[Generating an Index of Candidate Components]
|
||||
.
|
||||
|
||||
| `spring.jdbc.getParameterType.ignore`
|
||||
| Instructs Spring to ignore `java.sql.ParameterMetaData.getParameterType` completely.
|
||||
See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operations with a List of Objects].
|
||||
|
||||
| `spring.jndi.ignore`
|
||||
| Instructs Spring to ignore a default JNDI environment, as an optimization for scenarios
|
||||
where nothing is ever to be found for such JNDI fallback searches to begin with, avoiding
|
||||
the repeated JNDI lookup overhead. See
|
||||
{api-spring-framework}++/jndi/JndiLocatorDelegate.html#IGNORE_JNDI_PROPERTY_NAME++[`JndiLocatorDelegate`]
|
||||
for details.
|
||||
|
||||
| `spring.objenesis.ignore`
|
||||
| Instructs Spring to ignore Objenesis, not even attempting to use it. See
|
||||
{api-spring-framework}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
|
||||
for details.
|
||||
|
||||
| `spring.test.constructor.autowire.mode`
|
||||
| The default _test constructor autowire mode_ to use if `@TestConstructor` is not present
|
||||
on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[Changing the default test constructor autowire mode]
|
||||
.
|
||||
|
||||
| `spring.test.context.cache.maxSize`
|
||||
| The maximum size of the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching].
|
||||
|
||||
| `spring.test.enclosing.configuration`
|
||||
| The default _enclosing configuration inheritance mode_ to use if
|
||||
`@NestedTestConfiguration` is not present on a test class. See
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[Changing the default enclosing configuration inheritance mode]
|
||||
.
|
||||
|
||||
|===
|
||||
@@ -1,20 +0,0 @@
|
||||
// Spring Portfolio
|
||||
:docs-site: https://docs.spring.io
|
||||
:docs-spring-boot: {docs-site}/spring-boot/docs/current/reference
|
||||
:docs-spring-gemfire: {docs-site}/spring-gemfire/docs/current/reference
|
||||
:docs-spring-security: {docs-site}/spring-security/reference
|
||||
// spring-asciidoctor-backends Settings
|
||||
:chomp: default headers packages
|
||||
:fold: all
|
||||
// Spring Framework
|
||||
:docs-spring-framework: {docs-site}/spring-framework/docs/{spring-version}
|
||||
:api-spring-framework: {docs-spring-framework}/javadoc-api/org/springframework
|
||||
:docs-java: {docdir}/../../main/java/org/springframework/docs
|
||||
:docs-kotlin: {docdir}/../../main/kotlin/org/springframework/docs
|
||||
:docs-resources: {docdir}/../../main/resources
|
||||
:spring-framework-main-code: https://github.com/spring-projects/spring-framework/tree/main
|
||||
// Third-party Links
|
||||
:docs-graalvm: https://www.graalvm.org/22.3/reference-manual
|
||||
:gh-rsocket: https://github.com/rsocket
|
||||
:gh-rsocket-extensions: {gh-rsocket}/rsocket/blob/master/Extensions
|
||||
:gh-rsocket-java: {gh-rsocket}/rsocket-java
|
||||
@@ -1,30 +0,0 @@
|
||||
[[spring-core]]
|
||||
= Core Technologies
|
||||
|
||||
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.
|
||||
|
||||
AOT processing can be used to optimize your application ahead-of-time. It is typically
|
||||
used for native image deployment using GraalVM.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
[[aop-api]]
|
||||
= Spring AOP APIs
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
The previous chapter described the Spring's support for AOP with @AspectJ and schema-based
|
||||
aspect definitions. In this chapter, we discuss the lower-level Spring AOP APIs. For common
|
||||
applications, we recommend the use of Spring AOP with AspectJ pointcuts as described in the
|
||||
previous chapter.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,592 +0,0 @@
|
||||
[[aop-api-advice]]
|
||||
= Advice API in Spring
|
||||
|
||||
Now we can examine how Spring AOP handles advice.
|
||||
|
||||
|
||||
|
||||
[[aop-api-advice-lifecycle]]
|
||||
== Advice Lifecycles
|
||||
|
||||
Each advice is a Spring bean. An advice instance can be shared across all advised
|
||||
objects or be unique to each advised object. This corresponds to per-class or
|
||||
per-instance advice.
|
||||
|
||||
Per-class advice is used most often. It is appropriate for generic advice, such as
|
||||
transaction advisors. These do not depend on the state of the proxied object or add new
|
||||
state. They merely act on the method and arguments.
|
||||
|
||||
Per-instance advice is appropriate for introductions, to support mixins. In this case,
|
||||
the advice adds state to the proxied object.
|
||||
|
||||
You can use a mix of shared and per-instance advice in the same AOP proxy.
|
||||
|
||||
|
||||
|
||||
[[aop-api-advice-types]]
|
||||
== Advice Types in Spring
|
||||
|
||||
Spring provides several advice types and is extensible to support
|
||||
arbitrary advice types. This section describes the basic concepts and standard advice types.
|
||||
|
||||
|
||||
[[aop-api-advice-around]]
|
||||
=== Interception Around Advice
|
||||
|
||||
The most fundamental advice type in Spring is interception around advice.
|
||||
|
||||
Spring is compliant with the AOP `Alliance` interface for around advice that uses method
|
||||
interception. Classes that implement `MethodInterceptor` and that implement around advice should also implement the
|
||||
following interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface MethodInterceptor extends Interceptor {
|
||||
|
||||
Object invoke(MethodInvocation invocation) throws Throwable;
|
||||
}
|
||||
----
|
||||
|
||||
The `MethodInvocation` argument to the `invoke()` method exposes the method being
|
||||
invoked, the target join point, the AOP proxy, and the arguments to the method. The
|
||||
`invoke()` method should return the invocation's result: the return value of the join
|
||||
point.
|
||||
|
||||
The following example shows a simple `MethodInterceptor` implementation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class DebugInterceptor implements MethodInterceptor {
|
||||
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
System.out.println("Before: invocation=[" + invocation + "]");
|
||||
Object rval = invocation.proceed();
|
||||
System.out.println("Invocation returned");
|
||||
return rval;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class DebugInterceptor : MethodInterceptor {
|
||||
|
||||
override fun invoke(invocation: MethodInvocation): Any {
|
||||
println("Before: invocation=[$invocation]")
|
||||
val rval = invocation.proceed()
|
||||
println("Invocation returned")
|
||||
return rval
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Note the call to the `proceed()` method of `MethodInvocation`. This proceeds down the
|
||||
interceptor chain towards the join point. Most interceptors invoke this method and
|
||||
return its return value. However, a `MethodInterceptor`, like any around advice, can
|
||||
return a different value or throw an exception rather than invoke the proceed method.
|
||||
However, you do not want to do this without good reason.
|
||||
|
||||
NOTE: `MethodInterceptor` implementations offer interoperability with other AOP Alliance-compliant AOP
|
||||
implementations. The other advice types discussed in the remainder of this section
|
||||
implement common AOP concepts but in a Spring-specific way. While there is an advantage
|
||||
in using the most specific advice type, stick with `MethodInterceptor` around advice if
|
||||
you are likely to want to run the aspect in another AOP framework. Note that pointcuts
|
||||
are not currently interoperable between frameworks, and the AOP Alliance does not
|
||||
currently define pointcut interfaces.
|
||||
|
||||
|
||||
[[aop-api-advice-before]]
|
||||
=== Before Advice
|
||||
|
||||
A simpler advice type is a before advice. This does not need a `MethodInvocation`
|
||||
object, since it is called only before entering the method.
|
||||
|
||||
The main advantage of a before advice is that there is no need to invoke the `proceed()`
|
||||
method and, therefore, no possibility of inadvertently failing to proceed down the
|
||||
interceptor chain.
|
||||
|
||||
The following listing shows the `MethodBeforeAdvice` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface MethodBeforeAdvice extends BeforeAdvice {
|
||||
|
||||
void before(Method m, Object[] args, Object target) throws Throwable;
|
||||
}
|
||||
----
|
||||
|
||||
(Spring's API design would allow for
|
||||
field before advice, although the usual objects apply to field interception and it is
|
||||
unlikely for Spring to ever implement it.)
|
||||
|
||||
Note that the return type is `void`. Before advice can insert custom behavior before the join
|
||||
point runs but cannot change the return value. If a before advice throws an
|
||||
exception, it stops further execution of the interceptor chain. The exception
|
||||
propagates back up the interceptor chain. If it is unchecked or on the signature of
|
||||
the invoked method, it is passed directly to the client. Otherwise, it is
|
||||
wrapped in an unchecked exception by the AOP proxy.
|
||||
|
||||
The following example shows a before advice in Spring, which counts all method invocations:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class CountingBeforeAdvice implements MethodBeforeAdvice {
|
||||
|
||||
private int count;
|
||||
|
||||
public void before(Method m, Object[] args, Object target) throws Throwable {
|
||||
++count;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class CountingBeforeAdvice : MethodBeforeAdvice {
|
||||
|
||||
var count: Int = 0
|
||||
|
||||
override fun before(m: Method, args: Array<Any>, target: Any?) {
|
||||
++count
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
TIP: Before advice can be used with any pointcut.
|
||||
|
||||
|
||||
[[aop-api-advice-throws]]
|
||||
=== Throws Advice
|
||||
|
||||
Throws advice is invoked after the return of the join point if the join point threw
|
||||
an exception. Spring offers typed throws advice. Note that this means that the
|
||||
`org.springframework.aop.ThrowsAdvice` interface does not contain any methods. It is a
|
||||
tag interface identifying that the given object implements one or more typed throws
|
||||
advice methods. These should be in the following form:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
afterThrowing([Method, args, target], subclassOfThrowable)
|
||||
----
|
||||
|
||||
Only the last argument is required. The method signatures may have either one or four
|
||||
arguments, depending on whether the advice method is interested in the method and
|
||||
arguments. The next two listing show classes that are examples of throws advice.
|
||||
|
||||
The following advice is invoked if a `RemoteException` is thrown (including from subclasses):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class RemoteThrowsAdvice implements ThrowsAdvice {
|
||||
|
||||
public void afterThrowing(RemoteException ex) throws Throwable {
|
||||
// Do something with remote exception
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class RemoteThrowsAdvice : ThrowsAdvice {
|
||||
|
||||
fun afterThrowing(ex: RemoteException) {
|
||||
// Do something with remote exception
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Unlike the preceding
|
||||
advice, the next example declares four arguments, so that it has access to the invoked method, method
|
||||
arguments, and target object. The following advice is invoked if a `ServletException` is thrown:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
|
||||
|
||||
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
|
||||
// Do something with all arguments
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class ServletThrowsAdviceWithArguments : ThrowsAdvice {
|
||||
|
||||
fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
|
||||
// Do something with all arguments
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The final example illustrates how these two methods could be used in a single class
|
||||
that handles both `RemoteException` and `ServletException`. Any number of throws advice
|
||||
methods can be combined in a single class. The following listing shows the final example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public static class CombinedThrowsAdvice implements ThrowsAdvice {
|
||||
|
||||
public void afterThrowing(RemoteException ex) throws Throwable {
|
||||
// Do something with remote exception
|
||||
}
|
||||
|
||||
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
|
||||
// Do something with all arguments
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class CombinedThrowsAdvice : ThrowsAdvice {
|
||||
|
||||
fun afterThrowing(ex: RemoteException) {
|
||||
// Do something with remote exception
|
||||
}
|
||||
|
||||
fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
|
||||
// Do something with all arguments
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: If a throws-advice method throws an exception itself, it overrides the
|
||||
original exception (that is, it changes the exception thrown to the user). The overriding
|
||||
exception is typically a RuntimeException, which is compatible with any method
|
||||
signature. However, if a throws-advice method throws a checked exception, it must
|
||||
match the declared exceptions of the target method and is, hence, to some degree
|
||||
coupled to specific target method signatures. _Do not throw an undeclared checked
|
||||
exception that is incompatible with the target method's signature!_
|
||||
|
||||
TIP: Throws advice can be used with any pointcut.
|
||||
|
||||
|
||||
[[aop-api-advice-after-returning]]
|
||||
=== After Returning Advice
|
||||
|
||||
An after returning advice in Spring must implement the
|
||||
`org.springframework.aop.AfterReturningAdvice` interface, which the following listing shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface AfterReturningAdvice extends Advice {
|
||||
|
||||
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
|
||||
throws Throwable;
|
||||
}
|
||||
----
|
||||
|
||||
An after returning advice has access to the return value (which it cannot modify),
|
||||
the invoked method, the method's arguments, and the target.
|
||||
|
||||
The following after returning advice counts all successful method invocations that have
|
||||
not thrown exceptions:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
|
||||
|
||||
private int count;
|
||||
|
||||
public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
|
||||
throws Throwable {
|
||||
++count;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class CountingAfterReturningAdvice : AfterReturningAdvice {
|
||||
|
||||
var count: Int = 0
|
||||
private set
|
||||
|
||||
override fun afterReturning(returnValue: Any?, m: Method, args: Array<Any>, target: Any?) {
|
||||
++count
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
This advice does not change the execution path. If it throws an exception, it is
|
||||
thrown up the interceptor chain instead of the return value.
|
||||
|
||||
TIP: After returning advice can be used with any pointcut.
|
||||
|
||||
|
||||
[[aop-api-advice-introduction]]
|
||||
=== Introduction Advice
|
||||
|
||||
Spring treats introduction advice as a special kind of interception advice.
|
||||
|
||||
Introduction requires an `IntroductionAdvisor` and an `IntroductionInterceptor` that
|
||||
implement the following interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface IntroductionInterceptor extends MethodInterceptor {
|
||||
|
||||
boolean implementsInterface(Class intf);
|
||||
}
|
||||
----
|
||||
|
||||
The `invoke()` method inherited from the AOP Alliance `MethodInterceptor` interface must
|
||||
implement the introduction. That is, if the invoked method is on an introduced
|
||||
interface, the introduction interceptor is responsible for handling the method call -- it
|
||||
cannot invoke `proceed()`.
|
||||
|
||||
Introduction advice cannot be used with any pointcut, as it applies only at the class,
|
||||
rather than the method, level. You can only use introduction advice with the
|
||||
`IntroductionAdvisor`, which has the following methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
|
||||
|
||||
ClassFilter getClassFilter();
|
||||
|
||||
void validateInterfaces() throws IllegalArgumentException;
|
||||
}
|
||||
|
||||
public interface IntroductionInfo {
|
||||
|
||||
Class<?>[] getInterfaces();
|
||||
}
|
||||
----
|
||||
|
||||
There is no `MethodMatcher` and, hence, no `Pointcut` associated with introduction
|
||||
advice. Only class filtering is logical.
|
||||
|
||||
The `getInterfaces()` method returns the interfaces introduced by this advisor.
|
||||
|
||||
The `validateInterfaces()` method is used internally to see whether or not the
|
||||
introduced interfaces can be implemented by the configured `IntroductionInterceptor`.
|
||||
|
||||
Consider an example from the Spring test suite and suppose we want to
|
||||
introduce the following interface to one or more objects:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public interface Lockable {
|
||||
void lock();
|
||||
void unlock();
|
||||
boolean locked();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
interface Lockable {
|
||||
fun lock()
|
||||
fun unlock()
|
||||
fun locked(): Boolean
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
This illustrates a mixin. We want to be able to cast advised objects to `Lockable`,
|
||||
whatever their type and call lock and unlock methods. If we call the `lock()` method, we
|
||||
want all setter methods to throw a `LockedException`. Thus, we can add an aspect that
|
||||
provides the ability to make objects immutable without them having any knowledge of it:
|
||||
a good example of AOP.
|
||||
|
||||
First, we need an `IntroductionInterceptor` that does the heavy lifting. In this
|
||||
case, we extend the `org.springframework.aop.support.DelegatingIntroductionInterceptor`
|
||||
convenience class. We could implement `IntroductionInterceptor` directly, but using
|
||||
`DelegatingIntroductionInterceptor` is best for most cases.
|
||||
|
||||
The `DelegatingIntroductionInterceptor` is designed to delegate an introduction to an
|
||||
actual implementation of the introduced interfaces, concealing the use of interception
|
||||
to do so. You can set the delegate to any object using a constructor argument. The
|
||||
default delegate (when the no-argument constructor is used) is `this`. Thus, in the next example,
|
||||
the delegate is the `LockMixin` subclass of `DelegatingIntroductionInterceptor`.
|
||||
Given a delegate (by default, itself), a `DelegatingIntroductionInterceptor` instance
|
||||
looks for all interfaces implemented by the delegate (other than
|
||||
`IntroductionInterceptor`) and supports introductions against any of them.
|
||||
Subclasses such as `LockMixin` can call the `suppressInterface(Class intf)`
|
||||
method to suppress interfaces that should not be exposed. However, no matter how many
|
||||
interfaces an `IntroductionInterceptor` is prepared to support, the
|
||||
`IntroductionAdvisor` used controls which interfaces are actually exposed. An
|
||||
introduced interface conceals any implementation of the same interface by the target.
|
||||
|
||||
Thus, `LockMixin` extends `DelegatingIntroductionInterceptor` and implements `Lockable`
|
||||
itself. The superclass automatically picks up that `Lockable` can be supported for
|
||||
introduction, so we do not need to specify that. We could introduce any number of
|
||||
interfaces in this way.
|
||||
|
||||
Note the use of the `locked` instance variable. This effectively adds additional state
|
||||
to that held in the target object.
|
||||
|
||||
The following example shows the example `LockMixin` class:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {
|
||||
|
||||
private boolean locked;
|
||||
|
||||
public void lock() {
|
||||
this.locked = true;
|
||||
}
|
||||
|
||||
public void unlock() {
|
||||
this.locked = false;
|
||||
}
|
||||
|
||||
public boolean locked() {
|
||||
return this.locked;
|
||||
}
|
||||
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
|
||||
throw new LockedException();
|
||||
}
|
||||
return super.invoke(invocation);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class LockMixin : DelegatingIntroductionInterceptor(), Lockable {
|
||||
|
||||
private var locked: Boolean = false
|
||||
|
||||
fun lock() {
|
||||
this.locked = true
|
||||
}
|
||||
|
||||
fun unlock() {
|
||||
this.locked = false
|
||||
}
|
||||
|
||||
fun locked(): Boolean {
|
||||
return this.locked
|
||||
}
|
||||
|
||||
override fun invoke(invocation: MethodInvocation): Any? {
|
||||
if (locked() && invocation.method.name.indexOf("set") == 0) {
|
||||
throw LockedException()
|
||||
}
|
||||
return super.invoke(invocation)
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Often, you need not override the `invoke()` method. The
|
||||
`DelegatingIntroductionInterceptor` implementation (which calls the `delegate` method if
|
||||
the method is introduced, otherwise proceeds towards the join point) usually
|
||||
suffices. In the present case, we need to add a check: no setter method can be invoked
|
||||
if in locked mode.
|
||||
|
||||
The required introduction only needs to hold a distinct
|
||||
`LockMixin` instance and specify the introduced interfaces (in this case, only
|
||||
`Lockable`). A more complex example might take a reference to the introduction
|
||||
interceptor (which would be defined as a prototype). In this case, there is no
|
||||
configuration relevant for a `LockMixin`, so we create it by using `new`.
|
||||
The following example shows our `LockMixinAdvisor` class:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
|
||||
|
||||
public LockMixinAdvisor() {
|
||||
super(new LockMixin(), Lockable.class);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
We can apply this advisor very simply, because it requires no configuration. (However, it
|
||||
is impossible to use an `IntroductionInterceptor` without an
|
||||
`IntroductionAdvisor`.) As usual with introductions, the advisor must be per-instance,
|
||||
as it is stateful. We need a different instance of `LockMixinAdvisor`, and hence
|
||||
`LockMixin`, for each advised object. The advisor comprises part of the advised object's
|
||||
state.
|
||||
|
||||
We can apply this advisor programmatically by using the `Advised.addAdvisor()` method or
|
||||
(the recommended way) in XML configuration, as any other advisor. All proxy creation
|
||||
choices discussed below, including "`auto proxy creators,`" correctly handle introductions
|
||||
and stateful mixins.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
[[aop-api-advised]]
|
||||
= Manipulating Advised Objects
|
||||
|
||||
However you create AOP proxies, you can manipulate them BY using the
|
||||
`org.springframework.aop.framework.Advised` interface. Any AOP proxy can be cast to this
|
||||
interface, no matter which other interfaces it implements. This interface includes the
|
||||
following methods:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Advisor[] getAdvisors();
|
||||
|
||||
void addAdvice(Advice advice) throws AopConfigException;
|
||||
|
||||
void addAdvice(int pos, Advice advice) throws AopConfigException;
|
||||
|
||||
void addAdvisor(Advisor advisor) throws AopConfigException;
|
||||
|
||||
void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
|
||||
|
||||
int indexOf(Advisor advisor);
|
||||
|
||||
boolean removeAdvisor(Advisor advisor) throws AopConfigException;
|
||||
|
||||
void removeAdvisor(int index) throws AopConfigException;
|
||||
|
||||
boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
|
||||
|
||||
boolean isFrozen();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun getAdvisors(): Array<Advisor>
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun addAdvice(advice: Advice)
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun addAdvice(pos: Int, advice: Advice)
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun addAdvisor(advisor: Advisor)
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun addAdvisor(pos: Int, advisor: Advisor)
|
||||
|
||||
fun indexOf(advisor: Advisor): Int
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun removeAdvisor(advisor: Advisor): Boolean
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun removeAdvisor(index: Int)
|
||||
|
||||
@Throws(AopConfigException::class)
|
||||
fun replaceAdvisor(a: Advisor, b: Advisor): Boolean
|
||||
|
||||
fun isFrozen(): Boolean
|
||||
----
|
||||
======
|
||||
|
||||
The `getAdvisors()` method returns an `Advisor` for every advisor, interceptor, or
|
||||
other advice type that has been added to the factory. If you added an `Advisor`, the
|
||||
returned advisor at this index is the object that you added. If you added an
|
||||
interceptor or other advice type, Spring wrapped this in an advisor with a
|
||||
pointcut that always returns `true`. Thus, if you added a `MethodInterceptor`, the advisor
|
||||
returned for this index is a `DefaultPointcutAdvisor` that returns your
|
||||
`MethodInterceptor` and a pointcut that matches all classes and methods.
|
||||
|
||||
The `addAdvisor()` methods can be used to add any `Advisor`. Usually, the advisor holding
|
||||
pointcut and advice is the generic `DefaultPointcutAdvisor`, which you can use with
|
||||
any advice or pointcut (but not for introductions).
|
||||
|
||||
By default, it is possible to add or remove advisors or interceptors even once a proxy
|
||||
has been created. The only restriction is that it is impossible to add or remove an
|
||||
introduction advisor, as existing proxies from the factory do not show the interface
|
||||
change. (You can obtain a new proxy from the factory to avoid this problem.)
|
||||
|
||||
The following example shows casting an AOP proxy to the `Advised` interface and examining and
|
||||
manipulating its advice:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Advised advised = (Advised) myObject;
|
||||
Advisor[] advisors = advised.getAdvisors();
|
||||
int oldAdvisorCount = advisors.length;
|
||||
System.out.println(oldAdvisorCount + " advisors");
|
||||
|
||||
// Add an advice like an interceptor without a pointcut
|
||||
// Will match all proxied methods
|
||||
// Can use for interceptors, before, after returning or throws advice
|
||||
advised.addAdvice(new DebugInterceptor());
|
||||
|
||||
// Add selective advice using a pointcut
|
||||
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));
|
||||
|
||||
assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val advised = myObject as Advised
|
||||
val advisors = advised.advisors
|
||||
val oldAdvisorCount = advisors.size
|
||||
println("$oldAdvisorCount advisors")
|
||||
|
||||
// Add an advice like an interceptor without a pointcut
|
||||
// Will match all proxied methods
|
||||
// Can use for interceptors, before, after returning or throws advice
|
||||
advised.addAdvice(DebugInterceptor())
|
||||
|
||||
// Add selective advice using a pointcut
|
||||
advised.addAdvisor(DefaultPointcutAdvisor(mySpecialPointcut, myAdvice))
|
||||
|
||||
assertEquals("Added two advisors", oldAdvisorCount + 2, advised.advisors.size)
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: It is questionable whether it is advisable (no pun intended) to modify advice on a
|
||||
business object in production, although there are, no doubt, legitimate usage cases.
|
||||
However, it can be very useful in development (for example, in tests). We have sometimes
|
||||
found it very useful to be able to add test code in the form of an interceptor or other
|
||||
advice, getting inside a method invocation that we want to test. (For example, the advice can
|
||||
get inside a transaction created for that method, perhaps to run SQL to check that
|
||||
a database was correctly updated, before marking the transaction for roll back.)
|
||||
|
||||
Depending on how you created the proxy, you can usually set a `frozen` flag. In that
|
||||
case, the `Advised` `isFrozen()` method returns `true`, and any attempts to modify
|
||||
advice through addition or removal results in an `AopConfigException`. The ability
|
||||
to freeze the state of an advised object is useful in some cases (for example, to
|
||||
prevent calling code removing a security interceptor).
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
[[aop-api-advisor]]
|
||||
= The Advisor API in Spring
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
In Spring, an Advisor is an aspect that contains only a single advice object associated
|
||||
with a pointcut expression.
|
||||
|
||||
Apart from the special case of introductions, any advisor can be used with any advice.
|
||||
`org.springframework.aop.support.DefaultPointcutAdvisor` is the most commonly used
|
||||
advisor class. It can be used with a `MethodInterceptor`, `BeforeAdvice`, or
|
||||
`ThrowsAdvice`.
|
||||
|
||||
It is possible to mix advisor and advice types in Spring in the same AOP proxy. For
|
||||
example, you could use an interception around advice, throws advice, and before advice in
|
||||
one proxy configuration. Spring automatically creates the necessary interceptor
|
||||
chain.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
[[aop-autoproxy]]
|
||||
= Using the "auto-proxy" facility
|
||||
|
||||
So far, we have considered explicit creation of AOP proxies by using a `ProxyFactoryBean` or
|
||||
similar factory bean.
|
||||
|
||||
Spring also lets us use "`auto-proxy`" bean definitions, which can automatically
|
||||
proxy selected bean definitions. This is built on Spring's "`bean post processor`"
|
||||
infrastructure, which enables modification of any bean definition as the container loads.
|
||||
|
||||
In this model, you set up some special bean definitions in your XML bean definition file
|
||||
to configure the auto-proxy infrastructure. This lets you declare the targets
|
||||
eligible for auto-proxying. You need not use `ProxyFactoryBean`.
|
||||
|
||||
There are two ways to do this:
|
||||
|
||||
* By using an auto-proxy creator that refers to specific beans in the current context.
|
||||
* A special case of auto-proxy creation that deserves to be considered separately:
|
||||
auto-proxy creation driven by source-level metadata attributes.
|
||||
|
||||
|
||||
|
||||
[[aop-autoproxy-choices]]
|
||||
== Auto-proxy Bean Definitions
|
||||
|
||||
This section covers the auto-proxy creators provided by the
|
||||
`org.springframework.aop.framework.autoproxy` package.
|
||||
|
||||
|
||||
[[aop-api-autoproxy]]
|
||||
=== `BeanNameAutoProxyCreator`
|
||||
|
||||
The `BeanNameAutoProxyCreator` class is a `BeanPostProcessor` that automatically creates
|
||||
AOP proxies for beans with names that match literal values or wildcards. The following
|
||||
example shows how to create a `BeanNameAutoProxyCreator` bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
|
||||
<property name="beanNames" value="jdk*,onlyJdk"/>
|
||||
<property name="interceptorNames">
|
||||
<list>
|
||||
<value>myInterceptor</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
As with `ProxyFactoryBean`, there is an `interceptorNames` property rather than a list
|
||||
of interceptors, to allow correct behavior for prototype advisors. Named "`interceptors`"
|
||||
can be advisors or any advice type.
|
||||
|
||||
As with auto-proxying in general, the main point of using `BeanNameAutoProxyCreator` is
|
||||
to apply the same configuration consistently to multiple objects, with minimal volume of
|
||||
configuration. It is a popular choice for applying declarative transactions to multiple
|
||||
objects.
|
||||
|
||||
Bean definitions whose names match, such as `jdkMyBean` and `onlyJdk` in the preceding
|
||||
example, are plain old bean definitions with the target class. An AOP proxy is
|
||||
automatically created by the `BeanNameAutoProxyCreator`. The same advice is applied
|
||||
to all matching beans. Note that, if advisors are used (rather than the interceptor in
|
||||
the preceding example), the pointcuts may apply differently to different beans.
|
||||
|
||||
|
||||
[[aop-api-autoproxy-default]]
|
||||
=== `DefaultAdvisorAutoProxyCreator`
|
||||
|
||||
A more general and extremely powerful auto-proxy creator is
|
||||
`DefaultAdvisorAutoProxyCreator`. This automagically applies eligible advisors in the
|
||||
current context, without the need to include specific bean names in the auto-proxy
|
||||
advisor's bean definition. It offers the same merit of consistent configuration and
|
||||
avoidance of duplication as `BeanNameAutoProxyCreator`.
|
||||
|
||||
Using this mechanism involves:
|
||||
|
||||
* Specifying a `DefaultAdvisorAutoProxyCreator` bean definition.
|
||||
* Specifying any number of advisors in the same or related contexts. Note that these
|
||||
must be advisors, not interceptors or other advice. This is necessary,
|
||||
because there must be a pointcut to evaluate, to check the eligibility of each advice
|
||||
to candidate bean definitions.
|
||||
|
||||
The `DefaultAdvisorAutoProxyCreator` automatically evaluates the pointcut contained
|
||||
in each advisor, to see what (if any) advice it should apply to each business object
|
||||
(such as `businessObject1` and `businessObject2` in the example).
|
||||
|
||||
This means that any number of advisors can be applied automatically to each business
|
||||
object. If no pointcut in any of the advisors matches any method in a business object,
|
||||
the object is not proxied. As bean definitions are added for new business objects,
|
||||
they are automatically proxied if necessary.
|
||||
|
||||
Auto-proxying in general has the advantage of making it impossible for callers or
|
||||
dependencies to obtain an un-advised object. Calling `getBean("businessObject1")` on this
|
||||
`ApplicationContext` returns an AOP proxy, not the target business object. (The "`inner
|
||||
bean`" idiom shown earlier also offers this benefit.)
|
||||
|
||||
The following example creates a `DefaultAdvisorAutoProxyCreator` bean and the other
|
||||
elements discussed in this section:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
|
||||
|
||||
<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
|
||||
<property name="transactionInterceptor" ref="transactionInterceptor"/>
|
||||
</bean>
|
||||
|
||||
<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>
|
||||
|
||||
<bean id="businessObject1" class="com.mycompany.BusinessObject1">
|
||||
<!-- Properties omitted -->
|
||||
</bean>
|
||||
|
||||
<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>
|
||||
----
|
||||
|
||||
The `DefaultAdvisorAutoProxyCreator` is very useful if you want to apply the same advice
|
||||
consistently to many business objects. Once the infrastructure definitions are in place,
|
||||
you can add new business objects without including specific proxy configuration.
|
||||
You can also easily drop in additional aspects (for example, tracing or
|
||||
performance monitoring aspects) with minimal change to configuration.
|
||||
|
||||
The `DefaultAdvisorAutoProxyCreator` offers support for filtering (by using a naming
|
||||
convention so that only certain advisors are evaluated, which allows the use of multiple,
|
||||
differently configured, AdvisorAutoProxyCreators in the same factory) and ordering.
|
||||
Advisors can implement the `org.springframework.core.Ordered` interface to ensure
|
||||
correct ordering if this is an issue. The `TransactionAttributeSourceAdvisor` used in the
|
||||
preceding example has a configurable order value. The default setting is unordered.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
[[aop-concise-proxy]]
|
||||
= Concise Proxy Definitions
|
||||
|
||||
Especially when defining transactional proxies, you may end up with many similar proxy
|
||||
definitions. The use of parent and child bean definitions, along with inner bean
|
||||
definitions, can result in much cleaner and more concise proxy definitions.
|
||||
|
||||
First, we create a parent, template, bean definition for the proxy, as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="txProxyTemplate" abstract="true"
|
||||
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
|
||||
<property name="transactionManager" ref="transactionManager"/>
|
||||
<property name="transactionAttributes">
|
||||
<props>
|
||||
<prop key="*">PROPAGATION_REQUIRED</prop>
|
||||
</props>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
This is never instantiated itself, so it can actually be incomplete. Then, each proxy
|
||||
that needs to be created is a child bean definition, which wraps the target of the
|
||||
proxy as an inner bean definition, since the target is never used on its own anyway.
|
||||
The following example shows such a child bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="myService" parent="txProxyTemplate">
|
||||
<property name="target">
|
||||
<bean class="org.springframework.samples.MyServiceImpl">
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
You can override properties from the parent template. In the following example,
|
||||
we override the transaction propagation settings:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="mySpecialService" parent="txProxyTemplate">
|
||||
<property name="target">
|
||||
<bean class="org.springframework.samples.MySpecialServiceImpl">
|
||||
</bean>
|
||||
</property>
|
||||
<property name="transactionAttributes">
|
||||
<props>
|
||||
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
|
||||
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
|
||||
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
|
||||
<prop key="store*">PROPAGATION_REQUIRED</prop>
|
||||
</props>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that in the parent bean example, we explicitly marked the parent bean definition as
|
||||
being abstract by setting the `abstract` attribute to `true`, as described
|
||||
xref:core/beans/child-bean-definitions.adoc[previously], so that it may not actually ever be
|
||||
instantiated. Application contexts (but not simple bean factories), by default,
|
||||
pre-instantiate all singletons. Therefore, it is important (at least for singleton beans)
|
||||
that, if you have a (parent) bean definition that you intend to use only as a template,
|
||||
and this definition specifies a class, you must make sure to set the `abstract`
|
||||
attribute to `true`. Otherwise, the application context actually tries to
|
||||
pre-instantiate it.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
[[aop-extensibility]]
|
||||
= Defining New Advice Types
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring AOP is designed to be extensible. While the interception implementation strategy
|
||||
is presently used internally, it is possible to support arbitrary advice types in
|
||||
addition to the interception around advice, before, throws advice, and
|
||||
after returning advice.
|
||||
|
||||
The `org.springframework.aop.framework.adapter` package is an SPI package that lets
|
||||
support for new custom advice types be added without changing the core framework.
|
||||
The only constraint on a custom `Advice` type is that it must implement the
|
||||
`org.aopalliance.aop.Advice` marker interface.
|
||||
|
||||
See the {api-spring-framework}/aop/framework/adapter/package-summary.html[`org.springframework.aop.framework.adapter`]
|
||||
javadoc for further information.
|
||||
@@ -1,329 +0,0 @@
|
||||
[[aop-pfb]]
|
||||
= Using the `ProxyFactoryBean` to Create AOP Proxies
|
||||
|
||||
If you use the Spring IoC container (an `ApplicationContext` or `BeanFactory`) for your
|
||||
business objects (and you should be!), you want to use one of Spring's AOP
|
||||
`FactoryBean` implementations. (Remember that a factory bean introduces a layer of indirection, letting
|
||||
it create objects of a different type.)
|
||||
|
||||
NOTE: The Spring AOP support also uses factory beans under the covers.
|
||||
|
||||
The basic way to create an AOP proxy in Spring is to use the
|
||||
`org.springframework.aop.framework.ProxyFactoryBean`. This gives complete control over
|
||||
the pointcuts, any advice that applies, and their ordering. However, there are simpler
|
||||
options that are preferable if you do not need such control.
|
||||
|
||||
|
||||
|
||||
[[aop-pfb-1]]
|
||||
== Basics
|
||||
|
||||
The `ProxyFactoryBean`, like other Spring `FactoryBean` implementations, introduces a
|
||||
level of indirection. If you define a `ProxyFactoryBean` named `foo`, objects that
|
||||
reference `foo` do not see the `ProxyFactoryBean` instance itself but an object
|
||||
created by the implementation of the `getObject()` method in the `ProxyFactoryBean` . This
|
||||
method creates an AOP proxy that wraps a target object.
|
||||
|
||||
One of the most important benefits of using a `ProxyFactoryBean` or another IoC-aware
|
||||
class to create AOP proxies is that advice and pointcuts can also be
|
||||
managed by IoC. This is a powerful feature, enabling certain approaches that are hard to
|
||||
achieve with other AOP frameworks. For example, an advice may itself reference
|
||||
application objects (besides the target, which should be available in any AOP
|
||||
framework), benefiting from all the pluggability provided by Dependency Injection.
|
||||
|
||||
|
||||
|
||||
[[aop-pfb-2]]
|
||||
== JavaBean Properties
|
||||
|
||||
In common with most `FactoryBean` implementations provided with Spring, the
|
||||
`ProxyFactoryBean` class is itself a JavaBean. Its properties are used to:
|
||||
|
||||
* Specify the target you want to proxy.
|
||||
* Specify whether to use CGLIB (described later and see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
|
||||
|
||||
Some key properties are inherited from `org.springframework.aop.framework.ProxyConfig`
|
||||
(the superclass for all AOP proxy factories in Spring). These key properties include
|
||||
the following:
|
||||
|
||||
* `proxyTargetClass`: `true` if the target class is to be proxied, rather than the
|
||||
target class's interfaces. If this property value is set to `true`, then CGLIB proxies
|
||||
are created (but see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
|
||||
* `optimize`: Controls whether or not aggressive optimizations are applied to proxies
|
||||
created through CGLIB. You should not blithely use this setting unless you fully
|
||||
understand how the relevant AOP proxy handles optimization. This is currently used
|
||||
only for CGLIB proxies. It has no effect with JDK dynamic proxies.
|
||||
* `frozen`: If a proxy configuration is `frozen`, changes to the configuration are
|
||||
no longer allowed. This is useful both as a slight optimization and for those cases
|
||||
when you do not want callers to be able to manipulate the proxy (through the `Advised`
|
||||
interface) after the proxy has been created. The default value of this property is
|
||||
`false`, so changes (such as adding additional advice) are allowed.
|
||||
* `exposeProxy`: Determines whether or not the current proxy should be exposed in a
|
||||
`ThreadLocal` so that it can be accessed by the target. If a target needs to obtain
|
||||
the proxy and the `exposeProxy` property is set to `true`, the target can use the
|
||||
`AopContext.currentProxy()` method.
|
||||
|
||||
Other properties specific to `ProxyFactoryBean` include the following:
|
||||
|
||||
* `proxyInterfaces`: An array of `String` interface names. If this is not supplied, a CGLIB
|
||||
proxy for the target class is used (but see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
|
||||
* `interceptorNames`: A `String` array of `Advisor`, interceptor, or other advice names to
|
||||
apply. Ordering is significant, on a first come-first served basis. That is to say
|
||||
that the first interceptor in the list is the first to be able to intercept the
|
||||
invocation.
|
||||
+
|
||||
The names are bean names in the current factory, including bean names from ancestor
|
||||
factories. You cannot mention bean references here, since doing so results in the
|
||||
`ProxyFactoryBean` ignoring the singleton setting of the advice.
|
||||
+
|
||||
You can append an interceptor name with an asterisk (`*`). Doing so results in the
|
||||
application of all advisor beans with names that start with the part before the asterisk
|
||||
to be applied. You can find an example of using this feature in xref:core/aop-api/pfb.adoc#aop-global-advisors[Using "`Global`" Advisors].
|
||||
|
||||
* singleton: Whether or not the factory should return a single object, no matter how
|
||||
often the `getObject()` method is called. Several `FactoryBean` implementations offer
|
||||
such a method. The default value is `true`. If you want to use stateful advice - for
|
||||
example, for stateful mixins - use prototype advice along with a singleton value of
|
||||
`false`.
|
||||
|
||||
|
||||
|
||||
[[aop-pfb-proxy-types]]
|
||||
== JDK- and CGLIB-based proxies
|
||||
|
||||
This section serves as the definitive documentation on how the `ProxyFactoryBean`
|
||||
chooses to create either a JDK-based proxy or a CGLIB-based proxy for a particular target
|
||||
object (which is to be proxied).
|
||||
|
||||
NOTE: The behavior of the `ProxyFactoryBean` with regard to creating JDK- or CGLIB-based
|
||||
proxies changed between versions 1.2.x and 2.0 of Spring. The `ProxyFactoryBean` now
|
||||
exhibits similar semantics with regard to auto-detecting interfaces as those of the
|
||||
`TransactionProxyFactoryBean` class.
|
||||
|
||||
If the class of a target object that is to be proxied (hereafter simply referred to as
|
||||
the target class) does not implement any interfaces, a CGLIB-based proxy is
|
||||
created. This is the easiest scenario, because JDK proxies are interface-based, and no
|
||||
interfaces means JDK proxying is not even possible. You can plug in the target bean
|
||||
and specify the list of interceptors by setting the `interceptorNames` property. Note that a
|
||||
CGLIB-based proxy is created even if the `proxyTargetClass` property of the
|
||||
`ProxyFactoryBean` has been set to `false`. (Doing so makes no sense and is best
|
||||
removed from the bean definition, because it is, at best, redundant, and, at worst
|
||||
confusing.)
|
||||
|
||||
If the target class implements one (or more) interfaces, the type of proxy that is
|
||||
created depends on the configuration of the `ProxyFactoryBean`.
|
||||
|
||||
If the `proxyTargetClass` property of the `ProxyFactoryBean` has been set to `true`,
|
||||
a CGLIB-based proxy is created. This makes sense and is in keeping with the
|
||||
principle of least surprise. Even if the `proxyInterfaces` property of the
|
||||
`ProxyFactoryBean` has been set to one or more fully qualified interface names, the fact
|
||||
that the `proxyTargetClass` property is set to `true` causes CGLIB-based
|
||||
proxying to be in effect.
|
||||
|
||||
If the `proxyInterfaces` property of the `ProxyFactoryBean` has been set to one or more
|
||||
fully qualified interface names, a JDK-based proxy is created. The created
|
||||
proxy implements all of the interfaces that were specified in the `proxyInterfaces`
|
||||
property. If the target class happens to implement a whole lot more interfaces than
|
||||
those specified in the `proxyInterfaces` property, that is all well and good, but those
|
||||
additional interfaces are not implemented by the returned proxy.
|
||||
|
||||
If the `proxyInterfaces` property of the `ProxyFactoryBean` has not been set, but
|
||||
the target class does implement one (or more) interfaces, the
|
||||
`ProxyFactoryBean` auto-detects the fact that the target class does actually
|
||||
implement at least one interface, and a JDK-based proxy is created. The interfaces
|
||||
that are actually proxied are all of the interfaces that the target class
|
||||
implements. In effect, this is the same as supplying a list of each and every
|
||||
interface that the target class implements to the `proxyInterfaces` property. However,
|
||||
it is significantly less work and less prone to typographical errors.
|
||||
|
||||
|
||||
|
||||
[[aop-api-proxying-intf]]
|
||||
== Proxying Interfaces
|
||||
|
||||
Consider a simple example of `ProxyFactoryBean` in action. This example involves:
|
||||
|
||||
* A target bean that is proxied. This is the `personTarget` bean definition in
|
||||
the example.
|
||||
* An `Advisor` and an `Interceptor` used to provide advice.
|
||||
* An AOP proxy bean definition to specify the target object (the `personTarget` bean),
|
||||
the interfaces to proxy, and the advice to apply.
|
||||
|
||||
The following listing shows the example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="personTarget" class="com.mycompany.PersonImpl">
|
||||
<property name="name" value="Tony"/>
|
||||
<property name="age" value="51"/>
|
||||
</bean>
|
||||
|
||||
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
|
||||
<property name="someProperty" value="Custom string property value"/>
|
||||
</bean>
|
||||
|
||||
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
|
||||
</bean>
|
||||
|
||||
<bean id="person"
|
||||
class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="proxyInterfaces" value="com.mycompany.Person"/>
|
||||
|
||||
<property name="target" ref="personTarget"/>
|
||||
<property name="interceptorNames">
|
||||
<list>
|
||||
<value>myAdvisor</value>
|
||||
<value>debugInterceptor</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that the `interceptorNames` property takes a list of `String`, which holds the bean names of the
|
||||
interceptors or advisors in the current factory. You can use advisors, interceptors, before, after
|
||||
returning, and throws advice objects. The ordering of advisors is significant.
|
||||
|
||||
NOTE: You might be wondering why the list does not hold bean references. The reason for this is
|
||||
that, if the singleton property of the `ProxyFactoryBean` is set to `false`, it must be able to
|
||||
return independent proxy instances. If any of the advisors is itself a prototype, an
|
||||
independent instance would need to be returned, so it is necessary to be able to obtain
|
||||
an instance of the prototype from the factory. Holding a reference is not sufficient.
|
||||
|
||||
The `person` bean definition shown earlier can be used in place of a `Person` implementation, as
|
||||
follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Person person = (Person) factory.getBean("person");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val person = factory.getBean("person") as Person;
|
||||
----
|
||||
======
|
||||
|
||||
Other beans in the same IoC context can express a strongly typed dependency on it, as
|
||||
with an ordinary Java object. The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="personUser" class="com.mycompany.PersonUser">
|
||||
<property name="person"><ref bean="person"/></property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The `PersonUser` class in this example exposes a property of type `Person`. As far as
|
||||
it is concerned, the AOP proxy can be used transparently in place of a "`real`" person
|
||||
implementation. However, its class would be a dynamic proxy class. It would be possible
|
||||
to cast it to the `Advised` interface (discussed later).
|
||||
|
||||
You can conceal the distinction between target and proxy by using an anonymous
|
||||
inner bean. Only the `ProxyFactoryBean` definition is different. The
|
||||
advice is included only for completeness. The following example shows how to use an
|
||||
anonymous inner bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
|
||||
<property name="someProperty" value="Custom string property value"/>
|
||||
</bean>
|
||||
|
||||
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>
|
||||
|
||||
<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="proxyInterfaces" value="com.mycompany.Person"/>
|
||||
<!-- Use inner bean, not local reference to target -->
|
||||
<property name="target">
|
||||
<bean class="com.mycompany.PersonImpl">
|
||||
<property name="name" value="Tony"/>
|
||||
<property name="age" value="51"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="interceptorNames">
|
||||
<list>
|
||||
<value>myAdvisor</value>
|
||||
<value>debugInterceptor</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Using an anonymous inner bean has the advantage that there is only one object of type `Person`. This is useful if we want
|
||||
to prevent users of the application context from obtaining a reference to the un-advised
|
||||
object or need to avoid any ambiguity with Spring IoC autowiring. There is also,
|
||||
arguably, an advantage in that the `ProxyFactoryBean` definition is self-contained.
|
||||
However, there are times when being able to obtain the un-advised target from the
|
||||
factory might actually be an advantage (for example, in certain test scenarios).
|
||||
|
||||
|
||||
|
||||
[[aop-api-proxying-class]]
|
||||
== Proxying Classes
|
||||
|
||||
What if you need to proxy a class, rather than one or more interfaces?
|
||||
|
||||
Imagine that in our earlier example, there was no `Person` interface. We needed to advise
|
||||
a class called `Person` that did not implement any business interface. In this case, you
|
||||
can configure Spring to use CGLIB proxying rather than dynamic proxies. To do so, set the
|
||||
`proxyTargetClass` property on the `ProxyFactoryBean` shown earlier to `true`. While it is best to
|
||||
program to interfaces rather than classes, the ability to advise classes that do not
|
||||
implement interfaces can be useful when working with legacy code. (In general, Spring
|
||||
is not prescriptive. While it makes it easy to apply good practices, it avoids forcing a
|
||||
particular approach.)
|
||||
|
||||
If you want to, you can force the use of CGLIB in any case, even if you do have
|
||||
interfaces.
|
||||
|
||||
CGLIB proxying works by generating a subclass of the target class at runtime. Spring
|
||||
configures this generated subclass to delegate method calls to the original target. The
|
||||
subclass is used to implement the Decorator pattern, weaving in the advice.
|
||||
|
||||
CGLIB proxying should generally be transparent to users. However, there are some issues
|
||||
to consider:
|
||||
|
||||
* `final` classes cannot be proxied, because they cannot be extended.
|
||||
* `final` methods cannot be advised, because they cannot be overridden.
|
||||
* `private` methods cannot be advised, because they cannot be overridden.
|
||||
|
||||
NOTE: There is no need to add CGLIB to your classpath. CGLIB is repackaged and included
|
||||
in the `spring-core` JAR. In other words, CGLIB-based AOP works "out of the box", as do
|
||||
JDK dynamic proxies.
|
||||
|
||||
There is little performance difference between CGLIB proxies and dynamic proxies.
|
||||
Performance should not be a decisive consideration in this case.
|
||||
|
||||
|
||||
|
||||
[[aop-global-advisors]]
|
||||
== Using "`Global`" Advisors
|
||||
|
||||
By appending an asterisk to an interceptor name, all advisors with bean names that match
|
||||
the part before the asterisk are added to the advisor chain. This can come in handy
|
||||
if you need to add a standard set of "`global`" advisors. The following example defines
|
||||
two global advisors:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="target" ref="service"/>
|
||||
<property name="interceptorNames">
|
||||
<list>
|
||||
<value>global*</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
|
||||
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
[[aop-api-pointcuts]]
|
||||
= Pointcut API in Spring
|
||||
|
||||
This section describes how Spring handles the crucial pointcut concept.
|
||||
|
||||
|
||||
|
||||
[[aop-api-concepts]]
|
||||
== Concepts
|
||||
|
||||
Spring's pointcut model enables pointcut reuse independent of advice types. You can
|
||||
target different advice with the same pointcut.
|
||||
|
||||
The `org.springframework.aop.Pointcut` interface is the central interface, used to
|
||||
target advice to particular classes and methods. The complete interface follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface Pointcut {
|
||||
|
||||
ClassFilter getClassFilter();
|
||||
|
||||
MethodMatcher getMethodMatcher();
|
||||
}
|
||||
----
|
||||
|
||||
Splitting the `Pointcut` interface into two parts allows reuse of class and method
|
||||
matching parts and fine-grained composition operations (such as performing a "`union`"
|
||||
with another method matcher).
|
||||
|
||||
The `ClassFilter` interface is used to restrict the pointcut to a given set of target
|
||||
classes. If the `matches()` method always returns true, all target classes are
|
||||
matched. The following listing shows the `ClassFilter` interface definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface ClassFilter {
|
||||
|
||||
boolean matches(Class clazz);
|
||||
}
|
||||
----
|
||||
|
||||
The `MethodMatcher` interface is normally more important. The complete interface follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface MethodMatcher {
|
||||
|
||||
boolean matches(Method m, Class<?> targetClass);
|
||||
|
||||
boolean isRuntime();
|
||||
|
||||
boolean matches(Method m, Class<?> targetClass, Object... args);
|
||||
}
|
||||
----
|
||||
|
||||
The `matches(Method, Class)` method is used to test whether this pointcut ever
|
||||
matches a given method on a target class. This evaluation can be performed when an AOP
|
||||
proxy is created to avoid the need for a test on every method invocation. If the
|
||||
two-argument `matches` method returns `true` for a given method, and the `isRuntime()`
|
||||
method for the MethodMatcher returns `true`, the three-argument matches method is
|
||||
invoked on every method invocation. This lets a pointcut look at the arguments passed
|
||||
to the method invocation immediately before the target advice starts.
|
||||
|
||||
Most `MethodMatcher` implementations are static, meaning that their `isRuntime()` method
|
||||
returns `false`. In this case, the three-argument `matches` method is never invoked.
|
||||
|
||||
TIP: If possible, try to make pointcuts static, allowing the AOP framework to cache the
|
||||
results of pointcut evaluation when an AOP proxy is created.
|
||||
|
||||
|
||||
|
||||
[[aop-api-pointcut-ops]]
|
||||
== Operations on Pointcuts
|
||||
|
||||
Spring supports operations (notably, union and intersection) on pointcuts.
|
||||
|
||||
Union means the methods that either pointcut matches.
|
||||
Intersection means the methods that both pointcuts match.
|
||||
Union is usually more useful.
|
||||
You can compose pointcuts by using the static methods in the
|
||||
`org.springframework.aop.support.Pointcuts` class or by using the
|
||||
`ComposablePointcut` class in the same package. However, using AspectJ pointcut
|
||||
expressions is usually a simpler approach.
|
||||
|
||||
|
||||
|
||||
[[aop-api-pointcuts-aspectj]]
|
||||
== AspectJ Expression Pointcuts
|
||||
|
||||
Since 2.0, the most important type of pointcut used by Spring is
|
||||
`org.springframework.aop.aspectj.AspectJExpressionPointcut`. This is a pointcut that
|
||||
uses an AspectJ-supplied library to parse an AspectJ pointcut expression string.
|
||||
|
||||
See the xref:core/aop.adoc[previous chapter] for a discussion of supported AspectJ pointcut primitives.
|
||||
|
||||
|
||||
|
||||
[[aop-api-pointcuts-impls]]
|
||||
== Convenience Pointcut Implementations
|
||||
|
||||
Spring provides several convenient pointcut implementations. You can use some of them
|
||||
directly; others are intended to be subclassed in application-specific pointcuts.
|
||||
|
||||
|
||||
[[aop-api-pointcuts-static]]
|
||||
=== Static Pointcuts
|
||||
|
||||
Static pointcuts are based on the method and the target class and cannot take into account
|
||||
the method's arguments. Static pointcuts suffice -- and are best -- for most usages.
|
||||
Spring can evaluate a static pointcut only once, when a method is first invoked.
|
||||
After that, there is no need to evaluate the pointcut again with each method invocation.
|
||||
|
||||
The rest of this section describes some of the static pointcut implementations that are
|
||||
included with Spring.
|
||||
|
||||
[[aop-api-pointcuts-regex]]
|
||||
==== Regular Expression Pointcuts
|
||||
|
||||
One obvious way to specify static pointcuts is regular expressions. Several AOP
|
||||
frameworks besides Spring make this possible.
|
||||
`org.springframework.aop.support.JdkRegexpMethodPointcut` is a generic regular
|
||||
expression pointcut that uses the regular expression support in the JDK.
|
||||
|
||||
With the `JdkRegexpMethodPointcut` class, you can provide a list of pattern strings.
|
||||
If any of these is a match, the pointcut evaluates to `true`. (As a consequence,
|
||||
the resulting pointcut is effectively the union of the specified patterns.)
|
||||
|
||||
The following example shows how to use `JdkRegexpMethodPointcut`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulatePointcut"
|
||||
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Spring provides a convenience class named `RegexpMethodPointcutAdvisor`, which lets us
|
||||
also reference an `Advice` (remember that an `Advice` can be an interceptor, before advice,
|
||||
throws advice, and others). Behind the scenes, Spring uses a `JdkRegexpMethodPointcut`.
|
||||
Using `RegexpMethodPointcutAdvisor` simplifies wiring, as the one bean encapsulates both
|
||||
pointcut and advice, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulateAdvisor"
|
||||
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
|
||||
<property name="advice">
|
||||
<ref bean="beanNameOfAopAllianceInterceptor"/>
|
||||
</property>
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
You can use `RegexpMethodPointcutAdvisor` with any `Advice` type.
|
||||
|
||||
[[aop-api-pointcuts-attribute-driven]]
|
||||
==== Attribute-driven Pointcuts
|
||||
|
||||
An important type of static pointcut is a metadata-driven pointcut. This uses the
|
||||
values of metadata attributes (typically, source-level metadata).
|
||||
|
||||
|
||||
[[aop-api-pointcuts-dynamic]]
|
||||
=== Dynamic pointcuts
|
||||
|
||||
Dynamic pointcuts are costlier to evaluate than static pointcuts. They take into account
|
||||
method arguments as well as static information. This means that they must be
|
||||
evaluated with every method invocation and that the result cannot be cached, as arguments will
|
||||
vary.
|
||||
|
||||
The main example is the `control flow` pointcut.
|
||||
|
||||
[[aop-api-pointcuts-cflow]]
|
||||
==== Control Flow Pointcuts
|
||||
|
||||
Spring control flow pointcuts are conceptually similar to AspectJ `cflow` pointcuts,
|
||||
although less powerful. (There is currently no way to specify that a pointcut runs
|
||||
below a join point matched by another pointcut.) A control flow pointcut matches the
|
||||
current call stack. For example, it might fire if the join point was invoked by a method
|
||||
in the `com.mycompany.web` package or by the `SomeCaller` class. Control flow pointcuts
|
||||
are specified by using the `org.springframework.aop.support.ControlFlowPointcut` class.
|
||||
|
||||
NOTE: Control flow pointcuts are significantly more expensive to evaluate at runtime than even
|
||||
other dynamic pointcuts. In Java 1.4, the cost is about five times that of other dynamic
|
||||
pointcuts.
|
||||
|
||||
|
||||
|
||||
[[aop-api-pointcuts-superclasses]]
|
||||
== Pointcut Superclasses
|
||||
|
||||
Spring provides useful pointcut superclasses to help you to implement your own pointcuts.
|
||||
|
||||
Because static pointcuts are most useful, you should probably subclass
|
||||
`StaticMethodMatcherPointcut`. This requires implementing only one
|
||||
abstract method (although you can override other methods to customize behavior). The
|
||||
following example shows how to subclass `StaticMethodMatcherPointcut`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class TestStaticPointcut extends StaticMethodMatcherPointcut {
|
||||
|
||||
public boolean matches(Method m, Class targetClass) {
|
||||
// return true if custom criteria match
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class TestStaticPointcut : StaticMethodMatcherPointcut() {
|
||||
|
||||
override fun matches(method: Method, targetClass: Class<*>): Boolean {
|
||||
// return true if custom criteria match
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
There are also superclasses for dynamic pointcuts.
|
||||
You can use custom pointcuts with any advice type.
|
||||
|
||||
|
||||
|
||||
[[aop-api-pointcuts-custom]]
|
||||
== Custom Pointcuts
|
||||
|
||||
Because pointcuts in Spring AOP are Java classes rather than language features (as in
|
||||
AspectJ), you can declare custom pointcuts, whether static or dynamic. Custom
|
||||
pointcuts in Spring can be arbitrarily complex. However, we recommend using the AspectJ pointcut
|
||||
expression language, if you can.
|
||||
|
||||
NOTE: Later versions of Spring may offer support for "`semantic pointcuts`" as offered by JAC --
|
||||
for example, "`all methods that change instance variables in the target object.`"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
[[aop-prog]]
|
||||
= Creating AOP Proxies Programmatically with the `ProxyFactory`
|
||||
|
||||
It is easy to create AOP proxies programmatically with Spring. This lets you use
|
||||
Spring AOP without dependency on Spring IoC.
|
||||
|
||||
The interfaces implemented by the target object are
|
||||
automatically proxied. The following listing shows creation of a proxy for a target object, with one
|
||||
interceptor and one advisor:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
|
||||
factory.addAdvice(myMethodInterceptor);
|
||||
factory.addAdvisor(myAdvisor);
|
||||
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val factory = ProxyFactory(myBusinessInterfaceImpl)
|
||||
factory.addAdvice(myMethodInterceptor)
|
||||
factory.addAdvisor(myAdvisor)
|
||||
val tb = factory.proxy as MyBusinessInterface
|
||||
----
|
||||
======
|
||||
|
||||
The first step is to construct an object of type
|
||||
`org.springframework.aop.framework.ProxyFactory`. You can create this with a target
|
||||
object, as in the preceding example, or specify the interfaces to be proxied in an alternate
|
||||
constructor.
|
||||
|
||||
You can add advice (with interceptors as a specialized kind of advice), advisors, or both
|
||||
and manipulate them for the life of the `ProxyFactory`. If you add an
|
||||
`IntroductionInterceptionAroundAdvisor`, you can cause the proxy to implement additional
|
||||
interfaces.
|
||||
|
||||
There are also convenience methods on `ProxyFactory` (inherited from `AdvisedSupport`)
|
||||
that let you add other advice types, such as before and throws advice.
|
||||
`AdvisedSupport` is the superclass of both `ProxyFactory` and `ProxyFactoryBean`.
|
||||
|
||||
TIP: Integrating AOP proxy creation with the IoC framework is best practice in most
|
||||
applications. We recommend that you externalize configuration from Java code with AOP,
|
||||
as you should in general.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
[[aop-targetsource]]
|
||||
= Using `TargetSource` Implementations
|
||||
|
||||
Spring offers the concept of a `TargetSource`, expressed in the
|
||||
`org.springframework.aop.TargetSource` interface. This interface is responsible for
|
||||
returning the "`target object`" that implements the join point. The `TargetSource`
|
||||
implementation is asked for a target instance each time the AOP proxy handles a method
|
||||
invocation.
|
||||
|
||||
Developers who use Spring AOP do not normally need to work directly with `TargetSource` implementations, but
|
||||
this provides a powerful means of supporting pooling, hot swappable, and other
|
||||
sophisticated targets. For example, a pooling `TargetSource` can return a different target
|
||||
instance for each invocation, by using a pool to manage instances.
|
||||
|
||||
If you do not specify a `TargetSource`, a default implementation is used to wrap a
|
||||
local object. The same target is returned for each invocation (as you would expect).
|
||||
|
||||
The rest of this section describes the standard target sources provided with Spring and how you can use them.
|
||||
|
||||
TIP: When using a custom target source, your target will usually need to be a prototype
|
||||
rather than a singleton bean definition. This allows Spring to create a new target
|
||||
instance when required.
|
||||
|
||||
|
||||
|
||||
[[aop-ts-swap]]
|
||||
== Hot-swappable Target Sources
|
||||
|
||||
The `org.springframework.aop.target.HotSwappableTargetSource` exists to let the target
|
||||
of an AOP proxy be switched while letting callers keep their references to it.
|
||||
|
||||
Changing the target source's target takes effect immediately. The
|
||||
`HotSwappableTargetSource` is thread-safe.
|
||||
|
||||
You can change the target by using the `swap()` method on HotSwappableTargetSource, as the follow example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
|
||||
Object oldTarget = swapper.swap(newTarget);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
|
||||
val oldTarget = swapper.swap(newTarget)
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows the required XML definitions:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="initialTarget" class="mycompany.OldTarget"/>
|
||||
|
||||
<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
|
||||
<constructor-arg ref="initialTarget"/>
|
||||
</bean>
|
||||
|
||||
<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="targetSource" ref="swapper"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The preceding `swap()` call changes the target of the swappable bean. Clients that hold a
|
||||
reference to that bean are unaware of the change but immediately start hitting
|
||||
the new target.
|
||||
|
||||
Although this example does not add any advice (it is not necessary to add advice to
|
||||
use a `TargetSource`), any `TargetSource` can be used in conjunction with
|
||||
arbitrary advice.
|
||||
|
||||
|
||||
|
||||
[[aop-ts-pool]]
|
||||
== Pooling Target Sources
|
||||
|
||||
Using a pooling target source provides a similar programming model to stateless session
|
||||
EJBs, in which a pool of identical instances is maintained, with method invocations
|
||||
going to free objects in the pool.
|
||||
|
||||
A crucial difference between Spring pooling and SLSB pooling is that Spring pooling can
|
||||
be applied to any POJO. As with Spring in general, this service can be applied in a
|
||||
non-invasive way.
|
||||
|
||||
Spring provides support for Commons Pool 2.2, which provides a
|
||||
fairly efficient pooling implementation. You need the `commons-pool` Jar on your
|
||||
application's classpath to use this feature. You can also subclass
|
||||
`org.springframework.aop.target.AbstractPoolingTargetSource` to support any other
|
||||
pooling API.
|
||||
|
||||
NOTE: Commons Pool 1.5+ is also supported but is deprecated as of Spring Framework 4.2.
|
||||
|
||||
The following listing shows an example configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
|
||||
scope="prototype">
|
||||
... properties omitted
|
||||
</bean>
|
||||
|
||||
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
|
||||
<property name="targetBeanName" value="businessObjectTarget"/>
|
||||
<property name="maxSize" value="25"/>
|
||||
</bean>
|
||||
|
||||
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="targetSource" ref="poolTargetSource"/>
|
||||
<property name="interceptorNames" value="myInterceptor"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that the target object (`businessObjectTarget` in the preceding example) must be a
|
||||
prototype. This lets the `PoolingTargetSource` implementation create new instances
|
||||
of the target to grow the pool as necessary. See the {api-spring-framework}/aop/target/AbstractPoolingTargetSource.html[javadoc of
|
||||
`AbstractPoolingTargetSource`] and the concrete subclass you wish to use for information
|
||||
about its properties. `maxSize` is the most basic and is always guaranteed to be present.
|
||||
|
||||
In this case, `myInterceptor` is the name of an interceptor that would need to be
|
||||
defined in the same IoC context. However, you need not specify interceptors to
|
||||
use pooling. If you want only pooling and no other advice, do not set the
|
||||
`interceptorNames` property at all.
|
||||
|
||||
You can configure Spring to be able to cast any pooled object to the
|
||||
`org.springframework.aop.target.PoolingConfig` interface, which exposes information
|
||||
about the configuration and current size of the pool through an introduction. You
|
||||
need to define an advisor similar to the following:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
|
||||
<property name="targetObject" ref="poolTargetSource"/>
|
||||
<property name="targetMethod" value="getPoolingConfigMixin"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
This advisor is obtained by calling a convenience method on the
|
||||
`AbstractPoolingTargetSource` class, hence the use of `MethodInvokingFactoryBean`. This
|
||||
advisor's name (`poolConfigAdvisor`, here) must be in the list of interceptors names in
|
||||
the `ProxyFactoryBean` that exposes the pooled object.
|
||||
|
||||
The cast is defined as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
|
||||
System.out.println("Max pool size is " + conf.getMaxSize());
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val conf = beanFactory.getBean("businessObject") as PoolingConfig
|
||||
println("Max pool size is " + conf.maxSize)
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Pooling stateless service objects is not usually necessary. We do not believe it should
|
||||
be the default choice, as most stateless objects are naturally thread safe, and instance
|
||||
pooling is problematic if resources are cached.
|
||||
|
||||
Simpler pooling is available by using auto-proxying. You can set the `TargetSource` implementations
|
||||
used by any auto-proxy creator.
|
||||
|
||||
|
||||
|
||||
[[aop-ts-prototype]]
|
||||
== Prototype Target Sources
|
||||
|
||||
Setting up a "`prototype`" target source is similar to setting up a pooling `TargetSource`. In this
|
||||
case, a new instance of the target is created on every method invocation. Although
|
||||
the cost of creating a new object is not high in a modern JVM, the cost of wiring up the
|
||||
new object (satisfying its IoC dependencies) may be more expensive. Thus, you should not
|
||||
use this approach without very good reason.
|
||||
|
||||
To do this, you could modify the `poolTargetSource` definition shown earlier as follows
|
||||
(we also changed the name, for clarity):
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
|
||||
<property name="targetBeanName" ref="businessObjectTarget"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The only property is the name of the target bean. Inheritance is used in the
|
||||
`TargetSource` implementations to ensure consistent naming. As with the pooling target
|
||||
source, the target bean must be a prototype bean definition.
|
||||
|
||||
|
||||
|
||||
[[aop-ts-threadlocal]]
|
||||
== `ThreadLocal` Target Sources
|
||||
|
||||
`ThreadLocal` target sources are useful if you need an object to be created for each
|
||||
incoming request (per thread that is). The concept of a `ThreadLocal` provides a JDK-wide
|
||||
facility to transparently store a resource alongside a thread. Setting up a
|
||||
`ThreadLocalTargetSource` is pretty much the same as was explained for the other types
|
||||
of target source, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
|
||||
<property name="targetBeanName" value="businessObjectTarget"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
NOTE: `ThreadLocal` instances come with serious issues (potentially resulting in memory leaks) when
|
||||
incorrectly using them in multi-threaded and multi-classloader environments. You
|
||||
should always consider wrapping a `ThreadLocal` in some other class and never directly use
|
||||
the `ThreadLocal` itself (except in the wrapper class). Also, you should
|
||||
always remember to correctly set and unset (where the latter simply involves a call to
|
||||
`ThreadLocal.set(null)`) the resource local to the thread. Unsetting should be done in
|
||||
any case, since not unsetting it might result in problematic behavior. Spring's
|
||||
`ThreadLocal` support does this for you and should always be considered in favor of using
|
||||
`ThreadLocal` instances without other proper handling code.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
[[aop]]
|
||||
= Aspect Oriented Programming with Spring
|
||||
|
||||
Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by
|
||||
providing another way of thinking about program structure. The key unit of modularity
|
||||
in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects
|
||||
enable the modularization of concerns (such as transaction management) that cut across
|
||||
multiple types and objects. (Such concerns are often termed "crosscutting" concerns
|
||||
in AOP literature.)
|
||||
|
||||
One of the key components of Spring is the AOP framework. While the Spring IoC
|
||||
container does not depend on AOP (meaning you do not need to use AOP if you don't want
|
||||
to), AOP complements Spring IoC to provide a very capable middleware solution.
|
||||
|
||||
.Spring AOP with AspectJ pointcuts
|
||||
****
|
||||
Spring provides simple and powerful ways of writing custom aspects by using either a
|
||||
xref:core/aop/schema.adoc[schema-based approach] or the xref:core/aop/ataspectj.adoc[@AspectJ annotation style].
|
||||
Both of these styles offer fully typed advice and use of the AspectJ pointcut language
|
||||
while still using Spring AOP for weaving.
|
||||
|
||||
This chapter discusses the schema- and @AspectJ-based AOP support.
|
||||
The lower-level AOP support is discussed in xref:core/aop-api.adoc[the following chapter].
|
||||
****
|
||||
|
||||
AOP is used in the Spring Framework to:
|
||||
|
||||
* Provide declarative enterprise services. The most important such service is
|
||||
xref:data-access/transaction/declarative.adoc[declarative transaction management].
|
||||
* Let users implement custom aspects, complementing their use of OOP with AOP.
|
||||
|
||||
NOTE: If you are interested only in generic declarative services or other pre-packaged
|
||||
declarative middleware services such as pooling, you do not need to work directly with
|
||||
Spring AOP, and can skip most of this chapter.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
[[aop-aspectj-programmatic]]
|
||||
= Programmatic Creation of @AspectJ Proxies
|
||||
|
||||
In addition to declaring aspects in your configuration by using either `<aop:config>`
|
||||
or `<aop:aspectj-autoproxy>`, it is also possible to programmatically create proxies
|
||||
that advise target objects. For the full details of Spring's AOP API, see the
|
||||
xref:core/aop-api.adoc[next chapter]. Here, we want to focus on the ability to automatically
|
||||
create proxies by using @AspectJ aspects.
|
||||
|
||||
You can use the `org.springframework.aop.aspectj.annotation.AspectJProxyFactory` class
|
||||
to create a proxy for a target object that is advised by one or more @AspectJ aspects.
|
||||
The basic usage for this class is very simple, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
// create a factory that can generate a proxy for the given target object
|
||||
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
|
||||
|
||||
// add an aspect, the class must be an @AspectJ aspect
|
||||
// you can call this as many times as you need with different aspects
|
||||
factory.addAspect(SecurityManager.class);
|
||||
|
||||
// you can also add existing aspect instances, the type of the object supplied
|
||||
// must be an @AspectJ aspect
|
||||
factory.addAspect(usageTracker);
|
||||
|
||||
// now get the proxy object...
|
||||
MyInterfaceType proxy = factory.getProxy();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
// create a factory that can generate a proxy for the given target object
|
||||
val factory = AspectJProxyFactory(targetObject)
|
||||
|
||||
// add an aspect, the class must be an @AspectJ aspect
|
||||
// you can call this as many times as you need with different aspects
|
||||
factory.addAspect(SecurityManager::class.java)
|
||||
|
||||
// you can also add existing aspect instances, the type of the object supplied
|
||||
// must be an @AspectJ aspect
|
||||
factory.addAspect(usageTracker)
|
||||
|
||||
// now get the proxy object...
|
||||
val proxy = factory.getProxy<Any>()
|
||||
----
|
||||
======
|
||||
|
||||
See the {api-spring-framework}/aop/aspectj/annotation/AspectJProxyFactory.html[javadoc] for more information.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
[[aop-ataspectj]]
|
||||
= @AspectJ support
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
@AspectJ refers to a style of declaring aspects as regular Java classes annotated with
|
||||
annotations. The @AspectJ style was introduced by the
|
||||
https://www.eclipse.org/aspectj[AspectJ project] as part of the AspectJ 5 release. Spring
|
||||
interprets the same annotations as AspectJ 5, using a library supplied by AspectJ
|
||||
for pointcut parsing and matching. The AOP runtime is still pure Spring AOP, though, and
|
||||
there is no dependency on the AspectJ compiler or weaver.
|
||||
|
||||
NOTE: Using the AspectJ compiler and weaver enables use of the full AspectJ language and
|
||||
is discussed in xref:core/aop/using-aspectj.adoc[Using AspectJ with Spring Applications].
|
||||
|
||||
|
||||
|
||||
@@ -1,946 +0,0 @@
|
||||
[[aop-advice]]
|
||||
= Declaring Advice
|
||||
|
||||
Advice is associated with a pointcut expression and runs before, after, or around method
|
||||
executions matched by the pointcut. The pointcut expression may be either an _inline
|
||||
pointcut_ or a reference to a xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[_named pointcut_].
|
||||
|
||||
|
||||
[[aop-advice-before]]
|
||||
== Before Advice
|
||||
|
||||
You can declare before advice in an aspect by using the `@Before` annotation.
|
||||
|
||||
The following example uses an inline pointcut expression.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
|
||||
@Aspect
|
||||
public class BeforeExample {
|
||||
|
||||
@Before("execution(* com.xyz.dao.*.*(..))")
|
||||
public void doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
|
||||
@Aspect
|
||||
class BeforeExample {
|
||||
|
||||
@Before("execution(* com.xyz.dao.*.*(..))")
|
||||
fun doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If we use a xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[named pointcut], we can rewrite the preceding example
|
||||
as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
|
||||
@Aspect
|
||||
public class BeforeExample {
|
||||
|
||||
@Before("com.xyz.CommonPointcuts.dataAccessOperation()")
|
||||
public void doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Before
|
||||
|
||||
@Aspect
|
||||
class BeforeExample {
|
||||
|
||||
@Before("com.xyz.CommonPointcuts.dataAccessOperation()")
|
||||
fun doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[aop-advice-after-returning]]
|
||||
== After Returning Advice
|
||||
|
||||
After returning advice runs when a matched method execution returns normally.
|
||||
You can declare it by using the `@AfterReturning` annotation.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
|
||||
@Aspect
|
||||
public class AfterReturningExample {
|
||||
|
||||
@AfterReturning("execution(* com.xyz.dao.*.*(..))")
|
||||
public void doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterReturning
|
||||
|
||||
@Aspect
|
||||
class AfterReturningExample {
|
||||
|
||||
@AfterReturning("execution(* com.xyz.dao.*.*(..))")
|
||||
fun doAccessCheck() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: You can have multiple advice declarations (and other members as well),
|
||||
all inside the same aspect. We show only a single advice declaration in these
|
||||
examples to focus the effect of each one.
|
||||
|
||||
Sometimes, you need access in the advice body to the actual value that was returned.
|
||||
You can use the form of `@AfterReturning` that binds the return value to get that
|
||||
access, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
|
||||
@Aspect
|
||||
public class AfterReturningExample {
|
||||
|
||||
@AfterReturning(
|
||||
pointcut="execution(* com.xyz.dao.*.*(..))",
|
||||
returning="retVal")
|
||||
public void doAccessCheck(Object retVal) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterReturning
|
||||
|
||||
@Aspect
|
||||
class AfterReturningExample {
|
||||
|
||||
@AfterReturning(
|
||||
pointcut = "execution(* com.xyz.dao.*.*(..))",
|
||||
returning = "retVal")
|
||||
fun doAccessCheck(retVal: Any?) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The name used in the `returning` attribute must correspond to the name of a parameter
|
||||
in the advice method. When a method execution returns, the return value is passed to
|
||||
the advice method as the corresponding argument value. A `returning` clause also
|
||||
restricts matching to only those method executions that return a value of the
|
||||
specified type (in this case, `Object`, which matches any return value).
|
||||
|
||||
Please note that it is not possible to return a totally different reference when
|
||||
using after returning advice.
|
||||
|
||||
|
||||
[[aop-advice-after-throwing]]
|
||||
== After Throwing Advice
|
||||
|
||||
After throwing advice runs when a matched method execution exits by throwing an
|
||||
exception. You can declare it by using the `@AfterThrowing` annotation, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
|
||||
@Aspect
|
||||
public class AfterThrowingExample {
|
||||
|
||||
@AfterThrowing("execution(* com.xyz.dao.*.*(..))")
|
||||
public void doRecoveryActions() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterThrowing
|
||||
|
||||
@Aspect
|
||||
class AfterThrowingExample {
|
||||
|
||||
@AfterThrowing("execution(* com.xyz.dao.*.*(..))")
|
||||
fun doRecoveryActions() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Often, you want the advice to run only when exceptions of a given type are thrown,
|
||||
and you also often need access to the thrown exception in the advice body. You can
|
||||
use the `throwing` attribute to both restrict matching (if desired -- use `Throwable`
|
||||
as the exception type otherwise) and bind the thrown exception to an advice parameter.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
|
||||
@Aspect
|
||||
public class AfterThrowingExample {
|
||||
|
||||
@AfterThrowing(
|
||||
pointcut="execution(* com.xyz.dao.*.*(..))",
|
||||
throwing="ex")
|
||||
public void doRecoveryActions(DataAccessException ex) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.AfterThrowing
|
||||
|
||||
@Aspect
|
||||
class AfterThrowingExample {
|
||||
|
||||
@AfterThrowing(
|
||||
pointcut = "execution(* com.xyz.dao.*.*(..))",
|
||||
throwing = "ex")
|
||||
fun doRecoveryActions(ex: DataAccessException) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The name used in the `throwing` attribute must correspond to the name of a parameter in
|
||||
the advice method. When a method execution exits by throwing an exception, the exception
|
||||
is passed to the advice method as the corresponding argument value. A `throwing` clause
|
||||
also restricts matching to only those method executions that throw an exception of the
|
||||
specified type (`DataAccessException`, in this case).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Note that `@AfterThrowing` does not indicate a general exception handling callback.
|
||||
Specifically, an `@AfterThrowing` advice method is only supposed to receive exceptions
|
||||
from the join point (user-declared target method) itself but not from an accompanying
|
||||
`@After`/`@AfterReturning` method.
|
||||
====
|
||||
|
||||
|
||||
[[aop-advice-after-finally]]
|
||||
== After (Finally) Advice
|
||||
|
||||
After (finally) advice runs when a matched method execution exits. It is declared by
|
||||
using the `@After` annotation. After advice must be prepared to handle both normal and
|
||||
exception return conditions. It is typically used for releasing resources and similar
|
||||
purposes. The following example shows how to use after finally advice:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.After;
|
||||
|
||||
@Aspect
|
||||
public class AfterFinallyExample {
|
||||
|
||||
@After("execution(* com.xyz.dao.*.*(..))")
|
||||
public void doReleaseLock() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.After
|
||||
|
||||
@Aspect
|
||||
class AfterFinallyExample {
|
||||
|
||||
@After("execution(* com.xyz.dao.*.*(..))")
|
||||
fun doReleaseLock() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Note that `@After` advice in AspectJ is defined as "after finally advice", analogous
|
||||
to a finally block in a try-catch statement. It will be invoked for any outcome,
|
||||
normal return or exception thrown from the join point (user-declared target method),
|
||||
in contrast to `@AfterReturning` which only applies to successful normal returns.
|
||||
====
|
||||
|
||||
|
||||
[[aop-ataspectj-around-advice]]
|
||||
== Around Advice
|
||||
|
||||
The last kind of advice is _around_ advice. Around advice runs "around" a matched
|
||||
method's execution. It has the opportunity to do work both before and after the method
|
||||
runs and to determine when, how, and even if the method actually gets to run at all.
|
||||
Around advice is often used if you need to share state before and after a method
|
||||
execution in a thread-safe manner – for example, starting and stopping a timer.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Always use the least powerful form of advice that meets your requirements.
|
||||
|
||||
For example, do not use _around_ advice if _before_ advice is sufficient for your needs.
|
||||
====
|
||||
|
||||
Around advice is declared by annotating a method with the `@Around` annotation. The
|
||||
method should declare `Object` as its return type, and the first parameter of the method
|
||||
must be of type `ProceedingJoinPoint`. Within the body of the advice method, you must
|
||||
invoke `proceed()` on the `ProceedingJoinPoint` in order for the underlying method to
|
||||
run. Invoking `proceed()` without arguments will result in the caller's original
|
||||
arguments being supplied to the underlying method when it is invoked. For advanced use
|
||||
cases, there is an overloaded variant of the `proceed()` method which accepts an array of
|
||||
arguments (`Object[]`). The values in the array will be used as the arguments to the
|
||||
underlying method when it is invoked.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The behavior of `proceed` when called with an `Object[]` is a little different than the
|
||||
behavior of `proceed` for around advice compiled by the AspectJ compiler. For around
|
||||
advice written using the traditional AspectJ language, the number of arguments passed to
|
||||
`proceed` must match the number of arguments passed to the around advice (not the number
|
||||
of arguments taken by the underlying join point), and the value passed to proceed in a
|
||||
given argument position supplants the original value at the join point for the entity the
|
||||
value was bound to (do not worry if this does not make sense right now).
|
||||
|
||||
The approach taken by Spring is simpler and a better match to its proxy-based,
|
||||
execution-only semantics. You only need to be aware of this difference if you compile
|
||||
`@AspectJ` aspects written for Spring and use `proceed` with arguments with the AspectJ
|
||||
compiler and weaver. There is a way to write such aspects that is 100% compatible across
|
||||
both Spring AOP and AspectJ, and this is discussed in the
|
||||
xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-proceeding-with-the-call[following section on advice parameters].
|
||||
====
|
||||
|
||||
The value returned by the around advice is the return value seen by the caller of the
|
||||
method. For example, a simple caching aspect could return a value from a cache if it has
|
||||
one or invoke `proceed()` (and return that value) if it does not. Note that `proceed`
|
||||
may be invoked once, many times, or not at all within the body of the around advice. All
|
||||
of these are legal.
|
||||
|
||||
WARNING: If you declare the return type of your around advice method as `void`, `null`
|
||||
will always be returned to the caller, effectively ignoring the result of any invocation
|
||||
of `proceed()`. It is therefore recommended that an around advice method declare a return
|
||||
type of `Object`. The advice method should typically return the value returned from an
|
||||
invocation of `proceed()`, even if the underlying method has a `void` return type.
|
||||
However, the advice may optionally return a cached value, a wrapped value, or some other
|
||||
value depending on the use case.
|
||||
|
||||
The following example shows how to use around advice:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
|
||||
@Aspect
|
||||
public class AroundExample {
|
||||
|
||||
@Around("execution(* com.xyz..service.*.*(..))")
|
||||
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// start stopwatch
|
||||
Object retVal = pjp.proceed();
|
||||
// stop stopwatch
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Around
|
||||
import org.aspectj.lang.ProceedingJoinPoint
|
||||
|
||||
@Aspect
|
||||
class AroundExample {
|
||||
|
||||
@Around("execution(* com.xyz..service.*.*(..))")
|
||||
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
|
||||
// start stopwatch
|
||||
val retVal = pjp.proceed()
|
||||
// stop stopwatch
|
||||
return retVal
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[aop-ataspectj-advice-params]]
|
||||
== Advice Parameters
|
||||
|
||||
Spring offers fully typed advice, meaning that you declare the parameters you need in the
|
||||
advice signature (as we saw earlier for the returning and throwing examples) rather than
|
||||
work with `Object[]` arrays all the time. We see how to make argument and other contextual
|
||||
values available to the advice body later in this section. First, we take a look at how to
|
||||
write generic advice that can find out about the method the advice is currently advising.
|
||||
|
||||
[[aop-ataspectj-advice-params-the-joinpoint]]
|
||||
=== Access to the Current `JoinPoint`
|
||||
|
||||
Any advice method may declare, as its first parameter, a parameter of type
|
||||
`org.aspectj.lang.JoinPoint`. Note that around advice is required to declare a first
|
||||
parameter of type `ProceedingJoinPoint`, which is a subclass of `JoinPoint`.
|
||||
|
||||
The `JoinPoint` interface provides a number of useful methods:
|
||||
|
||||
* `getArgs()`: Returns the method arguments.
|
||||
* `getThis()`: Returns the proxy object.
|
||||
* `getTarget()`: Returns the target object.
|
||||
* `getSignature()`: Returns a description of the method that is being advised.
|
||||
* `toString()`: Prints a useful description of the method being advised.
|
||||
|
||||
See the https://www.eclipse.org/aspectj/doc/released/runtime-api/org/aspectj/lang/JoinPoint.html[javadoc] for more detail.
|
||||
|
||||
[[aop-ataspectj-advice-params-passing]]
|
||||
=== Passing Parameters to Advice
|
||||
|
||||
We have already seen how to bind the returned value or exception value (using after
|
||||
returning and after throwing advice). To make argument values available to the advice
|
||||
body, you can use the binding form of `args`. If you use a parameter name in place of a
|
||||
type name in an `args` expression, the value of the corresponding argument is passed as
|
||||
the parameter value when the advice is invoked. An example should make this clearer.
|
||||
Suppose you want to advise the execution of DAO operations that take an `Account`
|
||||
object as the first parameter, and you need access to the account in the advice body.
|
||||
You could write the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
public void validateAccount(Account account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
fun validateAccount(account: Account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The `args(account,..)` part of the pointcut expression serves two purposes. First, it
|
||||
restricts matching to only those method executions where the method takes at least one
|
||||
parameter, and the argument passed to that parameter is an instance of `Account`.
|
||||
Second, it makes the actual `Account` object available to the advice through the `account`
|
||||
parameter.
|
||||
|
||||
Another way of writing this is to declare a pointcut that "provides" the `Account`
|
||||
object value when it matches a join point, and then refer to the named pointcut
|
||||
from the advice. This would look as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
private void accountDataAccessOperation(Account account) {}
|
||||
|
||||
@Before("accountDataAccessOperation(account)")
|
||||
public void validateAccount(Account account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
|
||||
private fun accountDataAccessOperation(account: Account) {
|
||||
}
|
||||
|
||||
@Before("accountDataAccessOperation(account)")
|
||||
fun validateAccount(account: Account) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
See the AspectJ programming guide for more details.
|
||||
|
||||
The proxy object (`this`), target object (`target`), and annotations (`@within`,
|
||||
`@target`, `@annotation`, and `@args`) can all be bound in a similar fashion. The next
|
||||
set of examples shows how to match the execution of methods annotated with an
|
||||
`@Auditable` annotation and extract the audit code:
|
||||
|
||||
The following shows the definition of the `@Auditable` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Auditable {
|
||||
AuditCode value();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Auditable(val value: AuditCode)
|
||||
----
|
||||
======
|
||||
|
||||
The following shows the advice that matches the execution of `@Auditable` methods:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1>
|
||||
public void audit(Auditable auditable) {
|
||||
AuditCode code = auditable.value();
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1>
|
||||
fun audit(auditable: Auditable) {
|
||||
val code = auditable.value()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
======
|
||||
|
||||
[[aop-ataspectj-advice-params-generics]]
|
||||
=== Advice Parameters and Generics
|
||||
|
||||
Spring AOP can handle generics used in class declarations and method parameters. Suppose
|
||||
you have a generic type like the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
public interface Sample<T> {
|
||||
void sampleGenericMethod(T param);
|
||||
void sampleGenericCollectionMethod(Collection<T> param);
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
interface Sample<T> {
|
||||
fun sampleGenericMethod(param: T)
|
||||
fun sampleGenericCollectionMethod(param: Collection<T>)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can restrict interception of method types to certain parameter types by
|
||||
tying the advice parameter to the parameter type for which you want to intercept the method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
|
||||
public void beforeSampleMethod(MyType param) {
|
||||
// Advice implementation
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
|
||||
fun beforeSampleMethod(param: MyType) {
|
||||
// Advice implementation
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
This approach does not work for generic collections. So you cannot define a
|
||||
pointcut as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
|
||||
public void beforeSampleMethod(Collection<MyType> param) {
|
||||
// Advice implementation
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
|
||||
fun beforeSampleMethod(param: Collection<MyType>) {
|
||||
// Advice implementation
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
To make this work, we would have to inspect every element of the collection, which is not
|
||||
reasonable, as we also cannot decide how to treat `null` values in general. To achieve
|
||||
something similar to this, you have to type the parameter to `Collection<?>` and manually
|
||||
check the type of the elements.
|
||||
|
||||
[[aop-ataspectj-advice-params-names]]
|
||||
=== Determining Argument Names
|
||||
|
||||
Parameter binding in advice invocations relies on matching the names used in pointcut
|
||||
expressions to the parameter names declared in advice and pointcut method signatures.
|
||||
|
||||
NOTE: This section uses the terms _argument_ and _parameter_ interchangeably, since
|
||||
AspectJ APIs refer to parameter names as argument names.
|
||||
|
||||
Spring AOP uses the following `ParameterNameDiscoverer` implementations to determine
|
||||
parameter names. Each discoverer will be given a chance to discover parameter names, and
|
||||
the first successful discoverer wins. If none of the registered discoverers is capable
|
||||
of determining parameter names, an exception will be thrown.
|
||||
|
||||
`AspectJAnnotationParameterNameDiscoverer` :: Uses parameter names that have been explicitly
|
||||
specified by the user via the `argNames` attribute in the corresponding advice or
|
||||
pointcut annotation. See xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params-names-explicit[Explicit Argument Names] for details.
|
||||
`KotlinReflectionParameterNameDiscoverer` :: Uses Kotlin reflection APIs to determine
|
||||
parameter names. This discoverer is only used if such APIs are present on the classpath.
|
||||
`StandardReflectionParameterNameDiscoverer` :: Uses the standard `java.lang.reflect.Parameter`
|
||||
API to determine parameter names. Requires that code be compiled with the `-parameters`
|
||||
flag for `javac`. Recommended approach on Java 8+.
|
||||
`LocalVariableTableParameterNameDiscoverer` :: Analyzes the local variable table available
|
||||
in the byte code of the advice class to determine parameter names from debug information.
|
||||
Requires that code be compiled with debug symbols (`-g:vars` at a minimum). Deprecated
|
||||
as of Spring Framework 6.0 for removal in Spring Framework 6.1 in favor of compiling
|
||||
code with `-parameters`. Not supported in a GraalVM native image.
|
||||
`AspectJAdviceParameterNameDiscoverer` :: Deduces parameter names from the pointcut
|
||||
expression, `returning`, and `throwing` clauses. See the
|
||||
{api-spring-framework}/aop/aspectj/AspectJAdviceParameterNameDiscoverer.html[javadoc]
|
||||
for details on the algorithm used.
|
||||
|
||||
[[aop-ataspectj-advice-params-names-explicit]]
|
||||
=== Explicit Argument Names
|
||||
|
||||
@AspectJ advice and pointcut annotations have an optional `argNames` attribute that you
|
||||
can use to specify the argument names of the annotated method.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If an @AspectJ aspect has been compiled by the AspectJ compiler (`ajc`) even without
|
||||
debug information, you do not need to add the `argNames` attribute, since the compiler
|
||||
retains the needed information.
|
||||
|
||||
Similarly, if an @AspectJ aspect has been compiled with `javac` using the `-parameters`
|
||||
flag, you do not need to add the `argNames` attribute, since the compiler retains the
|
||||
needed information.
|
||||
====
|
||||
|
||||
The following example shows how to use the `argNames` attribute:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
argNames = "bean,auditable") // <2>
|
||||
public void audit(Object bean, Auditable auditable) {
|
||||
AuditCode code = auditable.value();
|
||||
// ... use code and bean
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
argNames = "bean,auditable") // <2>
|
||||
fun audit(bean: Any, auditable: Auditable) {
|
||||
val code = auditable.value()
|
||||
// ... use code and bean
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
======
|
||||
|
||||
If the first parameter is of type `JoinPoint`, `ProceedingJoinPoint`, or
|
||||
`JoinPoint.StaticPart`, you can omit the name of the parameter from the value of the
|
||||
`argNames` attribute. For example, if you modify the preceding advice to receive the join
|
||||
point object, the `argNames` attribute does not need to include it:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
argNames = "bean,auditable") // <2>
|
||||
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
|
||||
AuditCode code = auditable.value();
|
||||
// ... use code, bean, and jp
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before(
|
||||
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
|
||||
argNames = "bean,auditable") // <2>
|
||||
fun audit(jp: JoinPoint, bean: Any, auditable: Auditable) {
|
||||
val code = auditable.value()
|
||||
// ... use code, bean, and jp
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
<2> Declares `bean` and `auditable` as the argument names.
|
||||
======
|
||||
|
||||
The special treatment given to the first parameter of type `JoinPoint`,
|
||||
`ProceedingJoinPoint`, or `JoinPoint.StaticPart` is particularly convenient for advice
|
||||
methods that do not collect any other join point context. In such situations, you may
|
||||
omit the `argNames` attribute. For example, the following advice does not need to declare
|
||||
the `argNames` attribute:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod()") // <1>
|
||||
public void audit(JoinPoint jp) {
|
||||
// ... use jp
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Before("com.xyz.Pointcuts.publicMethod()") // <1>
|
||||
fun audit(jp: JoinPoint) {
|
||||
// ... use jp
|
||||
}
|
||||
----
|
||||
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
|
||||
======
|
||||
|
||||
|
||||
[[aop-ataspectj-advice-proceeding-with-the-call]]
|
||||
=== Proceeding with Arguments
|
||||
|
||||
We remarked earlier that we would describe how to write a `proceed` call with
|
||||
arguments that works consistently across Spring AOP and AspectJ. The solution is
|
||||
to ensure that the advice signature binds each of the method parameters in order.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Around("execution(List<Account> find*(..)) && " +
|
||||
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
|
||||
"args(accountHolderNamePattern)") // <1>
|
||||
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
|
||||
String accountHolderNamePattern) throws Throwable {
|
||||
String newPattern = preProcess(accountHolderNamePattern);
|
||||
return pjp.proceed(new Object[] {newPattern});
|
||||
}
|
||||
----
|
||||
<1> References the `inDataAccessLayer` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Around("execution(List<Account> find*(..)) && " +
|
||||
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
|
||||
"args(accountHolderNamePattern)") // <1>
|
||||
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
|
||||
accountHolderNamePattern: String): Any? {
|
||||
val newPattern = preProcess(accountHolderNamePattern)
|
||||
return pjp.proceed(arrayOf<Any>(newPattern))
|
||||
}
|
||||
----
|
||||
<1> References the `inDataAccessLayer` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
======
|
||||
|
||||
In many cases, you do this binding anyway (as in the preceding example).
|
||||
|
||||
|
||||
[[aop-ataspectj-advice-ordering]]
|
||||
== Advice Ordering
|
||||
|
||||
What happens when multiple pieces of advice all want to run at the same join point?
|
||||
Spring AOP follows the same precedence rules as AspectJ to determine the order of advice
|
||||
execution. The highest precedence advice runs first "on the way in" (so, given two pieces
|
||||
of before advice, the one with highest precedence runs first). "On the way out" from a
|
||||
join point, the highest precedence advice runs last (so, given two pieces of after
|
||||
advice, the one with the highest precedence will run second).
|
||||
|
||||
When two pieces of advice defined in different aspects both need to run at the same
|
||||
join point, unless you specify otherwise, the order of execution is undefined. You can
|
||||
control the order of execution by specifying precedence. This is done in the normal
|
||||
Spring way by either implementing the `org.springframework.core.Ordered` interface in
|
||||
the aspect class or annotating it with the `@Order` annotation. Given two aspects, the
|
||||
aspect returning the lower value from `Ordered.getOrder()` (or the annotation value) has
|
||||
the higher precedence.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Each of the distinct advice types of a particular aspect is conceptually meant to apply
|
||||
to the join point directly. As a consequence, an `@AfterThrowing` advice method is not
|
||||
supposed to receive an exception from an accompanying `@After`/`@AfterReturning` method.
|
||||
|
||||
As of Spring Framework 5.2.7, advice methods defined in the same `@Aspect` class that
|
||||
need to run at the same join point are assigned precedence based on their advice type in
|
||||
the following order, from highest to lowest precedence: `@Around`, `@Before`, `@After`,
|
||||
`@AfterReturning`, `@AfterThrowing`. Note, however, that an `@After` advice method will
|
||||
effectively be invoked after any `@AfterReturning` or `@AfterThrowing` advice methods
|
||||
in the same aspect, following AspectJ's "after finally advice" semantics for `@After`.
|
||||
|
||||
When two pieces of the same type of advice (for example, two `@After` advice methods)
|
||||
defined in the same `@Aspect` class both need to run at the same join point, the ordering
|
||||
is undefined (since there is no way to retrieve the source code declaration order through
|
||||
reflection for javac-compiled classes). Consider collapsing such advice methods into one
|
||||
advice method per join point in each `@Aspect` class or refactor the pieces of advice into
|
||||
separate `@Aspect` classes that you can order at the aspect level via `Ordered` or `@Order`.
|
||||
====
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
[[aop-aspectj-support]]
|
||||
= Enabling @AspectJ Support
|
||||
|
||||
To use @AspectJ aspects in a Spring configuration, you need to enable Spring support for
|
||||
configuring Spring AOP based on @AspectJ aspects and auto-proxying beans based on
|
||||
whether or not they are advised by those aspects. By auto-proxying, we mean that, if Spring
|
||||
determines that a bean is advised by one or more aspects, it automatically generates
|
||||
a proxy for that bean to intercept method invocations and ensures that advice is run
|
||||
as needed.
|
||||
|
||||
The @AspectJ support can be enabled with XML- or Java-style configuration. In either
|
||||
case, you also need to ensure that AspectJ's `aspectjweaver.jar` library is on the
|
||||
classpath of your application (version 1.9 or later). This library is available in the
|
||||
`lib` directory of an AspectJ distribution or from the Maven Central repository.
|
||||
|
||||
|
||||
[[aop-enable-aspectj-java]]
|
||||
== Enabling @AspectJ Support with Java Configuration
|
||||
|
||||
To enable @AspectJ support with Java `@Configuration`, add the `@EnableAspectJAutoProxy`
|
||||
annotation, as the following example shows:
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
class AppConfig
|
||||
----
|
||||
======
|
||||
|
||||
[[aop-enable-aspectj-xml]]
|
||||
== Enabling @AspectJ Support with XML Configuration
|
||||
|
||||
To enable @AspectJ support with XML-based configuration, use the `aop:aspectj-autoproxy`
|
||||
element, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
----
|
||||
|
||||
This assumes that you use schema support as described in
|
||||
xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration].
|
||||
See xref:core/appendix/xsd-schemas.adoc#aop[the AOP schema] for how to
|
||||
import the tags in the `aop` namespace.
|
||||
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
[[aop-at-aspectj]]
|
||||
= Declaring an Aspect
|
||||
|
||||
With @AspectJ support enabled, any bean defined in your application context with a
|
||||
class that is an @AspectJ aspect (has the `@Aspect` annotation) is automatically
|
||||
detected by Spring and used to configure Spring AOP. The next two examples show the
|
||||
minimal steps required for a not-very-useful aspect.
|
||||
|
||||
The first of the two examples shows a regular bean definition in the application context
|
||||
that points to a bean class that is annotated with `@Aspect`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="myAspect" class="com.xyz.NotVeryUsefulAspect">
|
||||
<!-- configure properties of the aspect here -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
The second of the two examples shows the `NotVeryUsefulAspect` class definition, which is
|
||||
annotated with `@Aspect`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
@Aspect
|
||||
public class NotVeryUsefulAspect {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
|
||||
@Aspect
|
||||
class NotVeryUsefulAspect
|
||||
----
|
||||
======
|
||||
|
||||
Aspects (classes annotated with `@Aspect`) can have methods and fields, the same as any
|
||||
other class. They can also contain pointcut, advice, and introduction (inter-type)
|
||||
declarations.
|
||||
|
||||
.Autodetecting aspects through component scanning
|
||||
NOTE: You can register aspect classes as regular beans in your Spring XML configuration,
|
||||
via `@Bean` methods in `@Configuration` classes, or have Spring autodetect them through
|
||||
classpath scanning -- the same as any other Spring-managed bean. However, note that the
|
||||
`@Aspect` annotation is not sufficient for autodetection in the classpath. For that
|
||||
purpose, you need to add a separate `@Component` annotation (or, alternatively, a custom
|
||||
stereotype annotation that qualifies, as per the rules of Spring's component scanner).
|
||||
|
||||
.Advising aspects with other aspects?
|
||||
NOTE: In Spring AOP, aspects themselves cannot be the targets of advice from other
|
||||
aspects. The `@Aspect` annotation on a class marks it as an aspect and, hence, excludes
|
||||
it from auto-proxying.
|
||||
|
||||
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
[[aop-ataspectj-example]]
|
||||
= An AOP Example
|
||||
|
||||
Now that you have seen how all the constituent parts work, we can put them together to do
|
||||
something useful.
|
||||
|
||||
The execution of business services can sometimes fail due to concurrency issues (for
|
||||
example, a deadlock loser). If the operation is retried, it is likely to succeed
|
||||
on the next try. For business services where it is appropriate to retry in such
|
||||
conditions (idempotent operations that do not need to go back to the user for conflict
|
||||
resolution), we want to transparently retry the operation to avoid the client seeing a
|
||||
`PessimisticLockingFailureException`. This is a requirement that clearly cuts across
|
||||
multiple services in the service layer and, hence, is ideal for implementing through an
|
||||
aspect.
|
||||
|
||||
Because we want to retry the operation, we need to use around advice so that we can
|
||||
call `proceed` multiple times. The following listing shows the basic aspect implementation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect
|
||||
public class ConcurrentOperationExecutor implements Ordered {
|
||||
|
||||
private static final int DEFAULT_MAX_RETRIES = 2;
|
||||
|
||||
private int maxRetries = DEFAULT_MAX_RETRIES;
|
||||
private int order = 1;
|
||||
|
||||
public void setMaxRetries(int maxRetries) {
|
||||
this.maxRetries = maxRetries;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
int numAttempts = 0;
|
||||
PessimisticLockingFailureException lockFailureException;
|
||||
do {
|
||||
numAttempts++;
|
||||
try {
|
||||
return pjp.proceed();
|
||||
}
|
||||
catch(PessimisticLockingFailureException ex) {
|
||||
lockFailureException = ex;
|
||||
}
|
||||
} while(numAttempts <= this.maxRetries);
|
||||
throw lockFailureException;
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect
|
||||
class ConcurrentOperationExecutor : Ordered {
|
||||
|
||||
private val DEFAULT_MAX_RETRIES = 2
|
||||
private var maxRetries = DEFAULT_MAX_RETRIES
|
||||
private var order = 1
|
||||
|
||||
fun setMaxRetries(maxRetries: Int) {
|
||||
this.maxRetries = maxRetries
|
||||
}
|
||||
|
||||
override fun getOrder(): Int {
|
||||
return this.order
|
||||
}
|
||||
|
||||
fun setOrder(order: Int) {
|
||||
this.order = order
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
var numAttempts = 0
|
||||
var lockFailureException: PessimisticLockingFailureException
|
||||
do {
|
||||
numAttempts++
|
||||
try {
|
||||
return pjp.proceed()
|
||||
} catch (ex: PessimisticLockingFailureException) {
|
||||
lockFailureException = ex
|
||||
}
|
||||
|
||||
} while (numAttempts <= this.maxRetries)
|
||||
throw lockFailureException
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
======
|
||||
|
||||
Note that the aspect implements the `Ordered` interface so that we can set the precedence of
|
||||
the aspect higher than the transaction advice (we want a fresh transaction each time we
|
||||
retry). The `maxRetries` and `order` properties are both configured by Spring. The
|
||||
main action happens in the `doConcurrentOperation` around advice. Notice that, for the
|
||||
moment, we apply the retry logic to each `businessService`. We try to proceed,
|
||||
and if we fail with a `PessimisticLockingFailureException`, we try again, unless
|
||||
we have exhausted all of our retry attempts.
|
||||
|
||||
The corresponding Spring configuration follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<bean id="concurrentOperationExecutor"
|
||||
class="com.xyz.service.impl.ConcurrentOperationExecutor">
|
||||
<property name="maxRetries" value="3"/>
|
||||
<property name="order" value="100"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
To refine the aspect so that it retries only idempotent operations, we might define the following
|
||||
`Idempotent` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// marker annotation
|
||||
public @interface Idempotent {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
// marker annotation
|
||||
annotation class Idempotent
|
||||
----
|
||||
======
|
||||
|
||||
We can then use the annotation to annotate the implementation of service operations. The change
|
||||
to the aspect to retry only idempotent operations involves refining the pointcut
|
||||
expression so that only `@Idempotent` operations match, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
[[aop-instantiation-models]]
|
||||
= Aspect Instantiation Models
|
||||
|
||||
NOTE: This is an advanced topic. If you are just starting out with AOP, you can safely skip
|
||||
it until later.
|
||||
|
||||
By default, there is a single instance of each aspect within the application
|
||||
context. AspectJ calls this the singleton instantiation model. It is possible to define
|
||||
aspects with alternate lifecycles. Spring supports AspectJ's `perthis` and `pertarget`
|
||||
instantiation models; `percflow`, `percflowbelow`, and `pertypewithin` are not currently
|
||||
supported.
|
||||
|
||||
You can declare a `perthis` aspect by specifying a `perthis` clause in the `@Aspect`
|
||||
annotation. Consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
|
||||
public class MyAspect {
|
||||
|
||||
private int someState;
|
||||
|
||||
@Before("execution(* com.xyz..service.*.*(..))")
|
||||
public void recordServiceUsage() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
|
||||
class MyAspect {
|
||||
|
||||
private val someState: Int = 0
|
||||
|
||||
@Before("execution(* com.xyz..service.*.*(..))")
|
||||
fun recordServiceUsage() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example, the effect of the `perthis` clause is that one aspect instance
|
||||
is created for each unique service object that performs a business service (each unique
|
||||
object bound to `this` at join points matched by the pointcut expression). The aspect
|
||||
instance is created the first time that a method is invoked on the service object. The
|
||||
aspect goes out of scope when the service object goes out of scope. Before the aspect
|
||||
instance is created, none of the advice within it runs. As soon as the aspect instance
|
||||
has been created, the advice declared within it runs at matched join points, but only
|
||||
when the service object is the one with which this aspect is associated. See the AspectJ
|
||||
Programming Guide for more information on `per` clauses.
|
||||
|
||||
The `pertarget` instantiation model works in exactly the same way as `perthis`, but it
|
||||
creates one aspect instance for each unique target object at matched join points.
|
||||
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
[[aop-introductions]]
|
||||
= Introductions
|
||||
|
||||
Introductions (known as inter-type declarations in AspectJ) enable an aspect to declare
|
||||
that advised objects implement a given interface, and to provide an implementation of
|
||||
that interface on behalf of those objects.
|
||||
|
||||
You can make an introduction by using the `@DeclareParents` annotation. This annotation
|
||||
is used to declare that matching types have a new parent (hence the name). For example,
|
||||
given an interface named `UsageTracked` and an implementation of that interface named
|
||||
`DefaultUsageTracked`, the following aspect declares that all implementors of service
|
||||
interfaces also implement the `UsageTracked` interface (e.g. for statistics via JMX):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect
|
||||
public class UsageTracking {
|
||||
|
||||
@DeclareParents(value="com.xyz.service.*+", defaultImpl=DefaultUsageTracked.class)
|
||||
public static UsageTracked mixin;
|
||||
|
||||
@Before("execution(* com.xyz..service.*.*(..)) && this(usageTracked)")
|
||||
public void recordUsage(UsageTracked usageTracked) {
|
||||
usageTracked.incrementUseCount();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect
|
||||
class UsageTracking {
|
||||
|
||||
companion object {
|
||||
@DeclareParents(value = "com.xyz.service.*+",
|
||||
defaultImpl = DefaultUsageTracked::class)
|
||||
lateinit var mixin: UsageTracked
|
||||
}
|
||||
|
||||
@Before("execution(* com.xyz..service.*.*(..)) && this(usageTracked)")
|
||||
fun recordUsage(usageTracked: UsageTracked) {
|
||||
usageTracked.incrementUseCount()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The interface to be implemented is determined by the type of the annotated field. The
|
||||
`value` attribute of the `@DeclareParents` annotation is an AspectJ type pattern. Any
|
||||
bean of a matching type implements the `UsageTracked` interface. Note that, in the
|
||||
before advice of the preceding example, service beans can be directly used as
|
||||
implementations of the `UsageTracked` interface. If accessing a bean programmatically,
|
||||
you would write the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
val usageTracked = context.getBean("myService", UsageTracked.class)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -1,586 +0,0 @@
|
||||
[[aop-pointcuts]]
|
||||
= Declaring a Pointcut
|
||||
|
||||
Pointcuts determine join points of interest and thus enable us to control
|
||||
when advice runs. Spring AOP only supports method execution join points for Spring
|
||||
beans, so you can think of a pointcut as matching the execution of methods on Spring
|
||||
beans. A pointcut declaration has two parts: a signature comprising a name and any
|
||||
parameters and a pointcut expression that determines exactly which method
|
||||
executions we are interested in. In the @AspectJ annotation-style of AOP, a pointcut
|
||||
signature is provided by a regular method definition, and the pointcut expression is
|
||||
indicated by using the `@Pointcut` annotation (the method serving as the pointcut signature
|
||||
must have a `void` return type).
|
||||
|
||||
An example may help make this distinction between a pointcut signature and a pointcut
|
||||
expression clear. The following example defines a pointcut named `anyOldTransfer` that
|
||||
matches the execution of any method named `transfer`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Pointcut("execution(* transfer(..))") // the pointcut expression
|
||||
private void anyOldTransfer() {} // the pointcut signature
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Pointcut("execution(* transfer(..))") // the pointcut expression
|
||||
private fun anyOldTransfer() {} // the pointcut signature
|
||||
----
|
||||
======
|
||||
|
||||
The pointcut expression that forms the value of the `@Pointcut` annotation is a regular
|
||||
AspectJ pointcut expression. For a full discussion of AspectJ's pointcut language, see
|
||||
the https://www.eclipse.org/aspectj/doc/released/progguide/index.html[AspectJ
|
||||
Programming Guide] (and, for extensions, the
|
||||
https://www.eclipse.org/aspectj/doc/released/adk15notebook/index.html[AspectJ 5
|
||||
Developer's Notebook]) or one of the books on AspectJ (such as _Eclipse AspectJ_, by Colyer
|
||||
et al., or _AspectJ in Action_, by Ramnivas Laddad).
|
||||
|
||||
|
||||
[[aop-pointcuts-designators]]
|
||||
== Supported Pointcut Designators
|
||||
|
||||
Spring AOP supports the following AspectJ pointcut designators (PCD) for use in pointcut
|
||||
expressions:
|
||||
|
||||
* `execution`: For matching method execution join points. This is the primary
|
||||
pointcut designator to use when working with Spring AOP.
|
||||
* `within`: Limits matching to join points within certain types (the execution
|
||||
of a method declared within a matching type when using Spring AOP).
|
||||
* `this`: Limits matching to join points (the execution of methods when using Spring
|
||||
AOP) where the bean reference (Spring AOP proxy) is an instance of the given type.
|
||||
* `target`: Limits matching to join points (the execution of methods when using
|
||||
Spring AOP) where the target object (application object being proxied) is an instance
|
||||
of the given type.
|
||||
* `args`: Limits matching to join points (the execution of methods when using Spring
|
||||
AOP) where the arguments are instances of the given types.
|
||||
* `@target`: Limits matching to join points (the execution of methods when using
|
||||
Spring AOP) where the class of the executing object has an annotation of the given type.
|
||||
* `@args`: Limits matching to join points (the execution of methods when using Spring
|
||||
AOP) where the runtime type of the actual arguments passed have annotations of the
|
||||
given types.
|
||||
* `@within`: Limits matching to join points within types that have the given
|
||||
annotation (the execution of methods declared in types with the given annotation when
|
||||
using Spring AOP).
|
||||
* `@annotation`: Limits matching to join points where the subject of the join point
|
||||
(the method being run in Spring AOP) has the given annotation.
|
||||
|
||||
.Other pointcut types
|
||||
****
|
||||
The full AspectJ pointcut language supports additional pointcut designators that are not
|
||||
supported in Spring: `call`, `get`, `set`, `preinitialization`,
|
||||
`staticinitialization`, `initialization`, `handler`, `adviceexecution`, `withincode`, `cflow`,
|
||||
`cflowbelow`, `if`, `@this`, and `@withincode`. Use of these pointcut designators in pointcut
|
||||
expressions interpreted by Spring AOP results in an `IllegalArgumentException` being
|
||||
thrown.
|
||||
|
||||
The set of pointcut designators supported by Spring AOP may be extended in future
|
||||
releases to support more of the AspectJ pointcut designators.
|
||||
****
|
||||
|
||||
Because Spring AOP limits matching to only method execution join points, the preceding discussion
|
||||
of the pointcut designators gives a narrower definition than you can find in the
|
||||
AspectJ programming guide. In addition, AspectJ itself has type-based semantics and, at
|
||||
an execution join point, both `this` and `target` refer to the same object: the
|
||||
object executing the method. Spring AOP is a proxy-based system and differentiates
|
||||
between the proxy object itself (which is bound to `this`) and the target object behind the
|
||||
proxy (which is bound to `target`).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Due to the proxy-based nature of Spring's AOP framework, calls within the target object
|
||||
are, by definition, not intercepted. For JDK proxies, only public interface method
|
||||
calls on the proxy can be intercepted. With CGLIB, public and protected method calls on
|
||||
the proxy are intercepted (and even package-visible methods, if necessary). However,
|
||||
common interactions through proxies should always be designed through public signatures.
|
||||
|
||||
Note that pointcut definitions are generally matched against any intercepted method.
|
||||
If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with
|
||||
potential non-public interactions through proxies, it needs to be defined accordingly.
|
||||
|
||||
If your interception needs include method calls or even constructors within the target
|
||||
class, consider the use of Spring-driven xref:core/aop/using-aspectj.adoc#aop-aj-ltw[native AspectJ weaving] instead
|
||||
of Spring's proxy-based AOP framework. This constitutes a different mode of AOP usage
|
||||
with different characteristics, so be sure to make yourself familiar with weaving
|
||||
before making a decision.
|
||||
====
|
||||
|
||||
Spring AOP also supports an additional PCD named `bean`. This PCD lets you limit
|
||||
the matching of join points to a particular named Spring bean or to a set of named
|
||||
Spring beans (when using wildcards). The `bean` PCD has the following form:
|
||||
|
||||
[source,indent=0,subs="verbatim"]
|
||||
----
|
||||
bean(idOrNameOfBean)
|
||||
----
|
||||
|
||||
The `idOrNameOfBean` token can be the name of any Spring bean. Limited wildcard
|
||||
support that uses the `*` character is provided, so, if you establish some naming
|
||||
conventions for your Spring beans, you can write a `bean` PCD expression
|
||||
to select them. As is the case with other pointcut designators, the `bean` PCD can
|
||||
be used with the `&&` (and), `||` (or), and `!` (negation) operators, too.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `bean` PCD is supported only in Spring AOP and not in
|
||||
native AspectJ weaving. It is a Spring-specific extension to the standard PCDs that
|
||||
AspectJ defines and is, therefore, not available for aspects declared in the `@Aspect` model.
|
||||
|
||||
The `bean` PCD operates at the instance level (building on the Spring bean name
|
||||
concept) rather than at the type level only (to which weaving-based AOP is limited).
|
||||
Instance-based pointcut designators are a special capability of Spring's
|
||||
proxy-based AOP framework and its close integration with the Spring bean factory, where
|
||||
it is natural and straightforward to identify specific beans by name.
|
||||
====
|
||||
|
||||
|
||||
[[aop-pointcuts-combining]]
|
||||
== Combining Pointcut Expressions
|
||||
|
||||
You can combine pointcut expressions by using `&&,` `||` and `!`. You can also refer to
|
||||
pointcut expressions by name. The following example shows three pointcut expressions:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
public class Pointcuts {
|
||||
|
||||
@Pointcut("execution(public * *(..))")
|
||||
public void publicMethod() {} // <1>
|
||||
|
||||
@Pointcut("within(com.xyz.trading..*)")
|
||||
public void inTrading() {} // <2>
|
||||
|
||||
@Pointcut("publicMethod() && inTrading()")
|
||||
public void tradingOperation() {} // <3>
|
||||
}
|
||||
----
|
||||
<1> `publicMethod` matches if a method execution join point represents the execution
|
||||
of any public method.
|
||||
<2> `inTrading` matches if a method execution is in the trading module.
|
||||
<3> `tradingOperation` matches if a method execution represents any public method in the
|
||||
trading module.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
class Pointcuts {
|
||||
|
||||
@Pointcut("execution(public * *(..))")
|
||||
fun publicMethod() {} // <1>
|
||||
|
||||
@Pointcut("within(com.xyz.trading..*)")
|
||||
fun inTrading() {} // <2>
|
||||
|
||||
@Pointcut("publicMethod() && inTrading()")
|
||||
fun tradingOperation() {} // <3>
|
||||
}
|
||||
----
|
||||
<1> `publicMethod` matches if a method execution join point represents the execution
|
||||
of any public method.
|
||||
<2> `inTrading` matches if a method execution is in the trading module.
|
||||
<3> `tradingOperation` matches if a method execution represents any public method in the
|
||||
trading module.
|
||||
======
|
||||
|
||||
It is a best practice to build more complex pointcut expressions out of smaller _named
|
||||
pointcuts_, as shown above. When referring to pointcuts by name, normal Java visibility
|
||||
rules apply (you can see `private` pointcuts in the same type, `protected` pointcuts in
|
||||
the hierarchy, `public` pointcuts anywhere, and so on). Visibility does not affect
|
||||
pointcut matching.
|
||||
|
||||
|
||||
[[aop-common-pointcuts]]
|
||||
== Sharing Named Pointcut Definitions
|
||||
|
||||
When working with enterprise applications, developers often have the need to refer to
|
||||
modules of the application and particular sets of operations from within several aspects.
|
||||
We recommend defining a dedicated class that captures commonly used _named pointcut_
|
||||
expressions for this purpose. Such a class typically resembles the following
|
||||
`CommonPointcuts` example (though what you name the class is up to you):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
|
||||
public class CommonPointcuts {
|
||||
|
||||
/**
|
||||
* A join point is in the web layer if the method is defined
|
||||
* in a type in the com.xyz.web package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.web..*)")
|
||||
public void inWebLayer() {}
|
||||
|
||||
/**
|
||||
* A join point is in the service layer if the method is defined
|
||||
* in a type in the com.xyz.service package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.service..*)")
|
||||
public void inServiceLayer() {}
|
||||
|
||||
/**
|
||||
* A join point is in the data access layer if the method is defined
|
||||
* in a type in the com.xyz.dao package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.dao..*)")
|
||||
public void inDataAccessLayer() {}
|
||||
|
||||
/**
|
||||
* A business service is the execution of any method defined on a service
|
||||
* interface. This definition assumes that interfaces are placed in the
|
||||
* "service" package, and that implementation types are in sub-packages.
|
||||
*
|
||||
* If you group service interfaces by functional area (for example,
|
||||
* in packages com.xyz.abc.service and com.xyz.def.service) then
|
||||
* the pointcut expression "execution(* com.xyz..service.*.*(..))"
|
||||
* could be used instead.
|
||||
*
|
||||
* Alternatively, you can write the expression using the 'bean'
|
||||
* PCD, like so "bean(*Service)". (This assumes that you have
|
||||
* named your Spring service beans in a consistent fashion.)
|
||||
*/
|
||||
@Pointcut("execution(* com.xyz..service.*.*(..))")
|
||||
public void businessService() {}
|
||||
|
||||
/**
|
||||
* A data access operation is the execution of any method defined on a
|
||||
* DAO interface. This definition assumes that interfaces are placed in the
|
||||
* "dao" package, and that implementation types are in sub-packages.
|
||||
*/
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..))")
|
||||
public void dataAccessOperation() {}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
import org.aspectj.lang.annotation.Pointcut
|
||||
|
||||
class CommonPointcuts {
|
||||
|
||||
/**
|
||||
* A join point is in the web layer if the method is defined
|
||||
* in a type in the com.xyz.web package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.web..*)")
|
||||
fun inWebLayer() {}
|
||||
|
||||
/**
|
||||
* A join point is in the service layer if the method is defined
|
||||
* in a type in the com.xyz.service package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.service..*)")
|
||||
fun inServiceLayer() {}
|
||||
|
||||
/**
|
||||
* A join point is in the data access layer if the method is defined
|
||||
* in a type in the com.xyz.dao package or any sub-package
|
||||
* under that.
|
||||
*/
|
||||
@Pointcut("within(com.xyz.dao..*)")
|
||||
fun inDataAccessLayer() {}
|
||||
|
||||
/**
|
||||
* A business service is the execution of any method defined on a service
|
||||
* interface. This definition assumes that interfaces are placed in the
|
||||
* "service" package, and that implementation types are in sub-packages.
|
||||
*
|
||||
* If you group service interfaces by functional area (for example,
|
||||
* in packages com.xyz.abc.service and com.xyz.def.service) then
|
||||
* the pointcut expression "execution(* com.xyz..service.*.*(..))"
|
||||
* could be used instead.
|
||||
*
|
||||
* Alternatively, you can write the expression using the 'bean'
|
||||
* PCD, like so "bean(*Service)". (This assumes that you have
|
||||
* named your Spring service beans in a consistent fashion.)
|
||||
*/
|
||||
@Pointcut("execution(* com.xyz..service.*.*(..))")
|
||||
fun businessService() {}
|
||||
|
||||
/**
|
||||
* A data access operation is the execution of any method defined on a
|
||||
* DAO interface. This definition assumes that interfaces are placed in the
|
||||
* "dao" package, and that implementation types are in sub-packages.
|
||||
*/
|
||||
@Pointcut("execution(* com.xyz.dao.*.*(..))")
|
||||
fun dataAccessOperation() {}
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can refer to the pointcuts defined in such a class anywhere you need a pointcut
|
||||
expression by referencing the fully-qualified name of the class combined with the
|
||||
`@Pointcut` method's name. For example, to make the service layer transactional, you
|
||||
could write the following which references the
|
||||
`com.xyz.CommonPointcuts.businessService()` _named pointcut_:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config>
|
||||
<aop:advisor
|
||||
pointcut="com.xyz.CommonPointcuts.businessService()"
|
||||
advice-ref="tx-advice"/>
|
||||
</aop:config>
|
||||
|
||||
<tx:advice id="tx-advice">
|
||||
<tx:attributes>
|
||||
<tx:method name="*" propagation="REQUIRED"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
----
|
||||
|
||||
The `<aop:config>` and `<aop:advisor>` elements are discussed in xref:core/aop/schema.adoc[Schema-based AOP Support]. The
|
||||
transaction elements are discussed in xref:data-access/transaction.adoc[Transaction Management].
|
||||
|
||||
|
||||
[[aop-pointcuts-examples]]
|
||||
== Examples
|
||||
|
||||
Spring AOP users are likely to use the `execution` pointcut designator the most often.
|
||||
The format of an execution expression follows:
|
||||
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(modifiers-pattern?
|
||||
ret-type-pattern
|
||||
declaring-type-pattern?name-pattern(param-pattern)
|
||||
throws-pattern?)
|
||||
----
|
||||
|
||||
All parts except the returning type pattern (`ret-type-pattern` in the preceding snippet),
|
||||
the name pattern, and the parameters pattern are optional. The returning type pattern determines
|
||||
what the return type of the method must be in order for a join point to be matched.
|
||||
`{asterisk}` is most frequently used as the returning type pattern. It matches any return
|
||||
type. A fully-qualified type name matches only when the method returns the given
|
||||
type. The name pattern matches the method name. You can use the `{asterisk}` wildcard as all or
|
||||
part of a name pattern. If you specify a declaring type pattern,
|
||||
include a trailing `.` to join it to the name pattern component.
|
||||
The parameters pattern is slightly more complex: `()` matches a
|
||||
method that takes no parameters, whereas `(..)` matches any number (zero or more) of parameters.
|
||||
The `({asterisk})` pattern matches a method that takes one parameter of any type.
|
||||
`(*,String)` matches a method that takes two parameters. The first can be of any type, while the
|
||||
second must be a `String`. Consult the
|
||||
https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html[Language
|
||||
Semantics] section of the AspectJ Programming Guide for more information.
|
||||
|
||||
The following examples show some common pointcut expressions:
|
||||
|
||||
* The execution of any public method:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(public * *(..))
|
||||
----
|
||||
|
||||
* The execution of any method with a name that begins with `set`:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(* set*(..))
|
||||
----
|
||||
|
||||
* The execution of any method defined by the `AccountService` interface:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(* com.xyz.service.AccountService.*(..))
|
||||
----
|
||||
|
||||
* The execution of any method defined in the `service` package:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(* com.xyz.service.*.*(..))
|
||||
----
|
||||
|
||||
* The execution of any method defined in the service package or one of its sub-packages:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
execution(* com.xyz.service..*.*(..))
|
||||
----
|
||||
|
||||
* Any join point (method execution only in Spring AOP) within the service package:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
within(com.xyz.service.*)
|
||||
----
|
||||
|
||||
* Any join point (method execution only in Spring AOP) within the service package or one of its
|
||||
sub-packages:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
within(com.xyz.service..*)
|
||||
----
|
||||
|
||||
* Any join point (method execution only in Spring AOP) where the proxy implements the
|
||||
`AccountService` interface:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
this(com.xyz.service.AccountService)
|
||||
----
|
||||
+
|
||||
NOTE: `this` is more commonly used in a binding form. See the section on xref:core/aop/ataspectj/advice.adoc[Declaring Advice]
|
||||
for how to make the proxy object available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) where the target object
|
||||
implements the `AccountService` interface:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
target(com.xyz.service.AccountService)
|
||||
----
|
||||
+
|
||||
NOTE: `target` is more commonly used in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section
|
||||
for how to make the target object available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) that takes a single parameter
|
||||
and where the argument passed at runtime is `Serializable`:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
args(java.io.Serializable)
|
||||
----
|
||||
+
|
||||
NOTE: `args` is more commonly used in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section
|
||||
for how to make the method arguments available in the advice body.
|
||||
+
|
||||
Note that the pointcut given in this example is different from `execution(*
|
||||
*(java.io.Serializable))`. The args version matches if the argument passed at runtime is
|
||||
`Serializable`, and the execution version matches if the method signature declares a single
|
||||
parameter of type `Serializable`.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) where the target object has a
|
||||
`@Transactional` annotation:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
@target(org.springframework.transaction.annotation.Transactional)
|
||||
----
|
||||
+
|
||||
NOTE: You can also use `@target` in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section for
|
||||
how to make the annotation object available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) where the declared type of the
|
||||
target object has an `@Transactional` annotation:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
@within(org.springframework.transaction.annotation.Transactional)
|
||||
----
|
||||
+
|
||||
NOTE: You can also use `@within` in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section for
|
||||
how to make the annotation object available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) where the executing method has an
|
||||
`@Transactional` annotation:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
@annotation(org.springframework.transaction.annotation.Transactional)
|
||||
----
|
||||
+
|
||||
NOTE: You can also use `@annotation` in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section
|
||||
for how to make the annotation object available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) which takes a single parameter,
|
||||
and where the runtime type of the argument passed has the `@Classified` annotation:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
@args(com.xyz.security.Classified)
|
||||
----
|
||||
+
|
||||
NOTE: You can also use `@args` in a binding form. See the xref:core/aop/ataspectj/advice.adoc[Declaring Advice] section
|
||||
how to make the annotation object(s) available in the advice body.
|
||||
|
||||
* Any join point (method execution only in Spring AOP) on a Spring bean named
|
||||
`tradeService`:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
bean(tradeService)
|
||||
----
|
||||
|
||||
* Any join point (method execution only in Spring AOP) on Spring beans having names that
|
||||
match the wildcard expression `*Service`:
|
||||
+
|
||||
[literal,indent=0,subs="verbatim"]
|
||||
----
|
||||
bean(*Service)
|
||||
----
|
||||
|
||||
|
||||
[[writing-good-pointcuts]]
|
||||
== Writing Good Pointcuts
|
||||
|
||||
During compilation, AspectJ processes pointcuts in order to optimize matching
|
||||
performance. Examining code and determining if each join point matches (statically or
|
||||
dynamically) a given pointcut is a costly process. (A dynamic match means the match
|
||||
cannot be fully determined from static analysis and that a test is placed in the code to
|
||||
determine if there is an actual match when the code is running). On first encountering a
|
||||
pointcut declaration, AspectJ rewrites it into an optimal form for the matching
|
||||
process. What does this mean? Basically, pointcuts are rewritten in DNF (Disjunctive
|
||||
Normal Form) and the components of the pointcut are sorted such that those components
|
||||
that are cheaper to evaluate are checked first. This means you do not have to worry
|
||||
about understanding the performance of various pointcut designators and may supply them
|
||||
in any order in a pointcut declaration.
|
||||
|
||||
However, AspectJ can work only with what it is told. For optimal performance of
|
||||
matching, you should think about what you are trying to achieve and narrow the search
|
||||
space for matches as much as possible in the definition. The existing designators
|
||||
naturally fall into one of three groups: kinded, scoping, and contextual:
|
||||
|
||||
* Kinded designators select a particular kind of join point:
|
||||
`execution`, `get`, `set`, `call`, and `handler`.
|
||||
* Scoping designators select a group of join points of interest
|
||||
(probably of many kinds): `within` and `withincode`
|
||||
* Contextual designators match (and optionally bind) based on context:
|
||||
`this`, `target`, and `@annotation`
|
||||
|
||||
A well written pointcut should include at least the first two types (kinded and
|
||||
scoping). You can include the contextual designators to match based on
|
||||
join point context or bind that context for use in the advice. Supplying only a
|
||||
kinded designator or only a contextual designator works but could affect weaving
|
||||
performance (time and memory used), due to extra processing and analysis. Scoping
|
||||
designators are very fast to match, and using them means AspectJ can very quickly
|
||||
dismiss groups of join points that should not be further processed. A good
|
||||
pointcut should always include one if possible.
|
||||
|
||||
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
[[aop-choosing]]
|
||||
= Choosing which AOP Declaration Style to Use
|
||||
|
||||
Once you have decided that an aspect is the best approach for implementing a given
|
||||
requirement, how do you decide between using Spring AOP or AspectJ and between the
|
||||
Aspect language (code) style, the @AspectJ annotation style, or the Spring XML style? These
|
||||
decisions are influenced by a number of factors including application requirements,
|
||||
development tools, and team familiarity with AOP.
|
||||
|
||||
|
||||
|
||||
[[aop-spring-or-aspectj]]
|
||||
== Spring AOP or Full AspectJ?
|
||||
|
||||
Use the simplest thing that can work. Spring AOP is simpler than using full AspectJ, as
|
||||
there is no requirement to introduce the AspectJ compiler / weaver into your development
|
||||
and build processes. If you only need to advise the execution of operations on Spring
|
||||
beans, Spring AOP is the right choice. If you need to advise objects not managed by
|
||||
the Spring container (such as domain objects, typically), you need to use
|
||||
AspectJ. You also need to use AspectJ if you wish to advise join points other than
|
||||
simple method executions (for example, field get or set join points and so on).
|
||||
|
||||
When you use AspectJ, you have the choice of the AspectJ language syntax (also known as
|
||||
the "code style") or the @AspectJ annotation style. If aspects play a large
|
||||
role in your design, and you are able to use the https://www.eclipse.org/ajdt/[AspectJ
|
||||
Development Tools (AJDT)] plugin for Eclipse, the AspectJ language syntax is the
|
||||
preferred option. It is cleaner and simpler because the language was purposefully
|
||||
designed for writing aspects. If you do not use Eclipse or have only a few aspects
|
||||
that do not play a major role in your application, you may want to consider using
|
||||
the @AspectJ style, sticking with regular Java compilation in your IDE, and adding
|
||||
an aspect weaving phase to your build script.
|
||||
|
||||
|
||||
|
||||
[[aop-ataspectj-or-xml]]
|
||||
== @AspectJ or XML for Spring AOP?
|
||||
|
||||
If you have chosen to use Spring AOP, you have a choice of @AspectJ or XML style.
|
||||
There are various tradeoffs to consider.
|
||||
|
||||
The XML style may be most familiar to existing Spring users, and it is backed by genuine
|
||||
POJOs. When using AOP as a tool to configure enterprise services, XML can be a good
|
||||
choice (a good test is whether you consider the pointcut expression to be a part of your
|
||||
configuration that you might want to change independently). With the XML style, it is
|
||||
arguably clearer from your configuration which aspects are present in the system.
|
||||
|
||||
The XML style has two disadvantages. First, it does not fully encapsulate the
|
||||
implementation of the requirement it addresses in a single place. The DRY principle says
|
||||
that there should be a single, unambiguous, authoritative representation of any piece of
|
||||
knowledge within a system. When using the XML style, the knowledge of how a requirement
|
||||
is implemented is split across the declaration of the backing bean class and the XML in
|
||||
the configuration file. When you use the @AspectJ style, this information is encapsulated
|
||||
in a single module: the aspect. Secondly, the XML style is slightly more limited in what
|
||||
it can express than the @AspectJ style: Only the "singleton" aspect instantiation model
|
||||
is supported, and it is not possible to combine named pointcuts declared in XML.
|
||||
For example, in the @AspectJ style you can write something like the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Pointcut("execution(* get*())")
|
||||
public void propertyAccess() {}
|
||||
|
||||
@Pointcut("execution(com.xyz.Account+ *(..))")
|
||||
public void operationReturningAnAccount() {}
|
||||
|
||||
@Pointcut("propertyAccess() && operationReturningAnAccount()")
|
||||
public void accountPropertyAccess() {}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Pointcut("execution(* get*())")
|
||||
fun propertyAccess() {}
|
||||
|
||||
@Pointcut("execution(com.xyz.Account+ *(..))")
|
||||
fun operationReturningAnAccount() {}
|
||||
|
||||
@Pointcut("propertyAccess() && operationReturningAnAccount()")
|
||||
fun accountPropertyAccess() {}
|
||||
----
|
||||
======
|
||||
|
||||
In the XML style you can declare the first two pointcuts:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:pointcut id="propertyAccess"
|
||||
expression="execution(* get*())"/>
|
||||
|
||||
<aop:pointcut id="operationReturningAnAccount"
|
||||
expression="execution(com.xyz.Account+ *(..))"/>
|
||||
----
|
||||
|
||||
The downside of the XML approach is that you cannot define the
|
||||
`accountPropertyAccess` pointcut by combining these definitions.
|
||||
|
||||
The @AspectJ style supports additional instantiation models and richer pointcut
|
||||
composition. It has the advantage of keeping the aspect as a modular unit. It also has
|
||||
the advantage that the @AspectJ aspects can be understood (and thus consumed) both by
|
||||
Spring AOP and by AspectJ. So, if you later decide you need the capabilities of AspectJ
|
||||
to implement additional requirements, you can easily migrate to a classic AspectJ setup.
|
||||
In general, the Spring team prefers the @AspectJ style for custom aspects beyond simple
|
||||
configuration of enterprise services.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
[[aop-introduction-defn]]
|
||||
= AOP Concepts
|
||||
|
||||
Let us begin by defining some central AOP concepts and terminology. These terms are not
|
||||
Spring-specific. Unfortunately, AOP terminology is not particularly intuitive.
|
||||
However, it would be even more confusing if Spring used its own terminology.
|
||||
|
||||
* Aspect: A modularization of a concern that cuts across multiple classes.
|
||||
Transaction management is a good example of a crosscutting concern in enterprise Java
|
||||
applications. In Spring AOP, aspects are implemented by using regular classes
|
||||
(the xref:core/aop/schema.adoc[schema-based approach]) or regular classes annotated with the
|
||||
`@Aspect` annotation (the xref:core/aop/ataspectj.adoc[@AspectJ style]).
|
||||
* Join point: A point during the execution of a program, such as the execution of a
|
||||
method or the handling of an exception. In Spring AOP, a join point always
|
||||
represents a method execution.
|
||||
* Advice: Action taken by an aspect at a particular join point. Different types of
|
||||
advice include "around", "before", and "after" advice. (Advice types are discussed
|
||||
later.) Many AOP frameworks, including Spring, model an advice as an interceptor and
|
||||
maintain a chain of interceptors around the join point.
|
||||
* Pointcut: A predicate that matches join points. Advice is associated with a
|
||||
pointcut expression and runs at any join point matched by the pointcut (for example,
|
||||
the execution of a method with a certain name). The concept of join points as matched
|
||||
by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut
|
||||
expression language by default.
|
||||
* Introduction: Declaring additional methods or fields on behalf of a type. Spring
|
||||
AOP lets you introduce new interfaces (and a corresponding implementation) to any
|
||||
advised object. For example, you could use an introduction to make a bean implement an
|
||||
`IsModified` interface, to simplify caching. (An introduction is known as an
|
||||
inter-type declaration in the AspectJ community.)
|
||||
* Target object: An object being advised by one or more aspects. Also referred to as
|
||||
the "advised object". Since Spring AOP is implemented by using runtime proxies, this
|
||||
object is always a proxied object.
|
||||
* AOP proxy: An object created by the AOP framework in order to implement the aspect
|
||||
contracts (advise method executions and so on). In the Spring Framework, an AOP proxy
|
||||
is a JDK dynamic proxy or a CGLIB proxy.
|
||||
* Weaving: linking aspects with other application types or objects to create an
|
||||
advised object. This can be done at compile time (using the AspectJ compiler, for
|
||||
example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks,
|
||||
performs weaving at runtime.
|
||||
|
||||
Spring AOP includes the following types of advice:
|
||||
|
||||
* Before advice: Advice that runs before a join point but that does not have
|
||||
the ability to prevent execution flow proceeding to the join point (unless it throws
|
||||
an exception).
|
||||
* After returning advice: Advice to be run after a join point completes
|
||||
normally (for example, if a method returns without throwing an exception).
|
||||
* After throwing advice: Advice to be run if a method exits by throwing an
|
||||
exception.
|
||||
* After (finally) advice: Advice to be run regardless of the means by which a
|
||||
join point exits (normal or exceptional return).
|
||||
* Around advice: Advice that surrounds a join point such as a method invocation.
|
||||
This is the most powerful kind of advice. Around advice can perform custom behavior
|
||||
before and after the method invocation. It is also responsible for choosing whether to
|
||||
proceed to the join point or to shortcut the advised method execution by returning its
|
||||
own return value or throwing an exception.
|
||||
|
||||
Around advice is the most general kind of advice. Since Spring AOP, like AspectJ,
|
||||
provides a full range of advice types, we recommend that you use the least powerful
|
||||
advice type that can implement the required behavior. For example, if you need only to
|
||||
update a cache with the return value of a method, you are better off implementing an
|
||||
after returning advice than an around advice, although an around advice can accomplish
|
||||
the same thing. Using the most specific advice type provides a simpler programming model
|
||||
with less potential for errors. For example, you do not need to invoke the `proceed()`
|
||||
method on the `JoinPoint` used for around advice, and, hence, you cannot fail to invoke it.
|
||||
|
||||
All advice parameters are statically typed so that you work with advice parameters of
|
||||
the appropriate type (e.g. the type of the return value from a method execution) rather
|
||||
than `Object` arrays.
|
||||
|
||||
The concept of join points matched by pointcuts is the key to AOP, which distinguishes
|
||||
it from older technologies offering only interception. Pointcuts enable advice to be
|
||||
targeted independently of the object-oriented hierarchy. For example, you can apply an
|
||||
around advice providing declarative transaction management to a set of methods that span
|
||||
multiple objects (such as all business operations in the service layer).
|
||||
|
||||
|
||||
|
||||
|
||||