Compare commits

...

131 Commits

Author SHA1 Message Date
Spring Buildmaster 5d46ae91f0 Release v5.2.15.RELEASE 2021-05-12 06:05:33 +00:00
Arjen Poutsma 85ad63bd35 Ensure Synchronoss temp directories do not collide
This commit makes sure that Synchronoss uses a random temporary
directory to store uploaded files, so that two instances do not collide.

Closes gh-26931
2021-05-11 21:19:27 +02:00
Juergen Hoeller a2ff769bc1 Polishing (backported from master) 2021-05-11 17:32:23 +02:00
Juergen Hoeller 9523f1ffd6 Correct clientInboundChannel assertion (includes constructor javadoc)
Closes gh-26896
2021-05-11 17:31:04 +02:00
Juergen Hoeller cd0570ee39 Log resource path resolution failure at debug level (instead of warn)
Closes gh-26828
2021-05-11 17:30:57 +02:00
Juergen Hoeller 05d767b1cf Always propagate checked exceptions from Kotlin code behind CGLIB proxies
Closes gh-23844
2021-05-11 17:29:40 +02:00
Stephane Nicoll 30655555bd Remove .RELEASE suffix before querying GitHub
This commit removes the `.RELEASE` suffix of the released version, if
necessary. Such commit is not present on GitHub and the changelog
generation process fails to find the milestone to use to generate the
changelog.

Closes gh-26796
2021-05-11 16:20:28 +02:00
Stephane Nicoll 0ee8790a9a Upgrade to Reactor Dysprosium-SR20
Closes gh-26891
2021-05-11 15:35:50 +02:00
Sam Brannen bcd8896689 Polish contribution
See gh-25200
2021-05-10 12:50:13 +02:00
Vlad Kisel 53b8d73078 Support single-value request param resolution for data class constructors
Prior to this commit, when Web MVC attempted to resolve a constructor
argument for a data class constructor with @ModelAttribute binding,
ModelAttributeMethodProcessor failed to unwrap the array returned by
WebRequest.getParameter(String).

According to the Javadoc for WebRequest.getParameter(String), "a
single-value parameter will be exposed as an array with a single
element."

This commit fixes this issue by extracting the single value from such
an array and using that as the constructor argument (potentially
converted by the WebDataBinder).

Closes gh-25200
2021-05-10 12:47:47 +02:00
ShindongLee 3ab270e0f1 Fix parameter bug of handler inside the filterFunction DSL
Co-authored-by: hojongs <hojong.jjh@gmail.com>
Co-authored-by: bjh970913 <bjh970913@gmail.com>

Closes gh-26921
2021-05-10 10:22:13 +02:00
Rossen Stoyanchev ddb727bee8 Switch to Reactor Dysprosium snapshots
See gh-26891
2021-05-04 17:12:19 +01:00
Sam Brannen a33adac6c8 Support @ModelAttribute(binding=false) with WebFlux
Prior to this commit, @ModelAttribute(binding=false) was honored with
Spring Web MVC but not with WebFlux.

This commit adds support for disabling binding via @ModelAttribute with
WebFlux by adding a check to resolveArgument(...) in
ModelAttributeMethodArgumentResolver.

Closes gh-26856
2021-05-03 19:13:16 +02:00
Sam Brannen 08663d4792 Polish Javadoc 2021-05-03 18:58:12 +02:00
Stephane Nicoll 30b5a7b5c3 Backport "Remove leftover Javadoc from WebClient"
Closes gh-26809
2021-04-15 09:57:21 +02:00
Spring Buildmaster 620023cbc3 Next development version (v5.2.15.BUILD-SNAPSHOT) 2021-04-13 11:46:36 +00:00
Juergen Hoeller 19474e2d03 Nullability refinements
(cherry picked from commit f31933e67e)
2021-04-13 12:32:16 +02:00
Juergen Hoeller 43071109f7 Polishing (backported from master) 2021-04-13 11:48:55 +02:00
Juergen Hoeller bb530dca5b Polishing (backported from master) 2021-04-12 23:39:24 +02:00
Juergen Hoeller e4e2212817 Bypass root path resolution for "file:" prefix only
See gh-26702
2021-04-12 23:39:18 +02:00
Stephane Nicoll 34fcbfbcc5 Upgrade to Reactor Dysprosium-SR19
Closes gh-26768
2021-04-12 18:09:16 +02:00
Stephane Nicoll 20cbd684d4 Start building against Reactor Dysprosium SR19 snapshots
See gh-26768
2021-04-07 09:52:32 +02:00
Brian Clozel 5f9a6ce98e Upgrade to Concourse Release Scripts 0.3.2 2021-03-23 13:58:04 +01:00
Juergen Hoeller 07e18df0b9 Polishing 2021-03-22 23:14:20 +01:00
Sam Brannen 4a6fea353f Honor class-level @DirtiesContext if test class is disabled via SpEL
Prior to this commit, if a test class annotated with @DirtiesContext
and @EnabledIf/@DisabledIf with `loadContext = true` was disabled due
to the evaluated SpEL expression, the ApplicationContext would not be
marked as dirty and closed.

The reason is that @EnabledIf/@DisabledIf are implemented via JUnit
Jupiter's ExecutionCondition extension API which results in the entire
test class (as well as any associated extension callbacks) being
skipped if the condition evaluates to `disabled`. This effectively
prevents any of Spring's TestExecutionListener APIs from being invoked.
Consequently, the DirtiesContextTestExecutionListener does not get a
chance to honor the class-level @DirtiesContext declaration.

This commit fixes this by implementing part of the logic of
DirtiesContextTestExecutionListener in
AbstractExpressionEvaluatingCondition (i.e., the base class for
@EnabledIf/@DisabledIf support). Specifically, if the test class for an
eagerly loaded ApplicationContext is disabled,
AbstractExpressionEvaluatingCondition will now mark the test
ApplicationContext as dirty if the test class is annotated with
@DirtiesContext.

Closes gh-26694
2021-03-18 14:21:05 +01:00
Brian Clozel f76504512e Fix release pipeline for Maven Central publication
This commit fixes the missing pieces in our Maven Central publication
pipeline. Our first attempt at releasing with it showed a few problems:

* the promote task did not have the artifacts downladed with the
  artifactory repository
* we applied the wrong Sonatype credentials to the task
* the github changelog task would fail because of docker rate limiting
  since we were not using the right type of resource, which is
  configured with the proper caching mechanism

See gh-26654
2021-03-16 14:34:10 +01:00
Juergen Hoeller 215514f27b Polishing (backported from master) 2021-03-12 15:29:29 +01:00
Juergen Hoeller ae2711866a Polishing 2021-03-12 11:07:19 +01:00
Juergen Hoeller a6ab7164a3 Use getRawStatusCode() for custom status code support in value methods
Closes gh-26658
2021-03-12 10:50:55 +01:00
Brian Clozel a7db92b44e Use ".RELEASE" suffix for releases on 5.2.x branch
This commit ensures that the release scripts generate a ".RELEASE"
suffixed release version, since the 5.2.x branch is still using this
naming scheme.

See https://github.com/spring-io/concourse-java-scripts#get_next_release
See gh-26659
2021-03-11 10:04:22 +01:00
Stephane Nicoll 63912f802a Merge branch 'ci' into 5.2.x
Closes gh-26659
2021-03-10 15:10:49 +01:00
Stephane Nicoll 3fbf3a597a Backport CI build
See gh-26659
2021-03-10 14:57:06 +01:00
Stephane Nicoll 5291c5b233 Remove artifactory plugin from build
Backport of gh-22490

See gh-26659
2021-03-10 14:57:06 +01:00
Juergen Hoeller c978fb4466 Documentation fixes etc (backported from master) 2021-03-09 16:57:04 +01:00
Juergen Hoeller 0fc831e8bd Polishing (backported from master) 2021-03-09 00:05:55 +01:00
Rossen Stoyanchev dd967215fe Upgrade to RSocket 1.0.4 2021-03-04 22:18:11 +00:00
Sam Brannen 31c5fc6417 Polish contribution
See gh-26619
2021-03-02 14:28:01 +01:00
GungnirLaevatain e8f685ecc8 Ensure local @CrossOrigin maxAge overrides global value
Prior to this commit, a method-level @CrossOrigin maxAge value did not
override a class-level @CrossOrigin maxAge value. This contradicts the
Javadoc for @CrossOrgin which states the following.

    For those attributes where only a single value can be accepted such
    as allowCredentials and maxAge, the local overrides the global
    value.

This commit ensures that a method-level @CrossOrigin maxAge value
overrides a class-level @CrossOrigin maxAge value.

Closes gh-26619
2021-03-02 14:27:49 +01:00
Juergen Hoeller 428dbc43da Add missing nullable annotation to ResponseEntity ok convenience method
Closes gh-26613
2021-02-26 17:42:05 +01:00
Juergen Hoeller dd3262bfe3 Polishing (backported from master) 2021-02-25 19:08:53 +01:00
Juergen Hoeller fdafd38d2f Fix handling of file: paths to non-existent files
For setAsText, if the text argument is a file: URL for a path that does not exist, Paths.get(text) is called where text is a file: URL, which doesn't work - the result is an InvalidPathException.

To fix this issue, also check that the resource isn't a file before calling Paths.get(). That way, resources that are files skip to the other branch.

Closes gh-26575
2021-02-25 19:08:18 +01:00
Juergen Hoeller 74b248a6b3 getResource can throw IllegalArgumentException
Class.getResource, ClassLoader.getResource, and ClassLoader.getSystemResource will throw IllegalArgumentException if a malformed URL is provided to them.

According to its javadoc, resolveURL should return null if not resolvable, so catch the IllegalArgumentException and return null.

Closes gh-26574
2021-02-25 19:07:58 +01:00
Sam Brannen 60e418959a Polish contribution
See gh-26600
2021-02-25 11:43:10 +01:00
nullzl 4530e36867 Correctly set auto-growing array's element
Prior to this commit, the implementation of processKeyedProperty() in
AbstractNestablePropertyAccessor resulted in a
`java.lang.IllegalArgumentException: array element type mismatch` when
the property expression had more than one property key and the last key
should cause the array to grow automatically.

For example, given a property `int[][] multiArray` and property
expression `multiArray[1][3]`, the `processKeyedProperty()` method
created a new array object and assigned it to `multiArray`; whereas,
the new array object should have be assigned to `multiArray[1]`.

This commit fixes this issue.

Closes gh-26600
2021-02-25 11:42:56 +01:00
Sam Brannen 2ec7d5c785 Support load-time weaving for @Component classes again
Since Spring Framework 5.2, the LoadTimeWeaver no longer weaves bean
classes annotated with @Component. This is a regression caused by the
changes in 40c62139ae, stemming from the fact that any class annotated
or meta-annotated with @Component is considered to be a candidate
configuration class in 'configuration lite' mode (i.e., a class without
the @Configuration annotation and without any @Bean methods) and
therefore now has its class eagerly loaded. This results in the class
being loaded before the LoadTimeWeaver has a chance to weave it.

This commit fixes this regression by explicitly avoiding eager class
loading for any 'lite' @Configuration or @Component class without @Bean
methods.

Closes gh-26199
2021-02-22 10:28:58 +01:00
Brian Clozel 9f7c8aea94 Fix ResourceUrlProvider handler auto-detection
Prior to this commit, `ResourceUrlProvider` would listen and consider
all `ContextRefreshedEvent` and use the given context to detect
`SimpleUrlHandlerMapping`.

This could lead to situations where a `ResourceUrlProvider` uses another
application context than its own (in a parent/child context setup) and
detect the wrong set of handlers.

Because `ResourceUrlProvider` locks itself once the auto-detection is
done, we need to ensure that it considers only events sent by its
application context.

Fixes gh-26562
2021-02-17 22:10:29 +01:00
Sam Brannen 792656097a Polish MockHttpServletResponseTests
See gh-26558
2021-02-17 14:51:19 +01:00
Sam Brannen 33add3350b Sync changes in MockHttpServletResponse test fixture
See gh-26558
2021-02-17 14:51:09 +01:00
Koos Gadellaa 8f2010d669 Support cookie w/ only Expires attribute in MockHttpServletResponse
Prior to this commit, MockHttpServletResponse only included the Expires
attribute in the generated Cookie header if the Max-Age attribute had
also been set.

This commit supports including the Expires attribute in the generated
Cookie Header even when the Max-Age attribute has not been set.

Closes gh-26558
2021-02-17 14:50:59 +01:00
Sam Brannen 9d4bfb1769 Implement toString() in MockCookie 2021-02-17 14:50:39 +01:00
Spring Buildmaster da5f410af4 Next Development Version 2021-02-16 11:39:43 +00:00
Juergen Hoeller 940f57d023 Document ASM version as 7.x (effectively 7.3 at present) 2021-02-16 00:15:50 +01:00
Rossen Stoyanchev 29799be474 Upgrade to Dysprosium-SR17
Closes gh-26549
2021-02-15 17:33:30 +00:00
Juergen Hoeller 67979c93c3 Upgrade to Hibernate ORM 5.4.28 and OpenPDF 1.3.25 2021-02-15 17:34:29 +01:00
Juergen Hoeller 4ae3ab14ec Avoid unnecessary wrapping for SqlParameterValue
Closes gh-26471
2021-02-15 11:49:59 +01:00
Juergen Hoeller 81f0d76f3d Upgrade to Apache Johnzon 1.2.10, Joda-Time 2.10.10, RxJava 2.2.21 2021-02-14 18:49:33 +01:00
Juergen Hoeller 9f1e822f3e Polishing 2021-02-14 18:48:49 +01:00
Juergen Hoeller b4baa86bfa Close mapping streams after the ValidatorFactory has been built
Closes gh-26418
2021-02-14 18:48:31 +01:00
Juergen Hoeller 81be4c22d0 Re-resolve cached arguments in case of NoSuchBeanDefinitionException
Closes gh-26517

(cherry picked from commit 99a1388bbd)
2021-02-14 18:17:06 +01:00
Juergen Hoeller a17c2ccb4e Preserve resolved destroy method name in RootBeanDefinition
Closes gh-26498

(cherry picked from commit 809813dd52)
2021-02-14 18:16:11 +01:00
Juergen Hoeller 64df93168d Consider non-initialized holders as equal to empty holders
Closes gh-26433

(cherry picked from commit d5e5dcb7e1)
2021-02-14 18:08:20 +01:00
Juergen Hoeller 27c5480c82 Fail early FactoryBean instantiation for LinkageError
Closes gh-26425

(cherry picked from commit defc2466b0)
2021-02-14 18:08:15 +01:00
Sam Brannen 594ec8f8a9 Update copyright date in reference manual 2021-02-10 17:57:23 +01:00
Arjen Poutsma c09b2513e0 Ensure StringDecoder supports multiline delimiters
This commit makes sure the StringDecoder supports stripping off
multi-line delimiters, such as \r\n. Specifically, we ensure that the
delimiter is stripped from the joined buffer.

Closes gh-26511
2021-02-08 15:59:02 +01:00
Sam Brannen 6c22f7ef5e Fix StoredProcedure documentation in reference manual
This commit aligns the documentation in the reference manual with the
actual source code for StoredProcedure with regard to public execute()
methods.

Closes gh-26505
2021-02-04 11:12:31 +01:00
Sam Brannen 236623f630 Do not retain partial column metadata in SimpleJdbcInsert
Prior to this commit, if an SQLException was thrown while retrieving
column metadata from the database, SimpleJdbcInsert would generate an
INSERT statement that was syntactically valid but missing columns,
which could lead to data silently missing in the database (for nullable
columns).

This commit fixes this by clearing all collected column metadata if an
SQLException is thrown while processing the metadata. The result is
that an InvalidDataAccessApiUsageException will be thrown later while
generating the INSERT statement. The exception message now contains an
additional hint to make use of SimpleJdbcInsert#usingColumns() in order
to ensure that all required columns are included in the generated
INSERT statement.

SimpleJdbcCall can also encounter an SQLException while retrieving
column metadata for a stored procedure/function, but an exception is
not thrown since a later invocation of the stored procedure/function
will likely fail anyway due to missing arguments. Consequently, this
commit only improves the warning level log message by including a hint
to make use of SimpleJdbcCall#addDeclaredParameter().

Closes gh-26486
2021-02-03 18:53:32 +01:00
Sam Brannen e18fba1626 Polishing 2021-02-03 18:52:15 +01:00
Sam Brannen 070c596a66 Use api-spring-framework attribute for links to SpringProperties
See gh-26492
2021-02-02 14:04:20 +01:00
Sam Brannen 7a9bf1578e Link to SpringProperties Javadoc from the Testing chapter
Closes gh-26492
2021-02-02 13:52:08 +01:00
Rossen Stoyanchev 8a150ee3a4 Fix order of headers in DefaultHandlerExceptionResolver
Closes gh-26470
2021-01-29 20:49:19 +00:00
Rossen Stoyanchev 6051f4ecbd Handle forwarded header parse issues
Closes gh-26459
2021-01-29 20:48:51 +00:00
Rossen Stoyanchev 698e74f7cd WebSocketExtension#equals matches sub-classes too
Closes gh-26449
2021-01-29 20:48:51 +00:00
Sam Brannen 51079a40ae Upgrade io.spring.ge.conventions plugin to version 0.0.7
This will hopefully allow the build to pass again on the CI server.
2021-01-27 18:09:13 +01:00
Sam Brannen 94ac2e4de7 Fix UriComponentsBuilder examples in ref docs
Closes gh-26453
2021-01-27 15:17:32 +01:00
Stephane Nicoll 271a9097ac Clarify behaviour of condition attribute of CachePut
Closes gh-26404
2021-01-19 15:15:38 +01:00
Juergen Hoeller 3c030edbf2 Upgrade to RxJava 2.2.20, OpenPDF 1.3.24, Hibernate ORM 5.4.27, Joda-Time 2.10.9, Apache Johnzon 1.2.9 2021-01-14 18:30:52 +01:00
Sam Brannen 059cff52b7 Remove obsolete commandName attribute in spring-form.tld
Since support for the commandName attribute was removed from the
implementation of FormTag in Spring Framework 5.0, the presence of the
commandName attribute in the spring-form.tld file is no longer valid
and can lead one to assume that the commandName attribute is still
supported -- for example when using code completion in a JSP editor.

This commit therefore removes the obsolete commandName attribute in
spring-form.tld.

Closes gh-26337
2021-01-03 17:50:15 +01:00
Sam Brannen 619a3edae5 Fix syntax in Kotlin example 2020-12-19 15:30:42 +01:00
Juergen Hoeller eb3811e590 Upgrade to Hibernate Validator 6.1.7, Caffeine 2.8.8, Joda-Time 2.10.8 2020-12-17 11:06:16 +01:00
Juergen Hoeller f011e58f90 Polishing 2020-12-17 11:05:58 +01:00
Juergen Hoeller 7325a863bb Consistent declarations and assertions in MockMultipartFile
See gh-26261

(cherry picked from commit fbd2ffdd23)
2020-12-17 10:50:47 +01:00
Brian Clozel fe26b7d3fd Fix NPE when calling NettyHeadersAdapter.add()
Prior to this commit, the `NettyHeadersAdapter` would directly delegate
the `add()` and `set()` calls to the adapted
`io.netty.handler.codec.http.HttpHeaders`. This implementation rejects
`null` values with exceptions.

This commit aligns the behavior here with other implementations, by not
rejecting null values but simply ignoring them.

Fixes gh-26277
(cherry-picked from commit 83c19cd60e)
2020-12-15 13:36:03 +01:00
Juergen Hoeller cfdceae70f Defensively handle loadClass null result in BeanUtils.findEditorByConvention
Closes gh-26252

(cherry picked from commit 2a47751fcd)
2020-12-10 16:26:47 +01:00
Spring Buildmaster 17647801aa Next Development Version 2020-12-09 06:34:43 +00:00
Rossen Stoyanchev 33476a2eae Backport fixes for discarding data buffers
Closes gh-26232
2020-12-08 22:00:28 +00:00
Brian Clozel a8091b916b Avoid closing Jackson JsonGenerator for error cases
Prior to this commit, a change introduced in gh-25910 would close the
`JsonGenerator` after it's been used for JSON serialization. This would
not only close it and recycle resources, but also flush the underlyning
buffer to the output.
In a case where the JSON serialization process would throw an exception,
the buffer would be still flushed to the response output. Before the
change introduced in gh-25910, the response body could be still empty at
that point and error handling could write an error body instead.

This commits only closes the `JsonGenerator` when serialization has been
successful.

Note that we're changing this in the spirit of backwards compatibility
in the 5.2.x line, but change this won't be merged forward on the 5.3.x
line, for several reasons:

* this behavior is not consistent. If the JSON output exceeds a
certain size, or if Jackson has been configured to flush after each
write, the response output might still contain an incomplete JSON
payload (just like before this change)

* this behavior is not consistent with the WebFlux and Messaging codecs,
which are flushing or closing the generator

* not closing the generator for error cases prevents resources from
being recycled as expected by Jackson

Fixes gh-26246
2020-12-08 20:50:26 +01:00
Juergen Hoeller 396fdf125f Remove duplicate "property" in PropertyCacheKey.toString()
Closes gh-26237
2020-12-08 15:03:33 +01:00
Stephane Nicoll 085825fbc0 Upgrade to Reactor Dysprosium-SR15
Closes gh-26175
2020-12-08 05:50:48 +01:00
Juergen Hoeller b0c6e5e322 Polishing
(cherry picked from commit c970c318f4)
2020-12-07 22:09:43 +01:00
Juergen Hoeller a7efa9659a Clarify intended advice execution behavior (includes related polishing)
Closes gh-26202

(cherry picked from commit 834032df1f)
2020-12-07 22:09:30 +01:00
Juergen Hoeller f7605eaf58 Upgrade to Hibernate ORM 5.4.25 and Checkstyle 8.38 2020-12-02 17:37:59 +01:00
Juergen Hoeller ce67b89861 Support for multi-threaded addConverter calls
Closes gh-26183

(cherry picked from commit 396fb0cd51)
2020-12-02 12:35:21 +01:00
Rossen Stoyanchev 736af46fc0 ContentCachingResponseWrapper skips contentLength for chunked responses
Closes gh-26182
2020-12-01 17:50:07 +00:00
Stephane Nicoll a495bd6679 Start building against Reactor Dysprosium-SR15 snapshots
See gh-26175
2020-11-30 07:23:19 +01:00
Juergen Hoeller 7c33c70742 Polishing 2020-11-27 21:58:55 +01:00
Juergen Hoeller 81c1b60f19 Register @Bean definitions as dependent on containing configuration class
Closes gh-26167
2020-11-27 21:57:55 +01:00
Rossen Stoyanchev 3703be5aaf MessageHeaderAccessor handle self-copy correctly
1. Revert changes in setHeader from 5.2.9 that caused regression on self-copy.
2. Update copyHeaders/IfAbsent to ensure a copy of native headers.
3. Exit if source and target are the same instance, as an optimization.

Closes gh-26155
2020-11-26 21:56:17 +00:00
Сергей Цыпанов b929edb221 Remove unused package-private class o.s.w.u.p.SubSequence
(cherry picked from commit 42216b77df)
2020-11-26 15:38:14 +01:00
Juergen Hoeller 50803ce142 Remove misleading default note on ISO.DATE_TIME
Closes gh-26134

(cherry picked from commit 86f9716fef)
2020-11-26 15:38:05 +01:00
Sam Brannen 346445ee7e Fix broken links to XSD schemas in ref docs
Closes gh-26129
2020-11-23 14:52:47 +01:00
Juergen Hoeller e238c8a87c Declare resolvedCharset as transient (restoring serializability)
Closes gh-26127
2020-11-20 18:57:30 +01:00
Juergen Hoeller 9e99fd5df2 Upgrade to Hibernate ORM 5.4.24
(cherry picked from commit 135682e073)
2020-11-19 16:03:03 +01:00
Juergen Hoeller 990c74b1e2 Upgrade to Apache HttpClient 4.5.13 (and consistent Caffeine declarations) 2020-11-19 15:19:29 +01:00
Rossen Stoyanchev 37bda566eb Allow "*" for Access-Control-Expose-Headers
Closes gh-26113
2020-11-19 09:48:41 +00:00
Juergen Hoeller 322babc04a Document that @Transactional does not propagate to new threads
Closes gh-25439
2020-11-17 16:16:24 +01:00
Juergen Hoeller 4e720e8104 Encode hash symbol in jar file path (for compatibility with JDK 11+)
Closes gh-26104

(cherry picked from commit b29723623b)
2020-11-17 16:11:07 +01:00
Juergen Hoeller fdab75a1d8 Upgrade to Reactor Dysprosium-SR14, OpenPDF 1.3.23, AssertJ 3.18.1, MockK 1.10.2, Checkstyle 8.37 2020-11-16 21:08:24 +01:00
Juergen Hoeller 240cfb1224 Polishing 2020-11-16 17:50:15 +01:00
Rossen Stoyanchev db2786264b UrlPathHelper.removeJsessionid correctly appends remainder
Closes gh-26079
2020-11-16 11:18:20 +00:00
Juergen Hoeller 763fa98bdf Early log entry for async EntityManagerFactory initialization failure
Closes gh-26093
2020-11-13 17:59:14 +01:00
Sam Brannen 25cbc263f0 Assert same instance returned for cached merged BeanDefinition 2020-11-12 14:58:58 +01:00
Juergen Hoeller 9949a91048 Fix javadoc and assertion glitches 2020-11-12 14:19:12 +01:00
Juergen Hoeller d8b7a593c5 Individually apply the SQL type from each SqlParameterSource argument
Closes gh-26071
2020-11-12 14:18:23 +01:00
Rossen Stoyanchev d3d8f1a487 LimitedDataBufferList adds or raises error
Closes gh-26060
2020-11-10 20:12:38 +00:00
Spring Buildmaster a637f6a27c Next Development Version 2020-11-10 07:09:28 +00:00
Juergen Hoeller e9416b369e Polishing 2020-11-09 18:23:45 +01:00
Juergen Hoeller cde95e1446 Polishing 2020-11-09 13:52:34 +01:00
Sam Brannen da4e37dc1d Do not create intermediate list in MergedAnnotationCollectors.toAnnotationSet()
Prior to this commit, MergedAnnotationCollectors.toAnnotationSet()
created an intermediate ArrayList for storing the results prior to
creating a LinkedHashSet in the finishing step.

Since the creation of the intermediate list is unnecessary, this commit
simplifies the implementation of toAnnotationSet() by using the
Collector.of() factory method that does not accept a `finisher` argument.
The resulting Collector internally uses a `castingIdentity()` function
as the `finisher`.

Closes gh-26031
2020-11-08 15:20:18 +01:00
Rossen Stoyanchev 010d0947c7 Refine logging in StompErrorHandler
Avoid a full stacktrace at ERROR level for a client message that could
not be sent to a MessageChannel.

See gh-26026
2020-11-05 21:51:16 +00:00
Juergen Hoeller 5b06c23a1b Consistent javadoc within SpelCompiler 2020-11-05 18:33:12 +01:00
Juergen Hoeller 7881329cf7 Polishing 2020-11-05 18:18:38 +01:00
Juergen Hoeller 58aa0659cc Suppress NotWritablePropertyException in case of ignoreUnknown=true
Closes gh-25986
2020-11-05 18:15:59 +01:00
Juergen Hoeller dbbedc6c86 Add FullyQualifiedAnnotationBeanNameGenerator.INSTANCE
Closes gh-26025
2020-11-05 18:15:29 +01:00
Juergen Hoeller 09c1e986b9 Upgrade to Hibernate ORM 5.4.23
(cherry picked from commit b815accca9)
2020-11-05 17:54:18 +01:00
Juergen Hoeller 10bff054a9 Reliably refresh metadata for dynamically changing prototype bean class
Closes gh-26019

(cherry picked from commit 412aa06d86)
2020-11-05 17:50:24 +01:00
Sam Brannen e713e0d6d5 Preserve registration order in @ActiveProfiles
With this commit, bean definition profiles declared via @ActiveProfiles
are once again stored in registration order, in order to support use
cases in Spring Boot and other frameworks that depend on the
registration order.

This effectively reverts the changes made in conjunction with gh-25973.

Closes gh-26004
2020-11-02 22:40:59 +01:00
Rossen Stoyanchev b4f8fc8177 Use static accessors in DefaultSimpUserRegistry
Closes gh-26010
2020-11-02 17:32:12 +00:00
Rossen Stoyanchev 8c3cdc6118 Remove unused import 2020-10-30 18:43:19 +00:00
Rossen Stoyanchev 01827fd8d2 Ensure response not closed by MappingJackson2HttpMessageConverter
Closes gh-25987
2020-10-30 18:42:11 +00:00
Stephane Nicoll af1d721aa3 Fix a broken Asciidoctor syntax in core-resources.adoc
Closes gh-26000
2020-10-30 09:33:36 +01:00
Arjen Poutsma 0acb1e5513 Copy default headers, cookies in WebClient builder
This commit makes copies of the default headers and cookies when a
WebClient is built, so that subsequent changes to these do not affect
previously built clients.

Closes: gh-25992
2020-10-29 13:36:26 +01:00
234 changed files with 4188 additions and 1726 deletions
+15 -16
View File
@@ -1,6 +1,6 @@
plugins {
id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
id 'io.spring.gradle-enterprise-conventions' version '0.0.2'
id 'io.spring.ge.conventions' version '0.0.7'
id 'io.spring.nohttp' version '0.0.5.RELEASE'
id 'org.jetbrains.kotlin.jvm' version '1.3.72' apply false
id 'org.jetbrains.dokka' version '0.10.1' apply false
@@ -8,9 +8,8 @@ plugins {
id 'org.asciidoctor.jvm.pdf' version '2.4.0'
id 'de.undercouch.download' version '4.1.1'
id "io.freefair.aspectj" version '4.1.6' apply false
id "com.github.ben-manes.versions" version '0.28.0'
id 'com.gradle.build-scan' version '3.2'
id "com.jfrog.artifactory" version '4.12.0' apply false
id "com.github.ben-manes.versions" version '0.24.0'
}
apply from: "$rootDir/gradle/build-scan-user-data.gradle"
@@ -29,8 +28,8 @@ configure(allprojects) { project ->
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.10.5"
mavenBom "io.netty:netty-bom:4.1.51.Final"
mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR13"
mavenBom "io.rsocket:rsocket-bom:1.0.3"
mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR20"
mavenBom "io.rsocket:rsocket-bom:1.0.4"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.31.v20200723"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.3.72"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.3.5"
@@ -61,7 +60,7 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.19"
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE"
dependency "com.caucho:hessian:4.0.63"
@@ -76,7 +75,7 @@ configure(allprojects) { project ->
exclude group: "xpp3", name: "xpp3_min"
exclude group: "xmlpull", name: "xmlpull"
}
dependency "org.apache.johnzon:johnzon-jsonb:1.2.8"
dependency "org.apache.johnzon:johnzon-jsonb:1.2.10"
dependency("org.codehaus.jettison:jettison:1.3.8") {
exclude group: "stax", name: "stax-api"
}
@@ -88,8 +87,8 @@ configure(allprojects) { project ->
dependency "org.yaml:snakeyaml:1.27"
dependency "com.h2database:h2:1.4.200"
dependency "com.github.ben-manes.caffeine:caffeine:2.8.6"
dependency "com.github.librepdf:openpdf:1.3.22"
dependency "com.github.ben-manes.caffeine:caffeine:2.8.8"
dependency "com.github.librepdf:openpdf:1.3.25"
dependency "com.rometools:rome:1.12.2"
dependency "commons-io:commons-io:2.5"
dependency "io.vavr:vavr:0.10.3"
@@ -116,8 +115,8 @@ configure(allprojects) { project ->
dependency "net.sf.ehcache:ehcache:2.10.6"
dependency "org.ehcache:jcache:1.0.1"
dependency "org.ehcache:ehcache:3.4.0"
dependency "org.hibernate:hibernate-core:5.4.22.Final"
dependency "org.hibernate:hibernate-validator:6.1.6.Final"
dependency "org.hibernate:hibernate-core:5.4.28.Final"
dependency "org.hibernate:hibernate-validator:6.1.7.Final"
dependency "org.webjars:webjars-locator-core:0.46"
dependency "org.webjars:underscorejs:1.8.3"
@@ -147,7 +146,7 @@ configure(allprojects) { project ->
entry 'okhttp'
entry 'mockwebserver'
}
dependency("org.apache.httpcomponents:httpclient:4.5.12") {
dependency("org.apache.httpcomponents:httpclient:4.5.13") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.apache.httpcomponents:httpasyncclient:4.1.4") {
@@ -180,7 +179,7 @@ configure(allprojects) { project ->
dependency "org.testng:testng:6.14.3"
dependency "org.hamcrest:hamcrest:2.1"
dependency "org.awaitility:awaitility:3.1.6"
dependency "org.assertj:assertj-core:3.17.2"
dependency "org.assertj:assertj-core:3.18.1"
dependencySet(group: 'org.xmlunit', version: '2.6.2') {
entry 'xmlunit-assertj'
entry('xmlunit-matchers') {
@@ -193,7 +192,7 @@ configure(allprojects) { project ->
}
entry 'mockito-junit-jupiter'
}
dependency "io.mockk:mockk:1.10.0"
dependency "io.mockk:mockk:1.10.2"
dependency("net.sourceforge.htmlunit:htmlunit:2.43.0") {
exclude group: "commons-logging", name: "commons-logging"
@@ -225,7 +224,7 @@ configure(allprojects) { project ->
dependency "com.ibm.websphere:uow:6.0.2.17"
dependency "com.jamonapi:jamon:2.82"
dependency "joda-time:joda-time:2.10.6"
dependency "joda-time:joda-time:2.10.10"
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.7"
dependency "org.javamoney:moneta:1.3"
@@ -326,7 +325,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "8.36.2"
toolVersion = "8.38"
configDir = rootProject.file("src/checkstyle")
}
+57
View File
@@ -0,0 +1,57 @@
== 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-5.2.x[Spring Framework 5.2.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-5.2.x -c ci/pipeline.yml -l ci/parameters.yml
----
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
+17
View File
@@ -0,0 +1,17 @@
changelog:
repository: spring-projects/spring-framework
sections:
- title: ":star: New Features"
labels:
- "type: enhancement"
- title: ":beetle: Bug Fixes"
labels:
- "type: bug"
- "type: regression"
- title: ":notebook_with_decorative_cover: Documentation"
labels:
- "type: documentation"
- title: ":hammer: Dependency Upgrades"
sort: "title"
labels:
- "type: dependency-upgrade"
+10
View File
@@ -0,0 +1,10 @@
logging:
level:
io.spring.concourse: DEBUG
spring:
main:
banner-mode: off
sonatype:
exclude:
- 'build-info\.json'
- '.*\.zip'
+21
View File
@@ -0,0 +1,21 @@
== CI Images
These images are used by CI to run the actual builds.
To build the image locally run the following from this directory:
----
$ docker build --no-cache -f <image-folder>/Dockerfile .
----
For example
----
$ docker build --no-cache -f spring-framework-ci-image/Dockerfile .
----
To test run:
----
$ docker run -it --entrypoint /bin/bash <SHA>
----
+8
View File
@@ -0,0 +1,8 @@
FROM ubuntu:focal-20210119
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
RUN ./setup.sh java11
ENV JAVA_HOME /opt/openjdk
ENV PATH $JAVA_HOME/bin:$PATH
+8
View File
@@ -0,0 +1,8 @@
FROM ubuntu:focal-20210119
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
RUN ./setup.sh java8
ENV JAVA_HOME /opt/openjdk
ENV PATH $JAVA_HOME/bin:$PATH
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
set -e
case "$1" in
java8)
echo "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u282-b08/OpenJDK8U-jdk_x64_linux_hotspot_8u282b08.tar.gz"
;;
java11)
echo "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.10_9.tar.gz"
;;
*)
echo $"Unknown java version"
exit 1
esac
+35
View File
@@ -0,0 +1,35 @@
#!/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
curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/io/spring/concourse/releasescripts/concourse-release-scripts/0.3.2/concourse-release-scripts-0.3.2.jar
###########################################################
# JAVA
###########################################################
JDK_URL=$( ./get-jdk-url.sh $1 )
mkdir -p /opt/openjdk
cd /opt/openjdk
curl -L ${JDK_URL} | tar zx --strip-components=1
test -f /opt/openjdk/bin/java
test -f /opt/openjdk/bin/javac
###########################################################
# GRADLE ENTERPRISE
###########################################################
cd /
mkdir ~/.gradle
echo 'systemProp.user.name=concourse' > ~/.gradle/gradle.properties
+13
View File
@@ -0,0 +1,13 @@
email-server: "smtp.svc.pivotal.io"
email-from: "ci@spring.io"
email-to: ["spring-framework-dev@pivotal.io"]
github-repo: "https://github.com/spring-projects/spring-framework.git"
github-repo-name: "spring-projects/spring-framework"
docker-hub-organization: "springci"
artifactory-server: "https://repo.spring.io"
branch: "5.2.x"
milestone: "5.2.x"
build-name: "spring-framework"
pipeline-name: "spring-framework"
concourse-url: "https://ci.spring.io"
task-timeout: 1h00m
+386
View File
@@ -0,0 +1,386 @@
anchors:
git-repo-resource-source: &git-repo-resource-source
uri: ((github-repo))
username: ((github-username))
password: ((github-password))
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_ID: ((sonatype-staging-profile-id))
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
privileged: true
timeout: ((task-timeout))
params:
BRANCH: ((branch))
<<: *gradle-enterprise-task-params
docker-resource-source: &docker-resource-source
username: ((docker-hub-username))
password: ((docker-hub-password))
tag: ((milestone))
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: artifactory-resource
type: registry-image
source:
repository: springio/artifactory-resource
tag: 0.0.13
- name: github-status-resource
type: registry-image
source:
repository: dpb587/github-status-resource
tag: master
- name: slack-notification
type: registry-image
source:
repository: cfcommunity/slack-notification-resource
tag: latest
resources:
- name: git-repo
type: git
icon: github
source:
<<: *git-repo-resource-source
- name: every-morning
type: time
icon: alarm
source:
start: 8:00 AM
stop: 9:00 AM
location: Europe/Vienna
- name: ci-images-git-repo
type: git
icon: github
source:
uri: ((github-repo))
branch: ((branch))
paths: ["ci/images/*"]
- name: ci-image
type: docker-image
icon: docker
source:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci
- name: ci-image-jdk11
type: docker-image
icon: docker
source:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci-jdk11
- name: artifactory-repo
type: artifactory-resource
icon: package-variant
source:
uri: ((artifactory-server))
username: ((artifactory-username))
password: ((artifactory-password))
build_name: ((build-name))
- name: repo-status-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: build
- name: repo-status-jdk11-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk11-build
- name: 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: ci-images-git-repo
trigger: true
- in_parallel:
- put: ci-image
params:
build: ci-images-git-repo/ci/images
dockerfile: ci-images-git-repo/ci/images/ci-image/Dockerfile
- put: ci-image-jdk11
params:
build: ci-images-git-repo/ci/images
dockerfile: ci-images-git-repo/ci/images/ci-image-jdk11/Dockerfile
- 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
<<: *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:
- "/**/spring-*.zip"
properties:
"zip.name": "spring-framework"
"zip.displayname": "Spring Framework"
"zip.deployed": "false"
- include:
- "/**/spring-*-docs.zip"
properties:
"zip.type": "docs"
- include:
- "/**/spring-*-dist.zip"
properties:
"zip.type": "dist"
- include:
- "/**/spring-*-schema.zip"
properties:
"zip.type": "schema"
get_params:
threads: 8
- name: jdk11-build
serial: true
public: true
plan:
- get: ci-image-jdk11
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk11-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image-jdk11
file: git-repo/ci/tasks/check-project.yml
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk11-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk11-build
params: { state: "success", commit: "git-repo" }
- name: 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
image: ci-image
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: M
<<: *artifactory-task-params
- task: generate-changelog
file: git-repo/ci/tasks/generate-changelog.yml
params:
RELEASE_TYPE: M
<<: *github-task-params
- put: github-pre-release
params:
<<: *changelog-task-params
- name: stage-rc
serial: true
plan:
- get: 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
image: ci-image
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: RC
<<: *artifactory-task-params
- task: generate-changelog
file: git-repo/ci/tasks/generate-changelog.yml
params:
RELEASE_TYPE: RC
<<: *github-task-params
- put: github-pre-release
params:
<<: *changelog-task-params
- name: stage-release
serial: true
plan:
- get: 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
image: ci-image
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: RELEASE
<<: *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
<<: *github-task-params
- put: github-release
params:
<<: *changelog-task-params
groups:
- name: "builds"
jobs: ["build", "jdk11-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"]
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
repository=$(pwd)/distribution-repository
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
popd > /dev/null
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 check
popd > /dev/null
+2
View File
@@ -0,0 +1,2 @@
source /opt/concourse-java.sh
setup_symlinks
+17
View File
@@ -0,0 +1,17 @@
#!/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/' )
milestone=${version}
if [[ $RELEASE_TYPE = "RELEASE" ]]; then
milestone=${version%.RELEASE}
fi
java -jar /github-changelog-generator.jar \
--spring.config.location=${CONFIG_DIR}/changelog-generator.yml \
${milestone} generated-changelog/changelog.md
echo ${version} > generated-changelog/version
echo v${version} > generated-changelog/tag
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
source $(dirname $0)/common.sh
CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
java -jar /opt/concourse-release-scripts.jar \
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; }
java -jar /opt/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
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
repository=$(pwd)/distribution-repository
pushd git-repo > /dev/null
git fetch --tags --all > /dev/null
popd > /dev/null
git clone git-repo stage-git-repo > /dev/null
pushd stage-git-repo > /dev/null
snapshotVersion=$( awk -F '=' '$1 == "version" { print $2 }' gradle.properties )
if [[ $RELEASE_TYPE = "M" ]]; then
stageVersion=$( get_next_milestone_release $snapshotVersion)
nextVersion=$snapshotVersion
elif [[ $RELEASE_TYPE = "RC" ]]; then
stageVersion=$( get_next_rc_release $snapshotVersion)
nextVersion=$snapshotVersion
elif [[ $RELEASE_TYPE = "RELEASE" ]]; then
stageVersion=$( get_next_release $snapshotVersion "RELEASE")
nextVersion=$( bump_version_number $snapshotVersion)
else
echo "Unknown release type $RELEASE_TYPE" >&2; exit 1;
fi
echo "Staging $stageVersion (next version will be $nextVersion)"
sed -i "s/version=$snapshotVersion/version=$stageVersion/" gradle.properties
git config user.name "Spring Buildmaster" > /dev/null
git config user.email "buildmaster@springframework.org" > /dev/null
git add gradle.properties > /dev/null
git commit -m"Release v$stageVersion" > /dev/null
git tag -a "v$stageVersion" -m"Release v$stageVersion" > /dev/null
./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
git reset --hard HEAD^ > /dev/null
if [[ $nextVersion != $snapshotVersion ]]; then
echo "Setting next development version (v$nextVersion)"
sed -i "s/version=$snapshotVersion/version=$nextVersion/" gradle.properties
git add gradle.properties > /dev/null
git commit -m"Next development version (v$nextVersion)" > /dev/null
fi;
echo "Staging Complete"
popd > /dev/null
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
java -jar /opt/concourse-release-scripts.jar syncToCentral "RELEASE" $BUILD_INFO_LOCATION || { exit 1; }
echo "Sync complete"
echo $version > version/version
+22
View File
@@ -0,0 +1,22 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/build-project.sh
+22
View File
@@ -0,0 +1,22 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/check-project.sh
+20
View File
@@ -0,0 +1,20 @@
---
platform: linux
image_resource:
type: registry-image
source:
repository: springio/github-changelog-generator
tag: '0.0.6'
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
+18
View File
@@ -0,0 +1,18 @@
---
platform: linux
inputs:
- name: git-repo
- name: artifactory-repo
outputs:
- name: version
params:
RELEASE_TYPE:
ARTIFACTORY_SERVER:
ARTIFACTORY_USERNAME:
ARTIFACTORY_PASSWORD:
SONATYPE_USER:
SONATYPE_PASSWORD:
SONATYPE_URL:
SONATYPE_STAGING_PROFILE_ID:
run:
path: git-repo/ci/scripts/promote-version.sh
+17
View File
@@ -0,0 +1,17 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: stage-git-repo
- name: distribution-repository
params:
RELEASE_TYPE:
CI: true
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
caches:
- path: gradle
run:
path: git-repo/ci/scripts/stage-version.sh
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.2.11.BUILD-SNAPSHOT
version=5.2.15.RELEASE
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
+1 -2
View File
@@ -29,12 +29,11 @@ eclipse.classpath.file.whenMerged { classpath ->
classpath.entries.removeAll { entry -> (entry.path =~ /(?!.*?repack.*\.jar).*?\/([^\/]+)\/build\/libs\/[^\/]+\.jar/) }
}
// Use separate main/test outputs (prevents WTP from packaging test classes)
eclipse.classpath.defaultOutputDir = file(project.name+"/bin/eclipse")
eclipse.classpath.file.beforeMerged { classpath ->
classpath.entries.findAll{ it instanceof SourceFolder }.each {
if(it.output.startsWith("bin/")) {
if (it.output.startsWith("bin/")) {
it.output = null
}
}
+12 -3
View File
@@ -1,5 +1,4 @@
apply plugin: "maven-publish"
apply plugin: 'com.jfrog.artifactory'
publishing {
publications {
@@ -50,6 +49,16 @@ publishing {
}
}
artifactoryPublish {
publications(publishing.publications.mavenJava)
configureDeploymentRepository(project)
void configureDeploymentRepository(Project project) {
project.plugins.withType(MavenPublishPlugin.class).all {
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
if (project.hasProperty("deploymentRepository")) {
publishing.repositories.maven {
it.url = project.property("deploymentRepository")
it.name = "deployment"
}
}
}
}
+2 -2
View File
@@ -10,8 +10,8 @@ include "spring-aop"
include "spring-aspects"
include "spring-beans"
include "spring-context"
include "spring-context-support"
include "spring-context-indexer"
include "spring-context-support"
include "spring-core"
include "kotlin-coroutines"
project(':kotlin-coroutines').projectDir = file('spring-core/kotlin-coroutines')
@@ -26,8 +26,8 @@ include "spring-oxm"
include "spring-test"
include "spring-tx"
include "spring-web"
include "spring-webmvc"
include "spring-webflux"
include "spring-webmvc"
include "spring-websocket"
include "framework-bom"
include "integration-tests"
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@ import org.aopalliance.aop.Advice;
* <p>Introductions are often <b>mixins</b>, enabling the building of composite
* objects that can achieve many of the goals of multiple inheritance in Java.
*
* <p>Compared to {qlink IntroductionInfo}, this interface allows an advice to
* <p>Compared to {@link IntroductionInfo}, this interface allows an advice to
* implement a range of interfaces that is not necessarily known in advance.
* Thus an {@link IntroductionAdvisor} can be used to specify which interfaces
* will be exposed in an advised object.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -219,10 +219,12 @@ public class MethodInvocationProceedingJoinPoint implements ProceedingJoinPoint,
@Override
@Nullable
public String[] getParameterNames() {
if (this.parameterNames == null) {
this.parameterNames = parameterNameDiscoverer.getParameterNames(getMethod());
String[] parameterNames = this.parameterNames;
if (parameterNames == null) {
parameterNames = parameterNameDiscoverer.getParameterNames(getMethod());
this.parameterNames = parameterNames;
}
return this.parameterNames;
return parameterNames;
}
@Override
@@ -49,6 +49,7 @@ import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.core.KotlinDetector;
import org.springframework.core.SmartClassLoader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -752,10 +753,17 @@ class CglibAopProxy implements AopProxy, Serializable {
throw ex;
}
catch (Exception ex) {
if (ReflectionUtils.declaresException(getMethod(), ex.getClass())) {
if (ReflectionUtils.declaresException(getMethod(), ex.getClass()) ||
KotlinDetector.isKotlinType(getMethod().getDeclaringClass())) {
// Propagate original exception if declared on the target method
// (with callers expecting it). Always propagate it for Kotlin code
// since checked exceptions do not have to be explicitly declared there.
throw ex;
}
else {
// Checked exception thrown in the interceptor but not declared on the
// target method signature -> apply an UndeclaredThrowableException,
// aligned with standard JDK dynamic proxy behavior.
throw new UndeclaredThrowableException(ex);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -513,7 +513,10 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
List<Object> allInterceptors = new ArrayList<>();
if (specificInterceptors != null) {
allInterceptors.addAll(Arrays.asList(specificInterceptors));
if (specificInterceptors.length > 0) {
// specificInterceptors may equals PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS
allInterceptors.addAll(Arrays.asList(specificInterceptors));
}
if (commonInterceptors.length > 0) {
if (this.applyCommonInterceptorsFirst) {
allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -59,7 +59,8 @@ public abstract aspect AbstractTransactionAspect extends TransactionAspectSuppor
@Override
public void destroy() {
clearTransactionManagerCache(); // An aspect is basically a singleton
// An aspect is basically a singleton -> cleanup on destruction
clearTransactionManagerCache();
}
@SuppressAjWarnings("adviceDidNotMatch")
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -305,8 +305,10 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
Class<?> componentType = propValue.getClass().getComponentType();
Object newArray = Array.newInstance(componentType, arrayIndex + 1);
System.arraycopy(propValue, 0, newArray, 0, length);
setPropertyValue(tokens.actualName, newArray);
propValue = getPropertyValue(tokens.actualName);
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
String propName = tokens.canonicalName.substring(0, lastKeyIndex);
setPropertyValue(propName, newArray);
propValue = getPropertyValue(propName);
}
Array.set(propValue, arrayIndex, convertedValue);
}
@@ -422,9 +424,12 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
}
return;
}
else {
throw createNotWritablePropertyException(tokens.canonicalName);
if (this.suppressNotWritablePropertyException) {
// Optimization for common ignoreUnknown=true scenario since the
// exception would be caught and swallowed higher up anyway...
return;
}
throw createNotWritablePropertyException(tokens.canonicalName);
}
Object oldValue = null;
@@ -806,7 +811,6 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
* @param propertyPath property path, which may be nested
* @return a property accessor for the target bean
*/
@SuppressWarnings("unchecked") // avoid nested generic
protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
// Handle nested properties recursively.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,6 +40,8 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
private boolean autoGrowNestedPaths = false;
boolean suppressNotWritablePropertyException = false;
@Override
public void setExtractOldValueForEditor(boolean extractOldValueForEditor) {
@@ -89,30 +91,41 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
List<PropertyAccessException> propertyAccessExceptions = null;
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
for (PropertyValue pv : propertyValues) {
try {
// This method may throw any BeansException, which won't be caught
if (ignoreUnknown) {
this.suppressNotWritablePropertyException = true;
}
try {
for (PropertyValue pv : propertyValues) {
// setPropertyValue may throw any BeansException, which won't be caught
// here, if there is a critical failure such as no matching field.
// We can attempt to deal only with less serious exceptions.
setPropertyValue(pv);
}
catch (NotWritablePropertyException ex) {
if (!ignoreUnknown) {
throw ex;
try {
setPropertyValue(pv);
}
// Otherwise, just ignore it and continue...
}
catch (NullValueInNestedPathException ex) {
if (!ignoreInvalid) {
throw ex;
catch (NotWritablePropertyException ex) {
if (!ignoreUnknown) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
// Otherwise, just ignore it and continue...
}
catch (PropertyAccessException ex) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new ArrayList<>();
catch (NullValueInNestedPathException ex) {
if (!ignoreInvalid) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
propertyAccessExceptions.add(ex);
catch (PropertyAccessException ex) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new ArrayList<>();
}
propertyAccessExceptions.add(ex);
}
}
}
finally {
if (ignoreUnknown) {
this.suppressNotWritablePropertyException = false;
}
}
@@ -506,6 +506,7 @@ public abstract class BeanUtils {
if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) {
return null;
}
ClassLoader cl = targetType.getClassLoader();
if (cl == null) {
try {
@@ -522,28 +523,34 @@ public abstract class BeanUtils {
return null;
}
}
String targetTypeName = targetType.getName();
String editorName = targetTypeName + "Editor";
try {
Class<?> editorClass = cl.loadClass(editorName);
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
if (logger.isInfoEnabled()) {
logger.info("Editor class [" + editorName +
"] does not implement [java.beans.PropertyEditor] interface");
if (editorClass != null) {
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
if (logger.isInfoEnabled()) {
logger.info("Editor class [" + editorName +
"] does not implement [java.beans.PropertyEditor] interface");
}
unknownEditorTypes.add(targetType);
return null;
}
unknownEditorTypes.add(targetType);
return null;
return (PropertyEditor) instantiateClass(editorClass);
}
return (PropertyEditor) instantiateClass(editorClass);
// Misbehaving ClassLoader returned null instead of ClassNotFoundException
// - fall back to unknown editor type registration below
}
catch (ClassNotFoundException ex) {
if (logger.isTraceEnabled()) {
logger.trace("No property editor [" + editorName + "] found for type " +
targetTypeName + " according to 'Editor' suffix convention");
}
unknownEditorTypes.add(targetType);
return null;
// Ignore - fall back to unknown editor type registration below
}
if (logger.isTraceEnabled()) {
logger.trace("No property editor [" + editorName + "] found for type " +
targetTypeName + " according to 'Editor' suffix convention");
}
unknownEditorTypes.add(targetType);
return null;
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -92,8 +92,7 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
@Override
protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) {
PropertyMatches matches = PropertyMatches.forField(propertyName, getRootClass());
throw new NotWritablePropertyException(
getRootClass(), getNestedPath() + propertyName,
throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName,
matches.buildErrorMessage(), matches.getPossibleMatches());
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,10 +41,10 @@ public class TypeMismatchException extends PropertyAccessException {
private String propertyName;
@Nullable
private transient Object value;
private final transient Object value;
@Nullable
private Class<?> requiredType;
private final Class<?> requiredType;
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -613,7 +613,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
private final boolean required;
private volatile boolean cached = false;
private volatile boolean cached;
@Nullable
private volatile Object cachedFieldValue;
@@ -628,46 +628,58 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
Field field = (Field) this.member;
Object value;
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
try {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
value = resolveFieldValue(field, bean, beanName);
}
}
else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
if (value != null || this.required) {
this.cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
this.cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
else {
this.cachedFieldValue = null;
}
this.cached = true;
}
}
value = resolveFieldValue(field, bean, beanName);
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
Object value;
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
Object cachedFieldValue = null;
if (value != null || this.required) {
cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
this.cachedFieldValue = cachedFieldValue;
this.cached = true;
}
}
return value;
}
}
@@ -678,7 +690,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
private final boolean required;
private volatile boolean cached = false;
private volatile boolean cached;
@Nullable
private volatile Object[] cachedMethodArguments;
@@ -696,58 +708,16 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
Method method = (Method) this.member;
Object[] arguments;
if (this.cached) {
// Shortcut for avoiding synchronization...
arguments = resolveCachedArguments(beanName);
try {
arguments = resolveCachedArguments(beanName);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
arguments = resolveMethodArguments(method, bean, beanName);
}
}
else {
int argumentCount = method.getParameterCount();
arguments = new Object[argumentCount];
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < arguments.length; i++) {
MethodParameter methodParam = new MethodParameter(method, i);
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
currDesc.setContainingClass(bean.getClass());
descriptors[i] = currDesc;
try {
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
if (arg == null && !this.required) {
arguments = null;
break;
}
arguments[i] = arg;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
}
}
synchronized (this) {
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
registerDependentBeans(beanName, autowiredBeans);
if (autowiredBeans.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator();
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
String autowiredBeanName = it.next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
descriptors[i], autowiredBeanName, paramTypes[i]);
}
}
}
this.cachedMethodArguments = cachedMethodArguments;
}
else {
this.cachedMethodArguments = null;
}
this.cached = true;
}
}
arguments = resolveMethodArguments(method, bean, beanName);
}
if (arguments != null) {
try {
@@ -772,6 +742,59 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
}
return arguments;
}
@Nullable
private Object[] resolveMethodArguments(Method method, Object bean, @Nullable String beanName) {
int argumentCount = method.getParameterCount();
Object[] arguments = new Object[argumentCount];
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < arguments.length; i++) {
MethodParameter methodParam = new MethodParameter(method, i);
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
currDesc.setContainingClass(bean.getClass());
descriptors[i] = currDesc;
try {
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
if (arg == null && !this.required) {
arguments = null;
break;
}
arguments[i] = arg;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
}
}
synchronized (this) {
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
registerDependentBeans(beanName, autowiredBeans);
if (autowiredBeans.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator();
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
String autowiredBeanName = it.next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
descriptors[i], autowiredBeanName, paramTypes[i]);
}
}
}
this.cachedMethodArguments = cachedMethodArguments;
}
else {
this.cachedMethodArguments = null;
}
this.cached = true;
}
}
return arguments;
}
}
@@ -26,9 +26,6 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -69,8 +66,6 @@ public class InjectionMetadata {
};
private static final Log logger = LogFactory.getLog(InjectionMetadata.class);
private final Class<?> targetClass;
private final Collection<InjectedElement> injectedElements;
@@ -110,9 +105,6 @@ public class InjectionMetadata {
if (!beanDefinition.isExternallyManagedConfigMember(member)) {
beanDefinition.registerExternallyManagedConfigMember(member);
checkedElements.add(element);
if (logger.isTraceEnabled()) {
logger.trace("Registered injected element on class [" + this.targetClass.getName() + "]: " + element);
}
}
}
this.checkedElements = checkedElements;
@@ -124,9 +116,6 @@ public class InjectionMetadata {
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
if (logger.isTraceEnabled()) {
logger.trace("Processing injected element of bean '" + beanName + "': " + element);
}
element.inject(target, beanName, pvs);
}
}
@@ -152,12 +141,12 @@ public class InjectionMetadata {
* Return an {@code InjectionMetadata} instance, possibly for empty elements.
* @param elements the elements to inject (possibly empty)
* @param clazz the target class
* @return a new {@link #InjectionMetadata(Class, Collection)} instance,
* or {@link #EMPTY} in case of no elements
* @return a new {@link #InjectionMetadata(Class, Collection)} instance
* @since 5.2
*/
public static InjectionMetadata forElements(Collection<InjectedElement> elements, Class<?> clazz) {
return (elements.isEmpty() ? InjectionMetadata.EMPTY : new InjectionMetadata(clazz, elements));
return (elements.isEmpty() ? new InjectionMetadata(clazz, Collections.emptyList()) :
new InjectionMetadata(clazz, elements));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,15 +36,16 @@ import org.springframework.util.StringValueResolver;
* Example XML bean definition:
*
* <pre class="code">
* &lt;bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"/&gt;
* &lt;property name="driverClassName" value="${driver}"/&gt;
* &lt;property name="url" value="jdbc:${dbname}"/&gt;
* &lt;bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"&gt;
* &lt;property name="driverClassName" value="${driver}" /&gt;
* &lt;property name="url" value="jdbc:${dbname}" /&gt;
* &lt;/bean&gt;
* </pre>
*
* Example properties file:
*
* <pre class="code">driver=com.mysql.jdbc.Driver
* <pre class="code">
* driver=com.mysql.jdbc.Driver
* dbname=mysql:mydb</pre>
*
* Annotated bean definitions may take advantage of property replacement using
@@ -56,7 +57,8 @@ import org.springframework.util.StringValueResolver;
* in bean references. Furthermore, placeholder values can also cross-reference
* other placeholders, like:
*
* <pre class="code">rootPath=myrootdir
* <pre class="code">
* rootPath=myrootdir
* subPath=${rootPath}/subdir</pre>
*
* In contrast to {@link PropertyOverrideConfigurer}, subclasses of this type allow
@@ -71,13 +73,13 @@ import org.springframework.util.StringValueResolver;
*
* <p>Default property values can be defined globally for each configurer instance
* via the {@link #setProperties properties} property, or on a property-by-property basis
* using the default value separator which is {@code ":"} by default and
* customizable via {@link #setValueSeparator(String)}.
* using the value separator which is {@code ":"} by default and customizable via
* {@link #setValueSeparator(String)}.
*
* <p>Example XML property with default value:
*
* <pre class="code">
* <property name="url" value="jdbc:${dbname:defaultdb}"/>
* &lt;property name="url" value="jdbc:${dbname:defaultdb}" /&gt;
* </pre>
*
* @author Chris Beams
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -489,8 +489,8 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
resolveConstructorArguments(args, 2, hasClosureArgument ? args.length - 1 : args.length);
this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, (Class<?>) args[1], constructorArgs);
Map<?, ?> namedArgs = (Map<?, ?>) args[0];
for (Object o : namedArgs.keySet()) {
String propName = (String) o;
for (Object key : namedArgs.keySet()) {
String propName = (String) key;
setProperty(propName, namedArgs.get(propName));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1013,6 +1013,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
throw ex;
}
catch (BeanCreationException ex) {
// Don't swallow a linkage error since it contains a full stacktrace on
// first occurrence... and just a plain NoClassDefFoundError afterwards.
if (ex.contains(LinkageError.class)) {
throw ex;
}
// Instantiation failure, maybe too early...
if (logger.isDebugEnabled()) {
logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1194,8 +1194,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
this.primary == that.primary &&
this.nonPublicAccessAllowed == that.nonPublicAccessAllowed &&
this.lenientConstructorResolution == that.lenientConstructorResolution &&
ObjectUtils.nullSafeEquals(this.constructorArgumentValues, that.constructorArgumentValues) &&
ObjectUtils.nullSafeEquals(this.propertyValues, that.propertyValues) &&
equalsConstructorArgumentValues(that) &&
equalsPropertyValues(that) &&
ObjectUtils.nullSafeEquals(this.methodOverrides, that.methodOverrides) &&
ObjectUtils.nullSafeEquals(this.factoryBeanName, that.factoryBeanName) &&
ObjectUtils.nullSafeEquals(this.factoryMethodName, that.factoryMethodName) &&
@@ -1208,12 +1208,30 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
super.equals(other));
}
private boolean equalsConstructorArgumentValues(AbstractBeanDefinition other) {
if (!hasConstructorArgumentValues()) {
return !other.hasConstructorArgumentValues();
}
return ObjectUtils.nullSafeEquals(this.constructorArgumentValues, other.constructorArgumentValues);
}
private boolean equalsPropertyValues(AbstractBeanDefinition other) {
if (!hasPropertyValues()) {
return !other.hasPropertyValues();
}
return ObjectUtils.nullSafeEquals(this.propertyValues, other.propertyValues);
}
@Override
public int hashCode() {
int hashCode = ObjectUtils.nullSafeHashCode(getBeanClassName());
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.scope);
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues);
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues);
if (hasConstructorArgumentValues()) {
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues);
}
if (hasPropertyValues()) {
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues);
}
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryBeanName);
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryMethodName);
hashCode = 29 * hashCode + super.hashCode();
@@ -66,6 +66,7 @@ import org.springframework.util.StringUtils;
/**
* Delegate for resolving constructors and factory methods.
*
* <p>Performs constructor resolution through argument matching.
*
* @author Juergen Hoeller
@@ -84,7 +85,7 @@ class ConstructorResolver {
private static final Object[] EMPTY_ARGS = new Object[0];
/**
* Marker for autowired arguments in a cached argument array, to be later replaced
* Marker for autowired arguments in a cached argument array, to be replaced
* by a {@linkplain #resolveAutowiredArgument resolved autowired argument}.
*/
private static final Object autowiredArgumentMarker = new Object();
@@ -148,7 +149,7 @@ class ConstructorResolver {
}
}
if (argsToResolve != null) {
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);
}
}
@@ -409,6 +410,7 @@ class ConstructorResolver {
if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
throw new ImplicitlyAppearedSingletonException();
}
this.beanFactory.registerDependentBean(factoryBeanName, beanName);
factoryClass = factoryBean.getClass();
isStatic = false;
}
@@ -443,7 +445,7 @@ class ConstructorResolver {
}
}
if (argsToResolve != null) {
argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve, true);
argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve);
}
}
@@ -815,7 +817,7 @@ class ConstructorResolver {
* Resolve the prepared arguments stored in the given bean definition.
*/
private Object[] resolvePreparedArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw,
Executable executable, Object[] argsToResolve, boolean fallback) {
Executable executable, Object[] argsToResolve) {
TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
TypeConverter converter = (customConverter != null ? customConverter : bw);
@@ -828,7 +830,7 @@ class ConstructorResolver {
Object argValue = argsToResolve[argIndex];
MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
if (argValue == autowiredArgumentMarker) {
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, fallback);
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, true);
}
else if (argValue instanceof BeanMetadataElement) {
argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -87,7 +87,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
private transient Method destroyMethod;
@Nullable
private List<DestructionAwareBeanPostProcessor> beanPostProcessors;
private final List<DestructionAwareBeanPostProcessor> beanPostProcessors;
/**
@@ -120,14 +120,16 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
else {
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
if (paramTypes.length > 1) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has more than one parameter - not supported as destroy method");
}
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has a non-boolean parameter - not supported as destroy method");
if (destroyMethod.getParameterCount() > 0) {
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
if (paramTypes.length > 1) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has more than one parameter - not supported as destroy method");
}
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
beanName + "' has a non-boolean parameter - not supported as destroy method");
}
}
destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod);
}
@@ -169,67 +171,6 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
/**
* If the current value of the given beanDefinition's "destroyMethodName" property is
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
* Candidate methods are currently limited to public, no-arg methods named "close" or
* "shutdown" (whether declared locally or inherited). The given BeanDefinition's
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
* to the name of the inferred method. This constant serves as the default for the
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
* used in XML within the {@code <bean destroy-method="">} or {@code
* <beans default-destroy-method="">} attributes.
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
* interfaces, reflectively calling the "close" method on implementing beans as well.
*/
@Nullable
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
String destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
// Only perform destroy method inference or Closeable detection
// in case of the bean not explicitly implementing DisposableBean
if (!(bean instanceof DisposableBean)) {
try {
return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex) {
try {
return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex2) {
// no candidate destroy method found
}
}
}
return null;
}
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}
/**
* Search for all DestructionAwareBeanPostProcessors in the List.
* @param processors the List to search
* @return the filtered List of DestructionAwareBeanPostProcessors
*/
@Nullable
private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) {
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
if (!CollectionUtils.isEmpty(processors)) {
filteredPostProcessors = new ArrayList<>(processors.size());
for (BeanPostProcessor processor : processors) {
if (processor instanceof DestructionAwareBeanPostProcessor) {
DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
if (dabpp.requiresDestruction(bean)) {
filteredPostProcessors.add(dabpp);
}
}
}
}
return filteredPostProcessors;
}
@Override
public void run() {
destroy();
@@ -384,12 +325,50 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
return true;
}
String destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) ||
ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME));
return inferDestroyMethodIfNecessary(bean, beanDefinition) != null;
}
/**
* If the current value of the given beanDefinition's "destroyMethodName" property is
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
* Candidate methods are currently limited to public, no-arg methods named "close" or
* "shutdown" (whether declared locally or inherited). The given BeanDefinition's
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
* to the name of the inferred method. This constant serves as the default for the
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
* used in XML within the {@code <bean destroy-method="">} or {@code
* <beans default-destroy-method="">} attributes.
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
* interfaces, reflectively calling the "close" method on implementing beans as well.
*/
@Nullable
private static String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
if (destroyMethodName == null) {
destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
// Only perform destroy method inference or Closeable detection
// in case of the bean not explicitly implementing DisposableBean
destroyMethodName = null;
if (!(bean instanceof DisposableBean)) {
try {
destroyMethodName = bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex) {
try {
destroyMethodName = bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex2) {
// no candidate destroy method found
}
}
}
}
beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
}
return StringUtils.hasLength(destroyMethodName);
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}
/**
@@ -411,4 +390,26 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
return false;
}
/**
* Search for all DestructionAwareBeanPostProcessors in the List.
* @param processors the List to search
* @return the filtered List of DestructionAwareBeanPostProcessors
*/
@Nullable
private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) {
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
if (!CollectionUtils.isEmpty(processors)) {
filteredPostProcessors = new ArrayList<>(processors.size());
for (BeanPostProcessor processor : processors) {
if (processor instanceof DestructionAwareBeanPostProcessor) {
DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
if (dabpp.requiresDestruction(bean)) {
filteredPostProcessors.add(dabpp);
}
}
}
}
return filteredPostProcessors;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,7 +65,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
boolean allowCaching = true;
boolean isFactoryMethodUnique = false;
boolean isFactoryMethodUnique;
@Nullable
volatile ResolvableType targetType;
@@ -86,6 +86,10 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
@Nullable
volatile Method factoryMethodToIntrospect;
/** Package-visible field for caching a resolved destroy method name (also for inferred). */
@Nullable
volatile String resolvedDestroyMethodName;
/** Common lock for the four constructor fields below. */
final Object constructorArgumentLock = new Object();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,8 +26,8 @@ import java.nio.file.Paths;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
/**
* Editor for {@code java.nio.file.Path}, to directly populate a Path
@@ -74,7 +74,7 @@ public class PathEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
boolean nioPathCandidate = !text.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX);
boolean nioPathCandidate = !text.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX);
if (nioPathCandidate && !text.startsWith("/")) {
try {
URI uri = new URI(text);
@@ -85,9 +85,13 @@ public class PathEditor extends PropertyEditorSupport {
return;
}
}
catch (URISyntaxException | FileSystemNotFoundException ex) {
// Not a valid URI (let's try as Spring resource location),
// or a URI scheme not registered for NIO (let's try URL
catch (URISyntaxException ex) {
// Not a valid URI; potentially a Windows-style path after
// a file prefix (let's try as Spring resource location)
nioPathCandidate = !text.startsWith(ResourceUtils.FILE_URL_PREFIX);
}
catch (FileSystemNotFoundException ex) {
// URI scheme not registered for NIO (let's try URL
// protocol handlers via Spring's resource mechanism).
}
}
@@ -97,7 +101,7 @@ public class PathEditor extends PropertyEditorSupport {
if (resource == null) {
setValue(null);
}
else if (!resource.exists() && nioPathCandidate) {
else if (nioPathCandidate && !resource.exists()) {
setValue(Paths.get(text).normalize());
}
else {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Keith Donald
* @author Juergen Hoeller
* @author Sam Brannen
*/
public class BeanWrapperAutoGrowingTests {
@@ -66,11 +67,6 @@ public class BeanWrapperAutoGrowingTests {
assertThat(bean.getArray()[0]).isInstanceOf(Bean.class);
}
private void assertNotNull(Object propertyValue) {
assertThat(propertyValue).isNotNull();
}
@Test
public void setPropertyValueAutoGrowArray() {
wrapper.setPropertyValue("array[0].prop", "test");
@@ -93,12 +89,39 @@ public class BeanWrapperAutoGrowingTests {
}
@Test
public void getPropertyValueAutoGrowMultiDimensionalArray() {
public void getPropertyValueAutoGrow2dArray() {
assertNotNull(wrapper.getPropertyValue("multiArray[0][0]"));
assertThat(bean.getMultiArray()[0].length).isEqualTo(1);
assertThat(bean.getMultiArray()[0][0]).isInstanceOf(Bean.class);
}
@Test
public void getPropertyValueAutoGrow3dArray() {
assertNotNull(wrapper.getPropertyValue("threeDimensionalArray[1][2][3]"));
assertThat(bean.getThreeDimensionalArray()[1].length).isEqualTo(3);
assertThat(bean.getThreeDimensionalArray()[1][2][3]).isInstanceOf(Bean.class);
}
@Test
public void setPropertyValueAutoGrow2dArray() {
Bean newBean = new Bean();
newBean.setProp("enigma");
wrapper.setPropertyValue("multiArray[2][3]", newBean);
assertThat(bean.getMultiArray()[2][3])
.isInstanceOf(Bean.class)
.extracting(Bean::getProp).isEqualTo("enigma");
}
@Test
public void setPropertyValueAutoGrow3dArray() {
Bean newBean = new Bean();
newBean.setProp("enigma");
wrapper.setPropertyValue("threeDimensionalArray[2][3][4]", newBean);
assertThat(bean.getThreeDimensionalArray()[2][3][4])
.isInstanceOf(Bean.class)
.extracting(Bean::getProp).isEqualTo("enigma");
}
@Test
public void getPropertyValueAutoGrowList() {
assertNotNull(wrapper.getPropertyValue("list[0]"));
@@ -131,7 +154,7 @@ public class BeanWrapperAutoGrowingTests {
public void getPropertyValueAutoGrowListFailsAgainstLimit() {
wrapper.setAutoGrowCollectionLimit(2);
assertThatExceptionOfType(InvalidPropertyException.class).isThrownBy(() ->
assertNotNull(wrapper.getPropertyValue("list[4]")))
wrapper.getPropertyValue("list[4]"))
.withRootCauseInstanceOf(IndexOutOfBoundsException.class);
}
@@ -161,6 +184,11 @@ public class BeanWrapperAutoGrowingTests {
}
private static void assertNotNull(Object propertyValue) {
assertThat(propertyValue).isNotNull();
}
@SuppressWarnings("rawtypes")
public static class Bean {
@@ -174,6 +202,8 @@ public class BeanWrapperAutoGrowingTests {
private Bean[][] multiArray;
private Bean[][][] threeDimensionalArray;
private List<Bean> list;
private List<List<Bean>> multiList;
@@ -214,6 +244,14 @@ public class BeanWrapperAutoGrowingTests {
this.multiArray = multiArray;
}
public Bean[][][] getThreeDimensionalArray() {
return threeDimensionalArray;
}
public void setThreeDimensionalArray(Bean[][][] threeDimensionalArray) {
this.threeDimensionalArray = threeDimensionalArray;
}
public List<Bean> getList() {
return list;
}
@@ -63,9 +63,8 @@ public class BeanFactoryUtilsTests {
@BeforeEach
public void setUp() {
public void setup() {
// Interesting hierarchical factory to test counts.
// Slow to read so we cache it.
DefaultListableBeanFactory grandParent = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(grandParent).loadBeanDefinitions(ROOT_CONTEXT);
@@ -93,7 +92,7 @@ public class BeanFactoryUtilsTests {
* Check that override doesn't count as two separate beans.
*/
@Test
public void testHierarchicalCountBeansWithOverride() throws Exception {
public void testHierarchicalCountBeansWithOverride() {
// Leaf count
assertThat(this.listableBeanFactory.getBeanDefinitionCount() == 1).isTrue();
// Count minus duplicate
@@ -101,14 +100,14 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testHierarchicalNamesWithNoMatch() throws Exception {
public void testHierarchicalNamesWithNoMatch() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, NoOp.class));
assertThat(names.size()).isEqualTo(0);
}
@Test
public void testHierarchicalNamesWithMatchOnlyInRoot() throws Exception {
public void testHierarchicalNamesWithMatchOnlyInRoot() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, IndexedTestBean.class));
assertThat(names.size()).isEqualTo(1);
@@ -118,7 +117,7 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testGetBeanNamesForTypeWithOverride() throws Exception {
public void testGetBeanNamesForTypeWithOverride() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class));
// includes 2 TestBeans from FactoryBeans (DummyFactory definitions)
@@ -236,7 +235,7 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testHierarchicalResolutionWithOverride() throws Exception {
public void testHierarchicalResolutionWithOverride() {
Object test3 = this.listableBeanFactory.getBean("test3");
Object test = this.listableBeanFactory.getBean("test");
@@ -276,14 +275,14 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testHierarchicalNamesForAnnotationWithNoMatch() throws Exception {
public void testHierarchicalNamesForAnnotationWithNoMatch() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, Override.class));
assertThat(names.size()).isEqualTo(0);
}
@Test
public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() throws Exception {
public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() {
List<String> names = Arrays.asList(
BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, TestAnnotation.class));
assertThat(names.size()).isEqualTo(1);
@@ -293,7 +292,7 @@ public class BeanFactoryUtilsTests {
}
@Test
public void testGetBeanNamesForAnnotationWithOverride() throws Exception {
public void testGetBeanNamesForAnnotationWithOverride() {
AnnotatedBean annotatedBean = new AnnotatedBean();
this.listableBeanFactory.registerSingleton("anotherAnnotatedBean", annotatedBean);
List<String> names = Arrays.asList(
@@ -433,6 +432,7 @@ public class BeanFactoryUtilsTests {
String basePackage() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@ControllerAdvice
@interface RestControllerAdvice {
@@ -444,18 +444,23 @@ public class BeanFactoryUtilsTests {
String basePackage() default "";
}
@ControllerAdvice("com.example")
static class ControllerAdviceClass {
}
@RestControllerAdvice("com.example")
static class RestControllerAdviceClass {
}
static class TestBeanSmartFactoryBean implements SmartFactoryBean<TestBean> {
private final TestBean testBean = new TestBean("enigma", 42);
private final boolean singleton;
private final boolean prototype;
TestBeanSmartFactoryBean(boolean singleton, boolean prototype) {
@@ -478,7 +483,7 @@ public class BeanFactoryUtilsTests {
return TestBean.class;
}
public TestBean getObject() throws Exception {
public TestBean getObject() {
// We don't really care if the actual instance is a singleton or prototype
// for the tests that use this factory.
return this.testBean;
@@ -796,12 +796,13 @@ class DefaultListableBeanFactoryTests {
factory.registerBeanDefinition("child", childDefinition);
factory.registerAlias("parent", "alias");
TestBean child = (TestBean) factory.getBean("child");
TestBean child = factory.getBean("child", TestBean.class);
assertThat(child.getName()).isEqualTo(EXPECTED_NAME);
assertThat(child.getAge()).isEqualTo(EXPECTED_AGE);
Object mergedBeanDefinition2 = factory.getMergedBeanDefinition("child");
BeanDefinition mergedBeanDefinition1 = factory.getMergedBeanDefinition("child");
BeanDefinition mergedBeanDefinition2 = factory.getMergedBeanDefinition("child");
assertThat(mergedBeanDefinition2).as("Use cached merged bean definition").isEqualTo(mergedBeanDefinition2);
assertThat(mergedBeanDefinition1).as("Use cached merged bean definition").isSameAs(mergedBeanDefinition2);
}
@Test
@@ -1849,8 +1850,7 @@ class DefaultListableBeanFactoryTests {
assertThat(factoryBean).as("The FactoryBean should have been registered.").isNotNull();
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) lbf.autowire(FactoryBeanDependentBean.class,
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
Object mergedBeanDefinition2 = bean.getFactoryBean();
assertThat(mergedBeanDefinition2).as("The FactoryBeanDependentBean should have been autowired 'by type' with the LazyInitFactory.").isEqualTo(mergedBeanDefinition2);
assertThat(bean.getFactoryBean()).as("The FactoryBeanDependentBean should have been autowired 'by type' with the LazyInitFactory.").isEqualTo(factoryBean);
}
@Test
@@ -2556,8 +2556,7 @@ class DefaultListableBeanFactoryTests {
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(1);
}
@Test
@@ -2571,8 +2570,7 @@ class DefaultListableBeanFactoryTests {
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(2);
}
@Test
@@ -2587,8 +2585,7 @@ class DefaultListableBeanFactoryTests {
BeanWithDestroyMethod.closeCount = 0;
lbf.preInstantiateSingletons();
lbf.destroySingletons();
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(1);
}
@Test
@@ -2710,14 +2707,15 @@ class DefaultListableBeanFactoryTests {
factory.registerBeanDefinition("child", child);
AbstractBeanDefinition def = (AbstractBeanDefinition) factory.getBeanDefinition("child");
Object mergedBeanDefinition2 = def.getScope();
assertThat(mergedBeanDefinition2).as("Child 'scope' not overriding parent scope (it must).").isEqualTo(mergedBeanDefinition2);
assertThat(def.getScope()).as("Child 'scope' not overriding parent scope (it must).").isEqualTo(theChildScope);
}
@Test
void scopeInheritanceForChildBeanDefinitions() {
String theParentScope = "bonanza!";
RootBeanDefinition parent = new RootBeanDefinition();
parent.setScope("bonanza!");
parent.setScope(theParentScope);
AbstractBeanDefinition child = new ChildBeanDefinition("parent");
child.setBeanClass(TestBean.class);
@@ -2727,8 +2725,7 @@ class DefaultListableBeanFactoryTests {
factory.registerBeanDefinition("child", child);
BeanDefinition def = factory.getMergedBeanDefinition("child");
Object mergedBeanDefinition2 = def.getScope();
assertThat(mergedBeanDefinition2).as("Child 'scope' not inherited").isEqualTo(mergedBeanDefinition2);
assertThat(def.getScope()).as("Child 'scope' not inherited").isEqualTo(theParentScope);
}
@Test
@@ -2764,15 +2761,12 @@ class DefaultListableBeanFactoryTests {
});
lbf.preInstantiateSingletons();
TestBean tb = (TestBean) lbf.getBean("test");
Object mergedBeanDefinition2 = tb.getName();
assertThat(mergedBeanDefinition2).as("Name was set on field by IAPP").isEqualTo(mergedBeanDefinition2);
assertThat(tb.getName()).as("Name was set on field by IAPP").isEqualTo(nameSetOnField);
if (!skipPropertyPopulation) {
Object mergedBeanDefinition21 = tb.getAge();
assertThat(mergedBeanDefinition21).as("Property value still set").isEqualTo(mergedBeanDefinition21);
assertThat(tb.getAge()).as("Property value still set").isEqualTo(ageSetByPropertyValue);
}
else {
Object mergedBeanDefinition21 = tb.getAge();
assertThat(mergedBeanDefinition21).as("Property value was NOT set and still has default value").isEqualTo(mergedBeanDefinition21);
assertThat(tb.getAge()).as("Property value was NOT set and still has default value").isEqualTo(0);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -297,6 +297,121 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
}
@Test
public void testOptionalResourceInjectionWithSingletonRemoval() {
RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", rbd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
IndexedTestBean itb = new IndexedTestBean();
bf.registerSingleton("indexedTestBean", itb);
NestedTestBean ntb1 = new NestedTestBean();
bf.registerSingleton("nestedTestBean1", ntb1);
NestedTestBean ntb2 = new NestedTestBean();
bf.registerSingleton("nestedTestBean2", ntb2);
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.destroySingleton("testBean");
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
assertThat(bean.getTestBean4()).isNull();
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.registerSingleton("testBean", tb);
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
}
@Test
public void testOptionalResourceInjectionWithBeanDefinitionRemoval() {
RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", rbd);
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
IndexedTestBean itb = new IndexedTestBean();
bf.registerSingleton("indexedTestBean", itb);
NestedTestBean ntb1 = new NestedTestBean();
bf.registerSingleton("nestedTestBean1", ntb1);
NestedTestBean ntb2 = new NestedTestBean();
bf.registerSingleton("nestedTestBean2", ntb2);
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.removeBeanDefinition("testBean");
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
assertThat(bean.getTestBean4()).isNull();
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean"));
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
}
@Test
public void testOptionalCollectionResourceInjection() {
RootBeanDefinition rbd = new RootBeanDefinition(OptionalCollectionResourceInjectionBean.class);
@@ -533,6 +648,83 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getBeanFactory()).isSameAs(bf);
}
@Test
public void testConstructorResourceInjectionWithSingletonRemoval() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
NestedTestBean ntb = new NestedTestBean();
bf.registerSingleton("nestedTestBean", ntb);
ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.destroySingleton("nestedTestBean");
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isNull();
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.registerSingleton("nestedTestBean", ntb);
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf);
}
@Test
public void testConstructorResourceInjectionWithBeanDefinitionRemoval() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class));
ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean"));
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.removeBeanDefinition("nestedTestBean");
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isNull();
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class));
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean"));
assertThat(bean.getBeanFactory()).isSameAs(bf);
}
@Test
public void testConstructorResourceInjectionWithNullFromFactoryBean() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -133,6 +133,16 @@ public class BeanDefinitionTests {
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
bd.getPropertyValues();
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
bd.getConstructorArgumentValues();
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,57 +34,78 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
public class PathEditorTests {
@Test
public void testClasspathPathName() throws Exception {
public void testClasspathPathName() {
PropertyEditor pathEditor = new PathEditor();
pathEditor.setAsText("classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
ClassUtils.getShortName(getClass()) + ".class");
Object value = pathEditor.getValue();
boolean condition = value instanceof Path;
assertThat(condition).isTrue();
assertThat(value instanceof Path).isTrue();
Path path = (Path) value;
assertThat(path.toFile().exists()).isTrue();
}
@Test
public void testWithNonExistentResource() throws Exception {
public void testWithNonExistentResource() {
PropertyEditor propertyEditor = new PathEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
propertyEditor.setAsText("classpath:/no_way_this_file_is_found.doc"));
}
@Test
public void testWithNonExistentPath() throws Exception {
public void testWithNonExistentPath() {
PropertyEditor pathEditor = new PathEditor();
pathEditor.setAsText("file:/no_way_this_file_is_found.doc");
Object value = pathEditor.getValue();
boolean condition1 = value instanceof Path;
assertThat(condition1).isTrue();
assertThat(value instanceof Path).isTrue();
Path path = (Path) value;
boolean condition = !path.toFile().exists();
assertThat(condition).isTrue();
assertThat(!path.toFile().exists()).isTrue();
}
@Test
public void testAbsolutePath() throws Exception {
public void testAbsolutePath() {
PropertyEditor pathEditor = new PathEditor();
pathEditor.setAsText("/no_way_this_file_is_found.doc");
Object value = pathEditor.getValue();
boolean condition1 = value instanceof Path;
assertThat(condition1).isTrue();
assertThat(value instanceof Path).isTrue();
Path path = (Path) value;
boolean condition = !path.toFile().exists();
assertThat(condition).isTrue();
assertThat(!path.toFile().exists()).isTrue();
}
@Test
public void testUnqualifiedPathNameFound() throws Exception {
public void testWindowsAbsolutePath() {
PropertyEditor pathEditor = new PathEditor();
pathEditor.setAsText("C:\\no_way_this_file_is_found.doc");
Object value = pathEditor.getValue();
assertThat(value instanceof Path).isTrue();
Path path = (Path) value;
assertThat(!path.toFile().exists()).isTrue();
}
@Test
public void testWindowsAbsoluteFilePath() {
PropertyEditor pathEditor = new PathEditor();
try {
pathEditor.setAsText("file://C:\\no_way_this_file_is_found.doc");
Object value = pathEditor.getValue();
assertThat(value instanceof Path).isTrue();
Path path = (Path) value;
assertThat(!path.toFile().exists()).isTrue();
}
catch (IllegalArgumentException ex) {
if (File.separatorChar == '\\') { // on Windows, otherwise silently ignore
throw ex;
}
}
}
@Test
public void testUnqualifiedPathNameFound() {
PropertyEditor pathEditor = new PathEditor();
String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
ClassUtils.getShortName(getClass()) + ".class";
pathEditor.setAsText(fileName);
Object value = pathEditor.getValue();
boolean condition = value instanceof Path;
assertThat(condition).isTrue();
assertThat(value instanceof Path).isTrue();
Path path = (Path) value;
File file = path.toFile();
assertThat(file.exists()).isTrue();
@@ -96,14 +117,13 @@ public class PathEditorTests {
}
@Test
public void testUnqualifiedPathNameNotFound() throws Exception {
public void testUnqualifiedPathNameNotFound() {
PropertyEditor pathEditor = new PathEditor();
String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
ClassUtils.getShortName(getClass()) + ".clazz";
pathEditor.setAsText(fileName);
Object value = pathEditor.getValue();
boolean condition = value instanceof Path;
assertThat(condition).isTrue();
assertThat(value instanceof Path).isTrue();
Path path = (Path) value;
File file = path.toFile();
assertThat(file.exists()).isFalse();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,8 @@ import org.springframework.core.annotation.AliasFor;
*
* <p>In contrast to the {@link Cacheable @Cacheable} annotation, this annotation
* does not cause the advised method to be skipped. Rather, it always causes the
* method to be invoked and its result to be stored in the associated cache. Note
* method to be invoked and its result to be stored in the associated cache if the
* {@link #condition()} and {@link #unless()} expressions match accordingly. Note
* that Java8's {@code Optional} return types are automatically handled and its
* content is stored in the cache if present.
*
@@ -118,10 +119,15 @@ public @interface CachePut {
/**
* Spring Expression Language (SpEL) expression used for making the cache
* put operation conditional.
* <p>This expression is evaluated after the method has been called due to the
* nature of the put operation and can therefore refer to the {@code result}.
* <p>Default is {@code ""}, meaning the method result is always cached.
* <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
* <ul>
* <li>{@code #result} for a reference to the result of the method invocation. For
* supported wrappers such as {@code Optional}, {@code #result} refers to the actual
* object, not the wrapper</li>
* <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
* references to the {@link java.lang.reflect.Method method}, target object, and
* affected cache(s) respectively.</li>
@@ -136,8 +142,6 @@ public @interface CachePut {
/**
* Spring Expression Language (SpEL) expression used to veto the cache put operation.
* <p>Unlike {@link #condition}, this expression is evaluated after the method
* has been called and can therefore refer to the {@code result}.
* <p>Default is {@code ""}, meaning that caching is never vetoed.
* <p>The SpEL expression evaluates against a dedicated context that provides the
* following meta-data:
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -71,16 +71,17 @@ import org.springframework.util.ClassUtils;
*
* <p>Registered by default when using {@code <context:annotation-config/>} or
* {@code <context:component-scan/>}. Otherwise, may be declared manually as
* with any other BeanFactoryPostProcessor.
* with any other {@link BeanFactoryPostProcessor}.
*
* <p>This post processor is priority-ordered as it is important that any
* {@link Bean} methods declared in {@code @Configuration} classes have
* {@link Bean @Bean} methods declared in {@code @Configuration} classes have
* their corresponding bean definitions registered before any other
* {@link BeanFactoryPostProcessor} executes.
* {@code BeanFactoryPostProcessor} executes.
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Phillip Webb
* @author Sam Brannen
* @since 3.0
*/
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
@@ -376,21 +377,30 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
AnnotationMetadata annotationMetadata = null;
MethodMetadata methodMetadata = null;
if (beanDef instanceof AnnotatedBeanDefinition) {
methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDef;
annotationMetadata = annotatedBeanDefinition.getMetadata();
methodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata();
}
if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
// Configuration class (full or lite) or a configuration-derived @Bean method
// -> resolve bean class at this point...
// -> eagerly resolve bean class at this point, unless it's a 'lite' configuration
// or component class without @Bean methods.
AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
if (!abd.hasBeanClass()) {
try {
abd.resolveBeanClass(this.beanClassLoader);
}
catch (Throwable ex) {
throw new IllegalStateException(
"Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
boolean liteConfigurationCandidateWithoutBeanMethods =
(ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) &&
annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata));
if (!liteConfigurationCandidateWithoutBeanMethods) {
try {
abd.resolveBeanClass(this.beanClassLoader);
}
catch (Throwable ex) {
throw new IllegalStateException(
"Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,6 +46,7 @@ import org.springframework.stereotype.Component;
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.1
*/
abstract class ConfigurationClassUtils {
@@ -162,6 +163,10 @@ abstract class ConfigurationClassUtils {
}
// Finally, let's look for @Bean methods...
return hasBeanMethods(metadata);
}
static boolean hasBeanMethods(AnnotationMetadata metadata) {
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
}
@@ -43,6 +43,15 @@ import org.springframework.util.Assert;
*/
public class FullyQualifiedAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator {
/**
* A convenient constant for a default {@code FullyQualifiedAnnotationBeanNameGenerator}
* instance, as used for configuration-level import purposes.
* @since 5.2.11
*/
public static final FullyQualifiedAnnotationBeanNameGenerator INSTANCE =
new FullyQualifiedAnnotationBeanNameGenerator();
@Override
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -81,7 +81,7 @@ public class SourceFilteringListener implements GenericApplicationListener, Smar
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return supportsEventType(ResolvableType.forType(eventType));
return supportsEventType(ResolvableType.forClass(eventType));
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,13 +48,13 @@ public final class CandidateComponentsIndexLoader {
public static final String COMPONENTS_RESOURCE_LOCATION = "META-INF/spring.components";
/**
* System property that instructs Spring to ignore the index, i.e.
* System property that instructs Spring to ignore the components index, i.e.
* to always return {@code null} from {@link #loadIndex(ClassLoader)}.
* <p>The default is "false", allowing for regular use of the index. Switching this
* flag to {@code true} fulfills a corner case scenario when an index is partially
* available for some libraries (or use cases) but couldn't be built for the whole
* application. In this case, the application context fallbacks to a regular
* classpath arrangement (i.e. as no index was present at all).
* classpath arrangement (i.e. as though no index were present at all).
*/
public static final String IGNORE_INDEX = "spring.index.ignore";
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -856,8 +856,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// Register a default embedded value resolver if no BeanFactoryPostProcessor
// (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,14 +36,17 @@ import java.lang.annotation.Target;
*
* <p>For ISO-based formatting, set the {@link #iso} attribute to be the desired {@link ISO} format,
* such as {@link ISO#DATE}. For custom formatting, set the {@link #pattern} attribute to be the
* DateTime pattern, such as {@code yyyy/MM/dd hh:mm:ss a}.
* DateTime pattern, such as {@code "yyyy/MM/dd hh:mm:ss a"}.
*
* <p>Each attribute is mutually exclusive, so only set one attribute per annotation instance
* (the one most convenient one for your formatting needs).
* When the pattern attribute is specified, it takes precedence over both the style and ISO attribute.
* When the {@link #iso} attribute is specified, it takes precedence over the style attribute.
* When no annotation attributes are specified, the default format applied is style-based
* with a style code of 'SS' (short date, short time).
* (the one most convenient for your formatting needs).
*
* <ul>
* <li>When the pattern attribute is specified, it takes precedence over both the style and ISO attribute.</li>
* <li>When the {@link #iso} attribute is specified, it takes precedence over the style attribute.</li>
* <li>When no annotation attributes are specified, the default format applied is style-based
* with a style code of 'SS' (short date, short time).</li>
* </ul>
*
* @author Keith Donald
* @author Juergen Hoeller
@@ -106,7 +109,6 @@ public @interface DateTimeFormat {
/**
* The most common ISO DateTime Format {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXX},
* e.g. "2000-10-31T01:30:00.000-05:00".
* <p>This is the default if no annotation value is specified.
*/
DATE_TIME,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -66,7 +66,8 @@ public class ConcurrentModel extends ConcurrentHashMap<String, Object> implement
@Override
public Object put(String key, Object value) {
@Nullable
public Object put(String key, @Nullable Object value) {
if (value != null) {
return super.put(key, value);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,10 @@
package org.springframework.validation.beanvalidation;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -291,12 +293,17 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
configureParameterNameProvider(this.parameterNameDiscoverer, configuration);
}
List<InputStream> mappingStreams = null;
if (this.mappingLocations != null) {
mappingStreams = new ArrayList<>(this.mappingLocations.length);
for (Resource location : this.mappingLocations) {
try {
configuration.addMapping(location.getInputStream());
InputStream stream = location.getInputStream();
mappingStreams.add(stream);
configuration.addMapping(stream);
}
catch (IOException ex) {
closeMappingStreams(mappingStreams);
throw new IllegalStateException("Cannot read mapping resource: " + location);
}
}
@@ -307,8 +314,13 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
// Allow for custom post-processing before we actually build the ValidatorFactory.
postProcessConfiguration(configuration);
this.validatorFactory = configuration.buildValidatorFactory();
setTargetValidator(this.validatorFactory.getValidator());
try {
this.validatorFactory = configuration.buildValidatorFactory();
setTargetValidator(this.validatorFactory.getValidator());
}
finally {
closeMappingStreams(mappingStreams);
}
}
private void configureParameterNameProvider(ParameterNameDiscoverer discoverer, Configuration<?> configuration) {
@@ -329,6 +341,18 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
});
}
private void closeMappingStreams(@Nullable List<InputStream> mappingStreams){
if (!CollectionUtils.isEmpty(mappingStreams)) {
for (InputStream stream : mappingStreams) {
try {
stream.close();
}
catch (IOException ignored) {
}
}
}
}
/**
* Post-process the given Bean Validation configuration,
* adding to or overriding any of its settings.
@@ -397,7 +421,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
return super.unwrap(type);
}
catch (ValidationException ex) {
// ignore - we'll try ValidatorFactory unwrapping next
// Ignore - we'll try ValidatorFactory unwrapping next
}
}
if (this.validatorFactory != null) {
@@ -405,7 +429,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
return this.validatorFactory.unwrap(type);
}
catch (ValidationException ex) {
// ignore if just being asked for ValidatorFactory
// Ignore if just being asked for ValidatorFactory
if (ValidatorFactory.class == type) {
return (T) this.validatorFactory;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -179,7 +179,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
}
}
else {
// got no BindingResult - can only do standard rejectValue call
// Got no BindingResult - can only do standard rejectValue call
// with automatic extraction of the current field value
errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());
}
@@ -386,7 +386,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
return (type != null ? this.targetValidator.unwrap(type) : (T) this.targetValidator);
}
catch (ValidationException ex) {
// ignore if just being asked for plain Validator
// Ignore if just being asked for plain JSR-303 Validator
if (javax.validation.Validator.class == type) {
return (T) this.targetValidator;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,24 +36,26 @@ import static org.assertj.core.api.Assertions.assertThat;
public class AtAspectJAfterThrowingTests {
@Test
public void testAccessThrowable() throws Exception {
public void testAccessThrowable() {
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass());
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass());
ITestBean bean = (ITestBean) ctx.getBean("testBean");
ExceptionHandlingAspect aspect = (ExceptionHandlingAspect) ctx.getBean("aspect");
assertThat(AopUtils.isAopProxy(bean)).isTrue();
IOException exceptionThrown = null;
try {
bean.unreliableFileOperation();
}
catch (IOException e) {
//
catch (IOException ex) {
exceptionThrown = ex;
}
assertThat(aspect.handled).isEqualTo(1);
assertThat(aspect.lastException).isNotNull();
assertThat(aspect.lastException).isSameAs(exceptionThrown);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class AtAspectJAnnotationBindingTests {
private AnnotatedTestBean testBean;
private ClassPathXmlApplicationContext ctx;
@@ -70,8 +71,7 @@ public class AtAspectJAnnotationBindingTests {
class AtAspectJAnnotationBindingTestAspect {
@Around("execution(* *(..)) && @annotation(testAnn)")
public Object doWithAnnotation(ProceedingJoinPoint pjp, TestAnnotation testAnn)
throws Throwable {
public Object doWithAnnotation(ProceedingJoinPoint pjp, TestAnnotation testAnn) throws Throwable {
String annValue = testAnn.value();
Object result = pjp.proceed();
return (result instanceof String ? annValue + " " + result : result);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +16,23 @@
package org.springframework.context.annotation;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
/**
* @author Andy Wilkinson
* @author Liu Dongmiao
*/
public class AggressiveFactoryBeanInstantiationTests {
@@ -49,17 +58,66 @@ public class AggressiveFactoryBeanInstantiationTests {
}
}
@Test
public void checkLinkageError() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(BeanMethodConfigurationWithExceptionInInitializer.class);
context.refresh();
fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException ex) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(baos);
ex.printStackTrace(pw);
pw.flush();
String stackTrace = baos.toString();
assertThat(stackTrace.contains(".<clinit>")).isTrue();
assertThat(stackTrace.contains("java.lang.NoClassDefFoundError")).isFalse();
}
}
@Configuration
static class BeanMethodConfiguration {
@Bean
public String foo() {
return "foo";
}
@Bean
public AutowiredBean autowiredBean() {
return new AutowiredBean();
}
@Bean
@DependsOn("autowiredBean")
public SimpleFactoryBean simpleFactoryBean(ApplicationContext applicationContext) {
return new SimpleFactoryBean(applicationContext);
}
}
@Configuration
static class BeanMethodConfigurationWithExceptionInInitializer extends BeanMethodConfiguration {
@Bean
@DependsOn("autowiredBean")
@Override
public SimpleFactoryBean simpleFactoryBean(ApplicationContext applicationContext) {
new ExceptionInInitializer();
return new SimpleFactoryBean(applicationContext);
}
}
static class AutowiredBean {
@Autowired
String foo;
}
static class SimpleFactoryBean implements FactoryBean<Object> {
public SimpleFactoryBean(ApplicationContext applicationContext) {
@@ -76,4 +134,14 @@ public class AggressiveFactoryBeanInstantiationTests {
}
}
static class ExceptionInInitializer {
private static final int ERROR = callInClinit();
private static int callInClinit() {
throw new UnsupportedOperationException();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -105,7 +104,9 @@ public class ConfigurationClassPostProcessorTests {
Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isSameAs(foo);
assertThat(Arrays.asList(beanFactory.getDependentBeans("foo")).contains("bar")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("foo"), "bar")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "foo")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "bar")).isTrue();
}
@Test
@@ -117,7 +118,9 @@ public class ConfigurationClassPostProcessorTests {
Foo foo = beanFactory.getBean("foo", Foo.class);
Bar bar = beanFactory.getBean("bar", Bar.class);
assertThat(bar.foo).isSameAs(foo);
assertThat(Arrays.asList(beanFactory.getDependentBeans("foo")).contains("bar")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("foo"), "bar")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "foo")).isTrue();
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "bar")).isTrue();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@ public class EnableLoadTimeWeavingTests {
@Test
public void control() {
GenericXmlApplicationContext ctx =
new GenericXmlApplicationContext(getClass(), "EnableLoadTimeWeavingTests-context.xml");
new GenericXmlApplicationContext(getClass(), "EnableLoadTimeWeavingTests-context.xml");
ctx.getBean("loadTimeWeaver", LoadTimeWeaver.class);
}
@@ -73,9 +73,11 @@ public class EnableLoadTimeWeavingTests {
verify(loadTimeWeaver).addTransformer(isA(ClassFileTransformer.class));
}
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.DISABLED)
static class EnableLTWConfig_withAjWeavingDisabled implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return mock(LoadTimeWeaver.class);
@@ -85,6 +87,7 @@ public class EnableLoadTimeWeavingTests {
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.AUTODETECT)
static class EnableLTWConfig_withAjWeavingAutodetect implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return mock(LoadTimeWeaver.class);
@@ -94,9 +97,11 @@ public class EnableLoadTimeWeavingTests {
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.ENABLED)
static class EnableLTWConfig_withAjWeavingEnabled implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return mock(LoadTimeWeaver.class);
}
}
}
@@ -307,6 +307,23 @@ public class ConfigurationClassProcessingTests {
assertThat(tb.getLawyer()).isEqualTo(ctx.getBean(NestedTestBean.class));
}
@Test // gh-26019
public void autowiringWithDynamicPrototypeBeanClass() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
ConfigWithDynamicPrototype.class, PrototypeDependency.class);
PrototypeInterface p1 = ctx.getBean(PrototypeInterface.class, 1);
assertThat(p1).isInstanceOf(PrototypeOne.class);
assertThat(((PrototypeOne) p1).prototypeDependency).isNotNull();
PrototypeInterface p2 = ctx.getBean(PrototypeInterface.class, 2);
assertThat(p2).isInstanceOf(PrototypeTwo.class);
PrototypeInterface p3 = ctx.getBean(PrototypeInterface.class, 1);
assertThat(p3).isInstanceOf(PrototypeOne.class);
assertThat(((PrototypeOne) p3).prototypeDependency).isNotNull();
}
/**
* Creates a new {@link BeanFactory}, populates it with a {@link BeanDefinition}
@@ -632,4 +649,42 @@ public class ConfigurationClassProcessingTests {
}
}
static class PrototypeDependency {
}
interface PrototypeInterface {
}
static class PrototypeOne extends AbstractPrototype {
@Autowired
PrototypeDependency prototypeDependency;
}
static class PrototypeTwo extends AbstractPrototype {
// no autowired dependency here, in contrast to above
}
static class AbstractPrototype implements PrototypeInterface {
}
@Configuration
static class ConfigWithDynamicPrototype {
@Bean
@Scope(value = "prototype")
public PrototypeInterface getDemoBean( int i) {
switch ( i) {
case 1: return new PrototypeOne();
case 2:
default:
return new PrototypeTwo();
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -66,10 +66,10 @@ internal fun isSuspendingFunction(method: Method) = method.kotlinFunction!!.isSu
* @since 5.2
*/
@Suppress("UNCHECKED_CAST")
internal fun invokeSuspendingFunction(method: Method, bean: Any, vararg args: Any?): Publisher<*> {
internal fun invokeSuspendingFunction(method: Method, target: Any, vararg args: Any?): Publisher<*> {
val function = method.kotlinFunction!!
val mono = mono(Dispatchers.Unconfined) {
function.callSuspend(bean, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it }
function.callSuspend(target, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it }
}.onErrorMap(InvocationTargetException::class.java) { it.targetException }
return if (function.returnType.classifier == Flow::class) {
mono.flatMapMany { (it as Flow<Any>).asFlux() }
+2 -2
View File
@@ -9,7 +9,7 @@ description = "Spring Core"
apply plugin: "kotlin"
// spring-core includes asm and repackages cglib, inlining both into the spring-core jar.
// cglib itself depends on asm and is therefore further transformed by the JarJar task to
// cglib itself depends on asm and is therefore further transformed by the ShadowJar task to
// depend on org.springframework.asm; this avoids including two different copies of asm.
def cglibVersion = "3.3.0"
def objenesisVersion = "3.1"
@@ -73,7 +73,7 @@ dependencies {
jar {
reproducibleFileOrder = true
preserveFileTimestamps = false // maybe not necessary here, but good for reproducibility
preserveFileTimestamps = false // maybe not necessary here, but good for reproducibility
// Inline repackaged cglib classes directly into spring-core jar
dependsOn cglibRepackJar
@@ -76,7 +76,7 @@ public abstract class AnnotationVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.av = annotationVisitor;
}
@@ -74,7 +74,7 @@ public abstract class ClassVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.cv = classVisitor;
}
@@ -72,7 +72,7 @@ public abstract class FieldVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.fv = fieldVisitor;
}
@@ -88,7 +88,7 @@ public abstract class MethodVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.mv = methodVisitor;
}
@@ -74,7 +74,7 @@ public abstract class ModuleVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.mv = moduleVisitor;
}
@@ -80,7 +80,7 @@ public abstract class RecordComponentVisitor {
&& api != Opcodes.ASM8_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
// SPRING PATCH: no preview mode check for ASM 8 experimental
// SPRING PATCH: no preview mode check for ASM experimental
this.api = api;
this.delegate = recordComponentVisitor;
}
@@ -1,6 +1,6 @@
/**
* Spring's repackaging of
* <a href="https://gitlab.ow2.org/asm/asm">ASM 7.0</a>
* <a href="https://gitlab.ow2.org/asm/asm">ASM 7.x</a>
* (with Spring-specific patches; for internal use only).
*
* <p>This repackaging technique avoids any potential conflicts with
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,8 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.IntFunction;
@@ -31,10 +31,11 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Collector implementations that provide various reduction operations for
* {@link Collector} implementations that provide various reduction operations for
* {@link MergedAnnotation} instances.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2
*/
public abstract class MergedAnnotationCollectors {
@@ -52,13 +53,16 @@ public abstract class MergedAnnotationCollectors {
* Create a new {@link Collector} that accumulates merged annotations to a
* {@link LinkedHashSet} containing {@linkplain MergedAnnotation#synthesize()
* synthesized} versions.
* <p>The collector returned by this method is effectively equivalent to
* {@code Collectors.mapping(MergedAnnotation::synthesize, Collectors.toCollection(LinkedHashSet::new))}
* but avoids the creation of a composite collector.
* @param <A> the annotation type
* @return a {@link Collector} which collects and synthesizes the
* annotations into a {@link Set}
*/
public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, Set<A>> toAnnotationSet() {
return Collector.of(ArrayList<A>::new, (list, annotation) -> list.add(annotation.synthesize()),
MergedAnnotationCollectors::addAll, LinkedHashSet::new);
return Collector.of(LinkedHashSet::new, (set, annotation) -> set.add(annotation.synthesize()),
MergedAnnotationCollectors::combiner);
}
/**
@@ -90,14 +94,14 @@ public abstract class MergedAnnotationCollectors {
IntFunction<R[]> generator) {
return Collector.of(ArrayList::new, (list, annotation) -> list.add(annotation.synthesize()),
MergedAnnotationCollectors::addAll, list -> list.toArray(generator.apply(list.size())));
MergedAnnotationCollectors::combiner, list -> list.toArray(generator.apply(list.size())));
}
/**
* Create a new {@link Collector} that accumulates merged annotations to an
* Create a new {@link Collector} that accumulates merged annotations to a
* {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
* added} from each merged annotation
* {@link MergedAnnotation#asMap(Adapt...) as a map}.
* {@linkplain MergedAnnotation#asMap(Adapt...) as a map}.
* @param <A> the annotation type
* @param adaptations the adaptations that should be applied to the annotation values
* @return a {@link Collector} which collects and synthesizes the
@@ -111,13 +115,13 @@ public abstract class MergedAnnotationCollectors {
}
/**
* Create a new {@link Collector} that accumulates merged annotations to an
* Create a new {@link Collector} that accumulates merged annotations to a
* {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
* added} from each merged annotation
* {@link MergedAnnotation#asMap(Adapt...) as a map}.
* {@linkplain MergedAnnotation#asMap(Adapt...) as a map}.
* @param <A> the annotation type
* @param adaptations the adaptations that should be applied to the annotation values
* @param finisher the finisher function for the new {@link MultiValueMap}
* @param adaptations the adaptations that should be applied to the annotation values
* @return a {@link Collector} which collects and synthesizes the
* annotations into a {@link LinkedMultiValueMap}
* @see #toMultiValueMap(MergedAnnotation.Adapt...)
@@ -130,7 +134,7 @@ public abstract class MergedAnnotationCollectors {
IDENTITY_FINISH_CHARACTERISTICS : NO_CHARACTERISTICS);
return Collector.of(LinkedMultiValueMap::new,
(map, annotation) -> annotation.asMap(adaptations).forEach(map::add),
MergedAnnotationCollectors::merge, finisher, characteristics);
MergedAnnotationCollectors::combiner, finisher, characteristics);
}
@@ -138,13 +142,22 @@ public abstract class MergedAnnotationCollectors {
return instance == candidate;
}
private static <E, L extends List<E>> L addAll(L list, L additions) {
list.addAll(additions);
return list;
/**
* {@link Collector#combiner() Combiner} for collections.
* <p>This method is only invoked if the {@link java.util.stream.Stream} is
* processed in {@linkplain java.util.stream.Stream#parallel() parallel}.
*/
private static <E, C extends Collection<E>> C combiner(C collection, C additions) {
collection.addAll(additions);
return collection;
}
private static <K, V> MultiValueMap<K, V> merge(MultiValueMap<K, V> map,
MultiValueMap<K, V> additions) {
/**
* {@link Collector#combiner() Combiner} for multi-value maps.
* <p>This method is only invoked if the {@link java.util.stream.Stream} is
* processed in {@linkplain java.util.stream.Stream#parallel() parallel}.
*/
private static <K, V> MultiValueMap<K, V> combiner(MultiValueMap<K, V> map, MultiValueMap<K, V> additions) {
map.addAll(additions);
return map;
}
@@ -157,16 +157,20 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
int startIndex = buffer.readPosition();
int length = (endIndex - startIndex + 1);
DataBuffer slice = buffer.retainedSlice(startIndex, length);
if (this.stripDelimiter) {
slice.writePosition(slice.writePosition() - matcher.delimiter().length);
}
result = (result != null ? result : new ArrayList<>());
if (chunks.isEmpty()) {
if (this.stripDelimiter) {
slice.writePosition(slice.writePosition() - matcher.delimiter().length);
}
result.add(slice);
}
else {
chunks.add(slice);
result.add(buffer.factory().join(chunks));
DataBuffer joined = buffer.factory().join(chunks);
if (this.stripDelimiter) {
joined.writePosition(joined.writePosition() - matcher.delimiter().length);
}
result.add(joined);
chunks.clear();
}
buffer.readPosition(endIndex + 1);
@@ -19,14 +19,15 @@ package org.springframework.core.convert.support;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CopyOnWriteArraySet;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.ResolvableType;
@@ -499,9 +500,9 @@ public class GenericConversionService implements ConfigurableConversionService {
*/
private static class Converters {
private final Set<GenericConverter> globalConverters = new LinkedHashSet<>();
private final Set<GenericConverter> globalConverters = new CopyOnWriteArraySet<>();
private final Map<ConvertiblePair, ConvertersForPair> converters = new LinkedHashMap<>(36);
private final Map<ConvertiblePair, ConvertersForPair> converters = new ConcurrentHashMap<>(256);
public void add(GenericConverter converter) {
Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
@@ -512,8 +513,7 @@ public class GenericConversionService implements ConfigurableConversionService {
}
else {
for (ConvertiblePair convertiblePair : convertibleTypes) {
ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair);
convertersForPair.add(converter);
getMatchableConverters(convertiblePair).add(converter);
}
}
}
@@ -651,7 +651,7 @@ public class GenericConversionService implements ConfigurableConversionService {
*/
private static class ConvertersForPair {
private final LinkedList<GenericConverter> converters = new LinkedList<>();
private final Deque<GenericConverter> converters = new ConcurrentLinkedDeque<>();
public void add(GenericConverter converter) {
this.converters.addFirst(converter);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -148,14 +148,21 @@ public class ClassPathResource extends AbstractFileResolvingResource {
*/
@Nullable
protected URL resolveURL() {
if (this.clazz != null) {
return this.clazz.getResource(this.path);
try {
if (this.clazz != null) {
return this.clazz.getResource(this.path);
}
else if (this.classLoader != null) {
return this.classLoader.getResource(this.path);
}
else {
return ClassLoader.getSystemResource(this.path);
}
}
else if (this.classLoader != null) {
return this.classLoader.getResource(this.path);
}
else {
return ClassLoader.getSystemResource(this.path);
catch (IllegalArgumentException ex) {
// Should not happen according to the JDK's contract:
// see https://github.com/openjdk/jdk/pull/2662
return null;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,11 +54,8 @@ public class LimitedDataBufferList extends ArrayList<DataBuffer> {
@Override
public boolean add(DataBuffer buffer) {
boolean result = super.add(buffer);
if (result) {
updateCount(buffer.readableByteCount());
}
return result;
updateCount(buffer.readableByteCount());
return super.add(buffer);
}
@Override
@@ -432,6 +432,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
// Possibly "c:" drive prefix on Windows, to be upper-cased for proper duplicate detection
filePath = StringUtils.capitalize(filePath);
}
// # can appear in directories/filenames, java.net.URL should not treat it as a fragment
filePath = StringUtils.replace(filePath, "#", "%23");
// Build URL that points to the root of the jar file
UrlResource jarResource = new UrlResource(ResourceUtils.JAR_URL_PREFIX +
ResourceUtils.FILE_URL_PREFIX + filePath + ResourceUtils.JAR_URL_SEPARATOR);
// Potentially overlapping with URLClassLoader.getURLs() result above!
@@ -1,6 +1,6 @@
/**
* Spring's repackaging of
* <a href="http://objenesis.org">Objenesis 3.0</a>
* <a href="http://objenesis.org">Objenesis 3.1</a>
* (with SpringObjenesis entry point; for internal use only).
*
* <p>This repackaging technique avoids any potential conflicts with
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -299,7 +299,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
@Override
@Nullable
public V remove(Object key) {
public V remove(@Nullable Object key) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
@Override
@Nullable
@@ -316,7 +316,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
}
@Override
public boolean remove(Object key, final Object value) {
public boolean remove(@Nullable Object key, final @Nullable Object value) {
Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
@Override
protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
@@ -333,7 +333,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
}
@Override
public boolean replace(K key, final V oldValue, final V newValue) {
public boolean replace(@Nullable K key, final @Nullable V oldValue, final @Nullable V newValue) {
Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
@Override
protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
@@ -349,7 +349,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
@Override
@Nullable
public V replace(K key, final V value) {
public V replace(@Nullable K key, final @Nullable V value) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
@Override
@Nullable
@@ -474,7 +474,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
* The total number of references contained in this segment. This includes chained
* references and references that have been garbage collected but not purged.
*/
private final AtomicInteger count = new AtomicInteger(0);
private final AtomicInteger count = new AtomicInteger();
/**
* The threshold when resizing of the references should occur. When {@code count}
@@ -16,6 +16,8 @@
package org.springframework.util;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.BitSet;
@@ -104,7 +106,7 @@ public class MimeType implements Comparable<MimeType>, Serializable {
private final Map<String, String> parameters;
@Nullable
private Charset resolvedCharset;
private transient Charset resolvedCharset;
@Nullable
private volatile String toStringValue;
@@ -184,9 +186,9 @@ public class MimeType implements Comparable<MimeType>, Serializable {
this.subtype = subtype.toLowerCase(Locale.ENGLISH);
if (!CollectionUtils.isEmpty(parameters)) {
Map<String, String> map = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ENGLISH);
parameters.forEach((attribute, value) -> {
checkParameters(attribute, value);
map.put(attribute, value);
parameters.forEach((parameter, value) -> {
checkParameters(parameter, value);
map.put(parameter, value);
});
this.parameters = Collections.unmodifiableMap(map);
}
@@ -210,11 +212,11 @@ public class MimeType implements Comparable<MimeType>, Serializable {
}
}
protected void checkParameters(String attribute, String value) {
Assert.hasLength(attribute, "'attribute' must not be empty");
protected void checkParameters(String parameter, String value) {
Assert.hasLength(parameter, "'parameter' must not be empty");
Assert.hasLength(value, "'value' must not be empty");
checkToken(attribute);
if (PARAM_CHARSET.equals(attribute)) {
checkToken(parameter);
if (PARAM_CHARSET.equals(parameter)) {
if (this.resolvedCharset == null) {
this.resolvedCharset = Charset.forName(unquote(value));
}
@@ -569,6 +571,17 @@ public class MimeType implements Comparable<MimeType>, Serializable {
return 0;
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// Rely on default serialization, just initialize state after deserialization.
ois.defaultReadObject();
// Initialize transient fields.
String charsetName = getParameter(PARAM_CHARSET);
if (charsetName != null) {
this.resolvedCharset = Charset.forName(unquote(charsetName));
}
}
/**
* Parse the given String value into a {@code MimeType} object,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,9 +28,11 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
/**
* Utility class for working with Strings that have placeholder values in them. A placeholder takes the form
* {@code ${name}}. Using {@code PropertyPlaceholderHelper} these placeholders can be substituted for
* user-supplied values. <p> Values for substitution can be supplied using a {@link Properties} instance or
* Utility class for working with Strings that have placeholder values in them.
* A placeholder takes the form {@code ${name}}. Using {@code PropertyPlaceholderHelper}
* these placeholders can be substituted for user-supplied values.
*
* <p>Values for substitution can be supplied using a {@link Properties} instance or
* using a {@link PlaceholderResolver}.
*
* @author Juergen Hoeller
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -384,7 +384,7 @@ public abstract class ReflectionUtils {
* @throws IllegalStateException if introspection fails
*/
public static Method[] getAllDeclaredMethods(Class<?> leafClass) {
final List<Method> methods = new ArrayList<>(32);
final List<Method> methods = new ArrayList<>(20);
doWithMethods(leafClass, methods::add);
return methods.toArray(EMPTY_METHOD_ARRAY);
}
@@ -410,7 +410,7 @@ public abstract class ReflectionUtils {
* @since 5.2
*/
public static Method[] getUniqueDeclaredMethods(Class<?> leafClass, @Nullable MethodFilter mf) {
final List<Method> methods = new ArrayList<>(32);
final List<Method> methods = new ArrayList<>(20);
doWithMethods(leafClass, method -> {
boolean knownSignature = false;
Method methodBeingOverriddenWithCovariantReturnType = null;
@@ -505,12 +505,15 @@ public abstract class ReflectionUtils {
* @see java.lang.Object#equals(Object)
*/
public static boolean isEqualsMethod(@Nullable Method method) {
if (method == null || !method.getName().equals("equals")) {
if (method == null) {
return false;
}
if (method.getParameterCount() != 1) {
return false;
}
if (!method.getName().equals("equals")) {
return false;
}
return method.getParameterTypes()[0] == Object.class;
}
@@ -519,7 +522,7 @@ public abstract class ReflectionUtils {
* @see java.lang.Object#hashCode()
*/
public static boolean isHashCodeMethod(@Nullable Method method) {
return (method != null && method.getName().equals("hashCode") && method.getParameterCount() == 0);
return method != null && method.getParameterCount() == 0 && method.getName().equals("hashCode");
}
/**
@@ -527,7 +530,7 @@ public abstract class ReflectionUtils {
* @see java.lang.Object#toString()
*/
public static boolean isToStringMethod(@Nullable Method method) {
return (method != null && method.getName().equals("toString") && method.getParameterCount() == 0);
return (method != null && method.getParameterCount() == 0 && method.getName().equals("toString"));
}
/**
@@ -622,6 +625,7 @@ public abstract class ReflectionUtils {
* <p>Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}.
* @param field the field to set
* @param target the target object on which to set the field
* (or {@code null} for a static field)
* @param value the value to set (may be {@code null})
*/
public static void setField(Field field, @Nullable Object target, @Nullable Object value) {
@@ -641,6 +645,7 @@ public abstract class ReflectionUtils {
* <p>Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}.
* @param field the field to get
* @param target the target object from which to get the field
* (or {@code null} for a static field)
* @return the field's current value
*/
@Nullable
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1341,8 +1341,8 @@ public abstract class StringUtils {
}
StringJoiner sj = new StringJoiner(delim);
for (Object o : arr) {
sj.add(String.valueOf(o));
for (Object elem : arr) {
sj.add(String.valueOf(elem));
}
return sj.toString();
}
@@ -125,10 +125,10 @@ class StringDecoderTests extends AbstractDecoderTests<StringDecoder> {
);
testDecode(input, String.class, step -> step
.expectNext("")
.expectNext("").as("1st")
.expectNext("abc")
.expectNext("defghi")
.expectNext("")
.expectNext("").as("2nd")
.expectNext("jklmno")
.expectNext("pqr")
.expectNext("stuvwxyz")
@@ -136,6 +136,21 @@ class StringDecoderTests extends AbstractDecoderTests<StringDecoder> {
.verify());
}
@Test
void decodeNewlinesAcrossBuffers() {
Flux<DataBuffer> input = Flux.just(
stringBuffer("\r"),
stringBuffer("\n"),
stringBuffer("xyz")
);
testDecode(input, String.class, step -> step
.expectNext("")
.expectNext("xyz")
.expectComplete()
.verify());
}
@Test
void maxInMemoryLimit() {
Flux<DataBuffer> input = Flux.just(
@@ -35,6 +35,8 @@ import java.util.List;
import java.util.concurrent.CountDownLatch;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import org.junit.jupiter.api.Test;
import org.mockito.stubbing.Answer;
import org.reactivestreams.Subscription;
import reactor.core.publisher.BaseSubscriber;
@@ -834,6 +836,22 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
.verifyError(DataBufferLimitException.class);
}
@Test // gh-26060
void joinWithLimitDoesNotOverRelease() {
NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT);
byte[] bytes = "foo-bar-baz".getBytes(StandardCharsets.UTF_8);
NettyDataBuffer buffer = bufferFactory.allocateBuffer(bytes.length);
buffer.getNativeBuffer().retain(); // should be at 2 now
buffer.write(bytes);
Mono<DataBuffer> result = DataBufferUtils.join(Flux.just(buffer), 8);
StepVerifier.create(result).verifyError(DataBufferLimitException.class);
assertThat(buffer.getNativeBuffer().refCnt()).isEqualTo(1);
buffer.release();
}
@ParameterizedDataBufferAllocatingTest
void joinErrors(String displayName, DataBufferFactory bufferFactory) {
super.bufferFactory = bufferFactory;
@@ -17,9 +17,11 @@ package org.springframework.core.io.buffer;
import java.nio.charset.StandardCharsets;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Unit tests for {@link LimitedDataBufferList}.
* @author Rossen Stoyanchev
@@ -32,8 +34,10 @@ public class LimitedDataBufferListTests {
@Test
void limitEnforced() {
Assertions.assertThatThrownBy(() -> new LimitedDataBufferList(5).add(toDataBuffer("123456")))
.isInstanceOf(DataBufferLimitException.class);
LimitedDataBufferList list = new LimitedDataBufferList(5);
assertThatThrownBy(() -> list.add(toDataBuffer("123456"))).isInstanceOf(DataBufferLimitException.class);
assertThat(list).isEmpty();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.testfixture.io.SerializationTestUtils;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
@@ -267,13 +268,13 @@ class MimeTypeTests {
assertThat(mimeType.getParameter("attr")).isEqualTo("'v>alue'");
}
@Test // SPR-16630
@Test // SPR-16630
void parseMimeTypeWithSpacesAroundEquals() {
MimeType mimeType = MimeTypeUtils.parseMimeType("multipart/x-mixed-replace;boundary = --myboundary");
assertThat(mimeType.getParameter("boundary")).isEqualTo("--myboundary");
}
@Test // SPR-16630
@Test // SPR-16630
void parseMimeTypeWithSpacesAroundEqualsAndQuotedValue() {
MimeType mimeType = MimeTypeUtils.parseMimeType("text/plain; foo = \" bar \" ");
assertThat(mimeType.getParameter("foo")).isEqualTo("\" bar \"");
@@ -303,14 +304,14 @@ class MimeTypeTests {
assertThat(mimeTypes.size()).as("Invalid amount of mime types").isEqualTo(0);
}
@Test // gh-23241
@Test // gh-23241
void parseMimeTypesWithTrailingComma() {
List<MimeType> mimeTypes = MimeTypeUtils.parseMimeTypes("text/plain, text/html,");
assertThat(mimeTypes).as("No mime types returned").isNotNull();
assertThat(mimeTypes.size()).as("Incorrect number of mime types").isEqualTo(2);
}
@Test // SPR-17459
@Test // SPR-17459
void parseMimeTypesWithQuotedParameters() {
testWithQuotedParameters("foo/bar;param=\",\"");
testWithQuotedParameters("foo/bar;param=\"s,a,\"");
@@ -323,8 +324,9 @@ class MimeTypeTests {
private void testWithQuotedParameters(String... mimeTypes) {
String s = String.join(",", mimeTypes);
List<MimeType> actual = MimeTypeUtils.parseMimeTypes(s);
assertThat(actual.size()).isEqualTo(mimeTypes.length);
for (int i=0; i < mimeTypes.length; i++) {
for (int i = 0; i < mimeTypes.length; i++) {
assertThat(actual.get(i).toString()).isEqualTo(mimeTypes[i]);
}
}
@@ -351,6 +353,7 @@ class MimeTypeTests {
List<MimeType> result = new ArrayList<>(expected);
Random rnd = new Random();
// shuffle & sort 10 times
for (int i = 0; i < 10; i++) {
Collections.shuffle(result, rnd);
@@ -380,11 +383,7 @@ class MimeTypeTests {
assertThat(m2.compareTo(m1) != 0).as("Invalid comparison result").isTrue();
}
/**
* SPR-13157
* @since 4.2
*/
@Test
@Test // SPR-13157
void equalsIsCaseInsensitiveForCharsets() {
MimeType m1 = new MimeType("text", "plain", singletonMap("charset", "UTF-8"));
MimeType m2 = new MimeType("text", "plain", singletonMap("charset", "utf-8"));
@@ -394,4 +393,12 @@ class MimeTypeTests {
assertThat(m2.compareTo(m1)).isEqualTo(0);
}
@Test // gh-26127
void serialize() throws Exception {
MimeType original = new MimeType("text", "plain", StandardCharsets.UTF_8);
MimeType deserialized = (MimeType) SerializationTestUtils.serializeAndDeserialize(original);
assertThat(deserialized).isEqualTo(original);
assertThat(original).isEqualTo(deserialized);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,10 +54,9 @@ public abstract class AstUtils {
}
else {
if (targetType != null) {
int pos = 0;
for (Class<?> clazz : targets) {
if (clazz == targetType) { // put exact matches on the front to be tried first?
specificAccessors.add(pos++, resolver);
specificAccessors.add(resolver);
}
else if (clazz.isAssignableFrom(targetType)) { // put supertype matches at the end of the
// specificAccessor list
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,18 +63,20 @@ import org.springframework.util.StringUtils;
* <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 4.1
*/
public final class SpelCompiler implements Opcodes {
private static final Log logger = LogFactory.getLog(SpelCompiler.class);
private static final int CLASSES_DEFINED_LIMIT = 100;
private static final Log logger = LogFactory.getLog(SpelCompiler.class);
// A compiler is created for each classloader, it manages a child class loader of that
// classloader and the child is used to load the compiled expressions.
private static final Map<ClassLoader, SpelCompiler> compilers = new ConcurrentReferenceHashMap<>();
// The child ClassLoader used to load the compiled expression classes
private ChildClassLoader ccl;
@@ -90,7 +92,7 @@ public final class SpelCompiler implements Opcodes {
/**
* Attempt compilation of the supplied expression. A check is made to see
* if it is compilable before compilation proceeds. The check involves
* visiting all the nodes in the expression Ast and ensuring enough state
* visiting all the nodes in the expression AST and ensuring enough state
* is known about them that bytecode can be generated for them.
* @param expression the expression to compile
* @return an instance of the class implementing the compiled expression,
@@ -125,7 +127,7 @@ public final class SpelCompiler implements Opcodes {
/**
* Generate the class that encapsulates the compiled expression and define it.
* The generated class will be a subtype of CompiledExpression.
* The generated class will be a subtype of CompiledExpression.
* @param expressionToCompile the expression to be compiled
* @return the expression call, or {@code null} if the decision was to opt out of
* compilation during code generation
@@ -150,7 +152,7 @@ public final class SpelCompiler implements Opcodes {
// Create getValue() method
mv = cw.visitMethod(ACC_PUBLIC, "getValue",
"(Ljava/lang/Object;Lorg/springframework/expression/EvaluationContext;)Ljava/lang/Object;", null,
new String[ ]{"org/springframework/expression/EvaluationException"});
new String[] {"org/springframework/expression/EvaluationException"});
mv.visitCode();
CodeFlow cf = new CodeFlow(className, cw);
@@ -187,7 +189,7 @@ public final class SpelCompiler implements Opcodes {
/**
* Load a compiled expression class. Makes sure the classloaders aren't used too much
* because they anchor compiled classes in memory and prevent GC. If you have expressions
* because they anchor compiled classes in memory and prevent GC. If you have expressions
* continually recompiling over time then by replacing the classloader periodically
* at least some of the older variants can be garbage collected.
* @param name the name of the class
@@ -202,6 +204,7 @@ public final class SpelCompiler implements Opcodes {
return (Class<? extends CompiledExpression>) this.ccl.defineClass(name, bytes);
}
/**
* Factory method for compiler instances. The returned SpelCompiler will
* attach a class loader as the child of the given class loader and this
@@ -222,10 +225,12 @@ public final class SpelCompiler implements Opcodes {
}
/**
* Request that an attempt is made to compile the specified expression. It may fail if
* components of the expression are not suitable for compilation or the data types
* involved are not suitable for compilation. Used for testing.
* @return true if the expression was successfully compiled
* Request that an attempt is made to compile the specified expression.
* It may fail if components of the expression are not suitable for compilation
* or the data types involved are not suitable for compilation. Used for testing.
* @param expression the expression to compile
* @return {@code true} if the expression was successfully compiled,
* {@code false} otherwise
*/
public static boolean compile(Expression expression) {
return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression());
@@ -256,18 +261,21 @@ public final class SpelCompiler implements Opcodes {
super(NO_URLS, classLoader);
}
int getClassesDefinedCount() {
return this.classesDefinedCount;
}
public Class<?> defineClass(String name, byte[] bytes) {
Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length);
this.classesDefinedCount++;
return clazz;
}
public int getClassesDefinedCount() {
return this.classesDefinedCount;
}
}
/**
* An ASM ClassWriter extension bound to the SpelCompiler's ClassLoader.
*/
private class ExpressionClassWriter extends ClassWriter {
public ExpressionClassWriter() {
@@ -47,7 +47,7 @@ import org.springframework.util.StringUtils;
/**
* A powerful {@link PropertyAccessor} that uses reflection to access properties
* for reading and possibly also for writing.
* for reading and possibly also for writing on a target instance.
*
* <p>A property can be referenced through a public getter method (when being read)
* or a public setter method (when being written), and also as a public field.
@@ -98,8 +98,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
/**
* Create a new property accessor for reading and possibly writing.
* @param allowWrite whether to also allow for write operations
* Create a new property accessor for reading and possibly also writing.
* @param allowWrite whether to allow write operations on a target instance
* @since 4.3.15
* @see #canWrite
*/
@@ -623,8 +623,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
@Override
public String toString() {
return "CacheKey [clazz=" + this.clazz.getName() + ", property=" + this.property + ", " +
this.property + ", targetIsClass=" + this.targetIsClass + "]";
return "PropertyCacheKey [clazz=" + this.clazz.getName() + ", property=" + this.property +
", targetIsClass=" + this.targetIsClass + "]";
}
@Override
@@ -76,7 +76,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*/
public class SpelReproTests extends AbstractExpressionTests {
@Test
public void NPE_SPR5661() {
evaluate("joinThreeStrings('a',null,'c')", "anullc", String.class);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,10 @@
package org.springframework.expression.spel.testresources;
///CLOVER:OFF
public class Person {
private String privateName;
Company company;
public Person(String name) {
@@ -41,4 +42,5 @@ public class Person {
public Company getCompany() {
return company;
}
}

Some files were not shown because too many files have changed in this diff Show More