mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f32e749dc0 | |||
| ccaecab500 | |||
| 67e03105b5 | |||
| 76bc9cf325 | |||
| db52c77cca | |||
| 1e742aae34 | |||
| 20dd585c93 | |||
| 707eb701dc | |||
| 2c97996796 | |||
| 3a068b807b | |||
| f54b19ff90 | |||
| 494d2ab727 | |||
| 627d9cf8be | |||
| dd3a67c7ab | |||
| 85cc229063 | |||
| fa95f12be0 | |||
| 035cc72fc8 | |||
| 29a39b617e | |||
| a1471a9266 | |||
| 21793b4f93 | |||
| 2d255c4d5e | |||
| 10391586d1 | |||
| edadc79835 | |||
| 3783d31c09 | |||
| 87730f76b1 | |||
| 6fae3e150e | |||
| ad44e8ab0a | |||
| aadf96ba92 | |||
| 2baf064d04 | |||
| 6121e2f526 | |||
| 8e33805d29 | |||
| 1f19bb2311 | |||
| 2784410cc6 | |||
| f4ac323409 | |||
| 6db00e63c7 | |||
| ed613e767a | |||
| 65781046cf | |||
| 0ee36095e7 | |||
| 0fc38117df | |||
| 86b8a70ce6 | |||
| 4bf759d872 |
@@ -1,36 +0,0 @@
|
||||
Juergen Hoeller <jhoeller@vmware.com>
|
||||
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@pivotal.io>
|
||||
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@gopivotal.com>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@pivotal.io>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@gopivotal.com>
|
||||
Phillip Webb <pwebb@vmware.com>
|
||||
Phillip Webb <pwebb@vmware.com> <pwebb@pivotal.io>
|
||||
Phillip Webb <pwebb@vmware.com> <pwebb@gopivotal.com>
|
||||
Chris Beams <cbeams@vmware.com>
|
||||
Chris Beams <cbeams@vmware.com> <cbeams@pivotal.io>
|
||||
Chris Beams <cbeams@vmware.com> <cbeams@gopivotal.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@pivotal.io>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@gopivotal.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <poutsma@mac.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@pivotal.io>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@gopivotal.com>
|
||||
Dave Syer <dsyer@vmware.com>
|
||||
Dave Syer <dsyer@vmware.com> <dsyer@pivotal.io>
|
||||
Dave Syer <dsyer@vmware.com> <dsyer@gopivotal.com>
|
||||
Dave Syer <dsyer@vmware.com> <david_syer@hotmail.com>
|
||||
Andy Clement <aclement@vmware.com>
|
||||
Andy Clement <aclement@vmware.com> <aclement@pivotal.io>
|
||||
Andy Clement <aclement@vmware.com> <aclement@gopivotal.com>
|
||||
Andy Clement <aclement@vmware.com> <andrew.clement@gmail.com>
|
||||
Sam Brannen <sbrannen@vmware.com>
|
||||
Sam Brannen <sbrannen@vmware.com> <sbrannen@pivotal.io>
|
||||
Sam Brannen <sbrannen@vmware.com> <sam@sambrannen.com>
|
||||
Simon Basle <sbasle@vmware.com>
|
||||
Simon Baslé <sbasle@vmware.com>
|
||||
<dmitry.katsubo@gmail.com> <dmitry.katsubo@gmai.com>
|
||||
Nick Williams <nicholas@nicholaswilliams.net>
|
||||
@@ -146,7 +146,6 @@ configure(rootProject) {
|
||||
description = "Spring Framework"
|
||||
|
||||
apply plugin: "io.spring.nohttp"
|
||||
apply plugin: 'org.springframework.build.api-diff'
|
||||
|
||||
nohttp {
|
||||
source.exclude "**/test-output/**"
|
||||
|
||||
@@ -22,21 +22,6 @@ but doesn't affect the classpath of dependent projects.
|
||||
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
|
||||
configurations are preferred.
|
||||
|
||||
### API Diff
|
||||
|
||||
This plugin uses the [Gradle JApiCmp](https://github.com/melix/japicmp-gradle-plugin) plugin
|
||||
to generate API Diff reports for each Spring Framework module. This plugin is applied once on the root
|
||||
project and creates tasks in each framework module. Unlike previous versions of this part of the build,
|
||||
there is no need for checking out a specific tag. The plugin will fetch the JARs we want to compare the
|
||||
current working version with. You can generate the reports for all modules or a single module:
|
||||
|
||||
```
|
||||
./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE
|
||||
./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE
|
||||
```
|
||||
|
||||
The reports are located under `build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/`.
|
||||
|
||||
|
||||
### RuntimeHints Java Agent
|
||||
|
||||
|
||||
@@ -19,16 +19,11 @@ ext {
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
|
||||
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}")
|
||||
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
apiDiffPlugin {
|
||||
id = "org.springframework.build.api-diff"
|
||||
implementationClass = "org.springframework.build.api.ApiDiffPlugin"
|
||||
}
|
||||
conventionsPlugin {
|
||||
id = "org.springframework.build.conventions"
|
||||
implementationClass = "org.springframework.build.ConventionsPlugin"
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.build.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.champeau.gradle.japicmp.JapicmpPlugin;
|
||||
import me.champeau.gradle.japicmp.JapicmpTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies the {@code "japicmp-gradle-plugin"}
|
||||
* and create tasks for all subprojects named {@code "spring-*"}, diffing the public API one by one
|
||||
* and creating the reports in {@code "build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/"}.
|
||||
* <p>{@code "./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE"} will output the
|
||||
* reports for the API diff between the baseline version and the current one for all modules.
|
||||
* You can limit the report to a single module with
|
||||
* {@code "./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE"}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ApiDiffPlugin implements Plugin<Project> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApiDiffPlugin.class);
|
||||
|
||||
public static final String TASK_NAME = "apiDiff";
|
||||
|
||||
private static final String BASELINE_VERSION_PROPERTY = "baselineVersion";
|
||||
|
||||
private static final List<String> PACKAGE_INCLUDES = Collections.singletonList("org.springframework.*");
|
||||
|
||||
private static final URI SPRING_MILESTONE_REPOSITORY = URI.create("https://repo.spring.io/milestone");
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
if (project.hasProperty(BASELINE_VERSION_PROPERTY) && project.equals(project.getRootProject())) {
|
||||
project.getPluginManager().apply(JapicmpPlugin.class);
|
||||
project.getPlugins().withType(JapicmpPlugin.class,
|
||||
plugin -> applyApiDiffConventions(project));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyApiDiffConventions(Project project) {
|
||||
String baselineVersion = project.property(BASELINE_VERSION_PROPERTY).toString();
|
||||
project.subprojects(subProject -> {
|
||||
if (subProject.getName().startsWith("spring-")) {
|
||||
createApiDiffTask(baselineVersion, subProject);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createApiDiffTask(String baselineVersion, Project project) {
|
||||
if (isProjectEligible(project)) {
|
||||
// Add Spring Milestone repository for generating diffs against previous milestones
|
||||
project.getRootProject()
|
||||
.getRepositories()
|
||||
.maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(SPRING_MILESTONE_REPOSITORY));
|
||||
JapicmpTask apiDiff = project.getTasks().create(TASK_NAME, JapicmpTask.class);
|
||||
apiDiff.setDescription("Generates an API diff report with japicmp");
|
||||
apiDiff.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
|
||||
|
||||
apiDiff.setOldClasspath(createBaselineConfiguration(baselineVersion, project));
|
||||
TaskProvider<Jar> jar = project.getTasks().withType(Jar.class).named("jar");
|
||||
apiDiff.setNewArchives(project.getLayout().files(jar.get().getArchiveFile().get().getAsFile()));
|
||||
apiDiff.setNewClasspath(getRuntimeClassPath(project));
|
||||
apiDiff.setPackageIncludes(PACKAGE_INCLUDES);
|
||||
apiDiff.setOnlyModified(true);
|
||||
apiDiff.setIgnoreMissingClasses(true);
|
||||
// Ignore Kotlin metadata annotations since they contain
|
||||
// illegal HTML characters and fail the report generation
|
||||
apiDiff.setAnnotationExcludes(Collections.singletonList("@kotlin.Metadata"));
|
||||
|
||||
apiDiff.setHtmlOutputFile(getOutputFile(baselineVersion, project));
|
||||
|
||||
apiDiff.dependsOn(project.getTasks().getByName("jar"));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isProjectEligible(Project project) {
|
||||
return project.getPlugins().hasPlugin(JavaPlugin.class)
|
||||
&& project.getPlugins().hasPlugin(MavenPublishPlugin.class);
|
||||
}
|
||||
|
||||
private Configuration createBaselineConfiguration(String baselineVersion, Project project) {
|
||||
String baseline = String.join(":",
|
||||
project.getGroup().toString(), project.getName(), baselineVersion);
|
||||
Dependency baselineDependency = project.getDependencies().create(baseline + "@jar");
|
||||
Configuration baselineConfiguration = project.getRootProject().getConfigurations().detachedConfiguration(baselineDependency);
|
||||
try {
|
||||
// eagerly resolve the baseline configuration to check whether this is a new Spring module
|
||||
baselineConfiguration.resolve();
|
||||
return baselineConfiguration;
|
||||
}
|
||||
catch (GradleException exception) {
|
||||
logger.warn("Could not resolve {} - assuming this is a new Spring module.", baseline);
|
||||
}
|
||||
return project.getRootProject().getConfigurations().detachedConfiguration();
|
||||
}
|
||||
|
||||
private Configuration getRuntimeClassPath(Project project) {
|
||||
return project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
|
||||
}
|
||||
|
||||
private File getOutputFile(String baseLineVersion, Project project) {
|
||||
String buildDirectoryPath = project.getRootProject()
|
||||
.getLayout().getBuildDirectory().getAsFile().get().getAbsolutePath();
|
||||
Path outDir = Paths.get(buildDirectoryPath, "reports", "api-diff",
|
||||
baseLineVersion + "_to_" + project.getRootProject().getVersion());
|
||||
return project.file(outDir.resolve(project.getName() + ".html").toString());
|
||||
}
|
||||
|
||||
}
|
||||
+4
-1
@@ -166,4 +166,7 @@ By default, any argument that is not a simple value type (as determined by
|
||||
and is not resolved by any other argument resolver is treated as if it were annotated
|
||||
with `@ModelAttribute`.
|
||||
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
support described above does not allow proper ahead-of-time inference of related data
|
||||
binding reflection hints. As a consequence, it is recommended to explicitly annotate
|
||||
method parameters with `@ModelAttribute` for use in a GraalVM native image.
|
||||
|
||||
+4
-1
@@ -216,4 +216,7 @@ By default, any argument that is not a simple value type (as determined by
|
||||
and is not resolved by any other argument resolver is treated as if it were annotated
|
||||
with `@ModelAttribute`.
|
||||
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
support described above does not allow proper ahead-of-time inference of related data
|
||||
binding reflection hints. As a consequence, it is recommended to explicitly annotate
|
||||
method parameters with `@ModelAttribute` for use in a GraalVM native image.
|
||||
|
||||
@@ -229,34 +229,27 @@ Java initialization API. The following example shows how to do so:
|
||||
|
||||
|
||||
[[websocket-server-runtime-configuration]]
|
||||
== Server Configuration
|
||||
== Configuring the Server
|
||||
[.small]#xref:web/webflux-websocket.adoc#webflux-websocket-server-config[See equivalent in the Reactive stack]#
|
||||
|
||||
Each underlying WebSocket engine exposes configuration properties that control
|
||||
runtime characteristics, such as the size of message buffer sizes, idle timeout,
|
||||
and others.
|
||||
You can configure of the underlying WebSocket server such as input message buffer size,
|
||||
idle timeout, and more.
|
||||
|
||||
For Tomcat, WildFly, and GlassFish, you can add a `ServletServerContainerFactoryBean` to your
|
||||
WebSocket Java config, as the following example shows:
|
||||
For Jakarta WebSocket servers, you can add a `ServletServerContainerFactoryBean` to your
|
||||
Java configuration. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Bean
|
||||
public ServletServerContainerFactoryBean createWebSocketContainer() {
|
||||
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
|
||||
container.setMaxTextMessageBufferSize(8192);
|
||||
container.setMaxBinaryMessageBufferSize(8192);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
@Bean
|
||||
public ServletServerContainerFactoryBean createWebSocketContainer() {
|
||||
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
|
||||
container.setMaxTextMessageBufferSize(8192);
|
||||
container.setMaxBinaryMessageBufferSize(8192);
|
||||
return container;
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
Or to your XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
@@ -277,12 +270,11 @@ The following example shows the XML configuration equivalent of the preceding ex
|
||||
</beans>
|
||||
----
|
||||
|
||||
NOTE: For client-side WebSocket configuration, you should use `WebSocketContainerFactoryBean`
|
||||
(XML) or `ContainerProvider.getWebSocketContainer()` (Java configuration).
|
||||
NOTE: For client Jakarta WebSocket configuration, use
|
||||
ContainerProvider.getWebSocketContainer() in Java configuration, or
|
||||
`WebSocketContainerFactoryBean` in XML.
|
||||
|
||||
For Jetty, you need to supply a pre-configured Jetty `WebSocketServerFactory` and plug
|
||||
that into Spring's `DefaultHandshakeHandler` through your WebSocket Java config.
|
||||
The following example shows how to do so:
|
||||
For Jetty, you can supply a `Consumer` callback to configure the WebSocket server:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -298,11 +290,9 @@ The following example shows how to do so:
|
||||
|
||||
@Bean
|
||||
public DefaultHandshakeHandler handshakeHandler() {
|
||||
|
||||
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
|
||||
policy.setInputBufferSize(8192);
|
||||
policy.setIdleTimeout(600000);
|
||||
|
||||
return new DefaultHandshakeHandler(
|
||||
new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
|
||||
}
|
||||
@@ -349,6 +339,10 @@ The following example shows the XML configuration equivalent of the preceding ex
|
||||
</beans>
|
||||
----
|
||||
|
||||
TIP: When using STOMP over WebSocket, you will also need to configure
|
||||
xref:web/websocket/stomp/server-config.adoc[STOMP WebSocket transport]
|
||||
properties.
|
||||
|
||||
|
||||
|
||||
[[websocket-server-allowed-origins]]
|
||||
|
||||
+3
-3
@@ -103,9 +103,9 @@ You can also use the WebSocket transport configuration shown earlier to configur
|
||||
maximum allowed size for incoming STOMP messages. In theory, a WebSocket
|
||||
message can be almost unlimited in size. In practice, WebSocket servers impose
|
||||
limits -- for example, 8K on Tomcat and 64K on Jetty. For this reason, STOMP clients
|
||||
(such as the JavaScript https://github.com/JSteunou/webstomp-client[webstomp-client]
|
||||
and others) split larger STOMP messages at 16K boundaries and send them as multiple
|
||||
WebSocket messages, which requires the server to buffer and re-assemble.
|
||||
such as https://github.com/stomp-js/stompjs[`stomp-js/stompjs`] and others split larger
|
||||
STOMP messages at 16K boundaries and send them as multiple WebSocket messages,
|
||||
which requires the server to buffer and re-assemble.
|
||||
|
||||
Spring's STOMP-over-WebSocket support does this ,so applications can configure the
|
||||
maximum size for STOMP messages irrespective of WebSocket server-specific message
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
[[websocket-stomp-server-config]]
|
||||
= WebSocket Server
|
||||
= WebSocket Transport
|
||||
|
||||
To configure the underlying WebSocket server, the information in
|
||||
xref:web/websocket/server.adoc#websocket-server-runtime-configuration[Server Configuration] applies. For Jetty, however you need to set
|
||||
the `HandshakeHandler` and `WebSocketPolicy` through the `StompEndpointRegistry`:
|
||||
This section explains how to configure the underlying WebSocket server transport.
|
||||
|
||||
For Jakarta WebSocket servers, add a `ServletServerContainerFactoryBean` to your
|
||||
configuration. For examples, see
|
||||
xref:web/websocket/server.adoc#websocket-server-runtime-configuration[Configuring the Server]
|
||||
under the WebSocket section.
|
||||
|
||||
For Jetty WebSocket servers, customize the `JettyRequestUpgradeStrategy` as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -29,5 +34,20 @@ the `HandshakeHandler` and `WebSocketPolicy` through the `StompEndpointRegistry`
|
||||
}
|
||||
----
|
||||
|
||||
In addition to WebSocket server properties, there are also STOMP WebSocket transport properties
|
||||
to customize as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
|
||||
registry.setMessageSizeLimit(4 * 8192);
|
||||
registry.setTimeToFirstMessage(30000);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
@@ -11,15 +11,15 @@ dependencies {
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.13"))
|
||||
api(platform("io.netty:netty-bom:4.1.101.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.13"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.14"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.15"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.16"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:11.0.18"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.4.0"))
|
||||
api(platform("org.junit:junit-bom:5.9.3"))
|
||||
api(platform("org.mockito:mockito-bom:5.7.0"))
|
||||
api(platform("org.mockito:mockito-bom:5.8.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.0.14-SNAPSHOT
|
||||
version=6.0.15
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+17
-12
@@ -159,6 +159,9 @@ import org.springframework.util.StringUtils;
|
||||
public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
|
||||
MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor, PriorityOrdered, BeanFactoryAware {
|
||||
|
||||
private static final Constructor<?>[] EMPTY_CONSTRUCTOR_ARRAY = new Constructor<?>[0];
|
||||
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);
|
||||
@@ -193,9 +196,10 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
this.autowiredAnnotationTypes.add(Autowired.class);
|
||||
this.autowiredAnnotationTypes.add(Value.class);
|
||||
|
||||
ClassLoader classLoader = AutowiredAnnotationBeanPostProcessor.class.getClassLoader();
|
||||
try {
|
||||
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
|
||||
ClassUtils.forName("jakarta.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
|
||||
ClassUtils.forName("jakarta.inject.Inject", classLoader));
|
||||
logger.trace("'jakarta.inject.Inject' annotation found and supported for autowiring");
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
@@ -204,7 +208,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
|
||||
try {
|
||||
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
|
||||
ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
|
||||
ClassUtils.forName("javax.inject.Inject", classLoader));
|
||||
logger.trace("'javax.inject.Inject' annotation found and supported for autowiring");
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
@@ -285,9 +289,16 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
|
||||
@Override
|
||||
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
|
||||
// Register externally managed config members on bean definition.
|
||||
findInjectionMetadata(beanName, beanType, beanDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBeanDefinition(String beanName) {
|
||||
this.lookupMethodsChecked.remove(beanName);
|
||||
this.injectionMetadataCache.remove(beanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
@@ -323,12 +334,6 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBeanDefinition(String beanName) {
|
||||
this.lookupMethodsChecked.remove(beanName);
|
||||
this.injectionMetadataCache.remove(beanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> determineBeanType(Class<?> beanClass, String beanName) throws BeanCreationException {
|
||||
checkLookupMethods(beanClass, beanName);
|
||||
@@ -428,7 +433,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
"default constructor to fall back to: " + candidates.get(0));
|
||||
}
|
||||
}
|
||||
candidateConstructors = candidates.toArray(new Constructor<?>[0]);
|
||||
candidateConstructors = candidates.toArray(EMPTY_CONSTRUCTOR_ARRAY);
|
||||
}
|
||||
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
|
||||
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
|
||||
@@ -441,7 +446,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
candidateConstructors = new Constructor<?>[] {primaryConstructor};
|
||||
}
|
||||
else {
|
||||
candidateConstructors = new Constructor<?>[0];
|
||||
candidateConstructors = EMPTY_CONSTRUCTOR_ARRAY;
|
||||
}
|
||||
this.candidateConstructorsCache.put(beanClass, candidateConstructors);
|
||||
}
|
||||
@@ -1011,7 +1016,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
hints.reflection().registerField(field);
|
||||
CodeBlock resolver = CodeBlock.of("$T.$L($S)",
|
||||
AutowiredFieldValueResolver.class,
|
||||
(!required) ? "forField" : "forRequiredField", field.getName());
|
||||
(!required ? "forField" : "forRequiredField"), field.getName());
|
||||
AccessControl accessControl = AccessControl.forMember(field);
|
||||
if (!accessControl.isAccessibleFrom(targetClassName)) {
|
||||
return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver,
|
||||
@@ -1026,7 +1031,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.add("$T.$L", AutowiredMethodArgumentsResolver.class,
|
||||
(!required) ? "forMethod" : "forRequiredMethod");
|
||||
(!required ? "forMethod" : "forRequiredMethod"));
|
||||
code.add("($S", method.getName());
|
||||
if (method.getParameterCount() > 0) {
|
||||
code.add(", $L", generateParameterTypesCode(method.getParameterTypes()));
|
||||
|
||||
+4
-6
@@ -191,16 +191,14 @@ public final class AutowiredFieldValueResolver extends AutowiredElementResolver
|
||||
return value;
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName,
|
||||
new InjectionPoint(field), ex);
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Field getField(RegisteredBean registeredBean) {
|
||||
Field field = ReflectionUtils.findField(registeredBean.getBeanClass(),
|
||||
this.fieldName);
|
||||
Assert.notNull(field, () -> "No field '" + this.fieldName + "' found on "
|
||||
+ registeredBean.getBeanClass().getName());
|
||||
Field field = ReflectionUtils.findField(registeredBean.getBeanClass(), this.fieldName);
|
||||
Assert.notNull(field, () -> "No field '" + this.fieldName + "' found on " +
|
||||
registeredBean.getBeanClass().getName());
|
||||
return field;
|
||||
}
|
||||
|
||||
|
||||
+7
-9
@@ -493,15 +493,13 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
|
||||
mbdToUse = new RootBeanDefinition(mbd);
|
||||
mbdToUse.setBeanClass(resolvedClass);
|
||||
}
|
||||
|
||||
// Prepare method overrides.
|
||||
try {
|
||||
mbdToUse.prepareMethodOverrides();
|
||||
}
|
||||
catch (BeanDefinitionValidationException ex) {
|
||||
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
|
||||
beanName, "Validation of method overrides failed", ex);
|
||||
try {
|
||||
mbdToUse.prepareMethodOverrides();
|
||||
}
|
||||
catch (BeanDefinitionValidationException ex) {
|
||||
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
|
||||
beanName, "Validation of method overrides failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+10
-2
@@ -731,7 +731,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
aliases.add(fullBeanName);
|
||||
}
|
||||
String[] retrievedAliases = super.getAliases(beanName);
|
||||
String prefix = factoryPrefix ? FACTORY_BEAN_PREFIX : "";
|
||||
String prefix = (factoryPrefix ? FACTORY_BEAN_PREFIX : "");
|
||||
for (String retrievedAlias : retrievedAliases) {
|
||||
String alias = prefix + retrievedAlias;
|
||||
if (!alias.equals(name)) {
|
||||
@@ -1497,7 +1497,11 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
if (mbd.hasBeanClass()) {
|
||||
return mbd.getBeanClass();
|
||||
}
|
||||
return doResolveBeanClass(mbd, typesToMatch);
|
||||
Class<?> beanClass = doResolveBeanClass(mbd, typesToMatch);
|
||||
if (mbd.hasBeanClass()) {
|
||||
mbd.prepareMethodOverrides();
|
||||
}
|
||||
return beanClass;
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);
|
||||
@@ -1505,6 +1509,10 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
catch (LinkageError err) {
|
||||
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err);
|
||||
}
|
||||
catch (BeanDefinitionValidationException ex) {
|
||||
throw new BeanDefinitionStoreException(mbd.getResourceDescription(),
|
||||
beanName, "Validation of method overrides failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+3
-3
@@ -71,7 +71,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
synchronized (bd.constructorArgumentLock) {
|
||||
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
|
||||
if (constructorToUse == null) {
|
||||
final Class<?> clazz = bd.getBeanClass();
|
||||
Class<?> clazz = bd.getBeanClass();
|
||||
if (clazz.isInterface()) {
|
||||
throw new BeanInstantiationException(clazz, "Specified class is an interface");
|
||||
}
|
||||
@@ -104,7 +104,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
|
||||
@Override
|
||||
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
|
||||
final Constructor<?> ctor, Object... args) {
|
||||
Constructor<?> ctor, Object... args) {
|
||||
|
||||
if (!bd.hasMethodOverrides()) {
|
||||
return BeanUtils.instantiateClass(ctor, args);
|
||||
@@ -128,7 +128,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
|
||||
@Override
|
||||
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
|
||||
@Nullable Object factoryBean, final Method factoryMethod, Object... args) {
|
||||
@Nullable Object factoryBean, Method factoryMethod, Object... args) {
|
||||
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(factoryMethod);
|
||||
|
||||
+7
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -78,8 +78,10 @@ public class PathEditor extends PropertyEditorSupport {
|
||||
if (nioPathCandidate && !text.startsWith("/")) {
|
||||
try {
|
||||
URI uri = ResourceUtils.toURI(text);
|
||||
if (uri.getScheme() != null) {
|
||||
nioPathCandidate = false;
|
||||
String scheme = uri.getScheme();
|
||||
if (scheme != null) {
|
||||
// No NIO candidate except for "C:" style drive letters
|
||||
nioPathCandidate = (scheme.length() == 1);
|
||||
// Let's try NIO file system providers via Paths.get(URI)
|
||||
setValue(Paths.get(uri).normalize());
|
||||
return;
|
||||
@@ -109,7 +111,8 @@ public class PathEditor extends PropertyEditorSupport {
|
||||
setValue(resource.getFile().toPath());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalArgumentException("Failed to retrieve file for " + resource, ex);
|
||||
throw new IllegalArgumentException(
|
||||
"Could not retrieve file for " + resource + ": " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+39
-1
@@ -2102,7 +2102,7 @@ class DefaultListableBeanFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void beanProviderWithParentBeanFactoryReuseOrder() {
|
||||
void beanProviderWithParentBeanFactoryDetectsOrder() {
|
||||
DefaultListableBeanFactory parentBf = new DefaultListableBeanFactory();
|
||||
parentBf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
|
||||
parentBf.registerBeanDefinition("regular", new RootBeanDefinition(TestBean.class));
|
||||
@@ -2110,10 +2110,36 @@ class DefaultListableBeanFactoryTests {
|
||||
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
|
||||
lbf.setParentBeanFactory(parentBf);
|
||||
lbf.registerBeanDefinition("low", new RootBeanDefinition(LowPriorityTestBean.class));
|
||||
|
||||
Stream<Class<?>> orderedTypes = lbf.getBeanProvider(TestBean.class).orderedStream().map(Object::getClass);
|
||||
assertThat(orderedTypes).containsExactly(HighPriorityTestBean.class, LowPriorityTestBean.class, TestBean.class);
|
||||
}
|
||||
|
||||
@Test // gh-28374
|
||||
void beanProviderWithParentBeanFactoryAndMixedOrder() {
|
||||
DefaultListableBeanFactory parentBf = new DefaultListableBeanFactory();
|
||||
parentBf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
|
||||
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
|
||||
lbf.setParentBeanFactory(parentBf);
|
||||
|
||||
lbf.registerSingleton("plainTestBean", new TestBean());
|
||||
|
||||
RootBeanDefinition bd1 = new RootBeanDefinition(PriorityTestBeanFactory.class);
|
||||
bd1.setFactoryMethodName("lowPriorityTestBean");
|
||||
lbf.registerBeanDefinition("lowPriorityTestBean", bd1);
|
||||
|
||||
RootBeanDefinition bd2 = new RootBeanDefinition(PriorityTestBeanFactory.class);
|
||||
bd2.setFactoryMethodName("highPriorityTestBean");
|
||||
parentBf.registerBeanDefinition("highPriorityTestBean", bd2);
|
||||
|
||||
ObjectProvider<TestBean> testBeanProvider = lbf.getBeanProvider(ResolvableType.forClass(TestBean.class));
|
||||
List<TestBean> resolved = testBeanProvider.orderedStream().toList();
|
||||
assertThat(resolved.size()).isEqualTo(3);
|
||||
assertThat(resolved.get(0)).isSameAs(lbf.getBean("highPriorityTestBean"));
|
||||
assertThat(resolved.get(1)).isSameAs(lbf.getBean("lowPriorityTestBean"));
|
||||
assertThat(resolved.get(2)).isSameAs(lbf.getBean("plainTestBean"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void autowireExistingBeanByName() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
|
||||
@@ -3287,6 +3313,18 @@ class DefaultListableBeanFactoryTests {
|
||||
}
|
||||
|
||||
|
||||
private static class PriorityTestBeanFactory {
|
||||
|
||||
public static LowPriorityTestBean lowPriorityTestBean() {
|
||||
return new LowPriorityTestBean();
|
||||
}
|
||||
|
||||
public static HighPriorityTestBean highPriorityTestBean() {
|
||||
return new HighPriorityTestBean();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class NullTestBeanFactoryBean<T> implements FactoryBean<TestBean> {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,12 +6,12 @@ dependencies {
|
||||
api(project(":spring-core"))
|
||||
optional(project(":spring-jdbc")) // for Quartz support
|
||||
optional(project(":spring-tx")) // for Quartz support
|
||||
optional("com.github.ben-manes.caffeine:caffeine")
|
||||
optional("jakarta.activation:jakarta.activation-api")
|
||||
optional("jakarta.mail:jakarta.mail-api")
|
||||
optional("javax.cache:cache-api")
|
||||
optional("com.github.ben-manes.caffeine:caffeine")
|
||||
optional("org.quartz-scheduler:quartz")
|
||||
optional("org.freemarker:freemarker")
|
||||
optional("org.quartz-scheduler:quartz")
|
||||
testFixturesApi("org.junit.jupiter:junit-jupiter-api")
|
||||
testFixturesImplementation("org.assertj:assertj-core")
|
||||
testFixturesImplementation("org.mockito:mockito-core")
|
||||
@@ -20,10 +20,10 @@ dependencies {
|
||||
testImplementation(testFixtures(project(":spring-context")))
|
||||
testImplementation(testFixtures(project(":spring-core")))
|
||||
testImplementation(testFixtures(project(":spring-tx")))
|
||||
testImplementation("org.hsqldb:hsqldb")
|
||||
testImplementation("jakarta.annotation:jakarta.annotation-api")
|
||||
testRuntimeOnly("org.ehcache:jcache")
|
||||
testRuntimeOnly("org.ehcache:ehcache")
|
||||
testRuntimeOnly("org.glassfish:jakarta.el")
|
||||
testImplementation("org.hsqldb:hsqldb")
|
||||
testRuntimeOnly("com.sun.mail:jakarta.mail")
|
||||
testRuntimeOnly("org.ehcache:ehcache")
|
||||
testRuntimeOnly("org.ehcache:jcache")
|
||||
testRuntimeOnly("org.glassfish:jakarta.el")
|
||||
}
|
||||
|
||||
+6
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,6 +32,7 @@ import java.lang.annotation.Target;
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
* @see Cacheable
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -42,8 +43,10 @@ public @interface CacheConfig {
|
||||
* Names of the default caches to consider for caching operations defined
|
||||
* in the annotated class.
|
||||
* <p>If none is set at the operation level, these are used instead of the default.
|
||||
* <p>May be used to determine the target cache (or caches), matching the
|
||||
* qualifier value or the bean names of a specific bean definition.
|
||||
* <p>Names may be used to determine the target cache(s), to be resolved via the
|
||||
* configured {@link #cacheResolver()} which typically delegates to
|
||||
* {@link org.springframework.cache.CacheManager#getCache}.
|
||||
* For further details see {@link Cacheable#cacheNames()}.
|
||||
*/
|
||||
String[] cacheNames() default {};
|
||||
|
||||
|
||||
+6
-2
@@ -70,8 +70,12 @@ public @interface Cacheable {
|
||||
|
||||
/**
|
||||
* Names of the caches in which method invocation results are stored.
|
||||
* <p>Names may be used to determine the target cache (or caches), matching
|
||||
* the qualifier value or bean name of a specific bean definition.
|
||||
* <p>Names may be used to determine the target cache(s), to be resolved via the
|
||||
* configured {@link #cacheResolver()} which typically delegates to
|
||||
* {@link org.springframework.cache.CacheManager#getCache}.
|
||||
* <p>This will usually be a single cache name. If multiple names are specified,
|
||||
* they will be consulted for a cache hit in the order of definition, and they
|
||||
* will all receive a put/evict request for the same newly cached value.
|
||||
* @since 4.2
|
||||
* @see #value
|
||||
* @see CacheConfig#cacheNames
|
||||
|
||||
+3
-3
@@ -547,10 +547,10 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the {@link CachePutRequest} for all {@link CacheOperation} using
|
||||
* the specified result value.
|
||||
* Collect a {@link CachePutRequest} for every {@link CacheOperation}
|
||||
* using the specified result value.
|
||||
* @param contexts the contexts to handle
|
||||
* @param result the result value (never {@code null})
|
||||
* @param result the result value
|
||||
* @param putRequests the collection to update
|
||||
*/
|
||||
private void collectPutRequests(Collection<CacheOperationContext> contexts,
|
||||
|
||||
+11
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -62,11 +62,13 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A component provider that provides candidate components from a base package. Can
|
||||
* use {@link CandidateComponentsIndex the index} if it is available of scans the
|
||||
* classpath otherwise. Candidate components are identified by applying exclude and
|
||||
* include filters. {@link AnnotationTypeFilter}, {@link AssignableTypeFilter} include
|
||||
* filters on an annotation/superclass that are annotated with {@link Indexed} are
|
||||
* A component provider that scans for candidate components starting from a
|
||||
* specified base package. Can use the {@linkplain CandidateComponentsIndex component
|
||||
* index}, if it is available, and scans the classpath otherwise.
|
||||
*
|
||||
* <p>Candidate components are identified by applying exclude and include filters.
|
||||
* {@link AnnotationTypeFilter} and {@link AssignableTypeFilter} include filters
|
||||
* for an annotation/target-type that is annotated with {@link Indexed} are
|
||||
* supported: if any other include filter is specified, the index is ignored and
|
||||
* classpath scanning is used instead.
|
||||
*
|
||||
@@ -201,7 +203,6 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
* {@link Controller @Controller} stereotype annotations.
|
||||
* <p>Also supports Jakarta EE's {@link jakarta.annotation.ManagedBean} and
|
||||
* JSR-330's {@link jakarta.inject.Named} annotations, if available.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void registerDefaultFilters() {
|
||||
@@ -305,7 +306,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
|
||||
|
||||
/**
|
||||
* Scan the class path for candidate components.
|
||||
* Scan the component index or class path for candidate components.
|
||||
* @param basePackage the package to check for annotated classes
|
||||
* @return a corresponding Set of autodetected bean definitions
|
||||
*/
|
||||
@@ -319,7 +320,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the index can be used by this instance.
|
||||
* Determine if the component index can be used by this instance.
|
||||
* @return {@code true} if the index is available and the configuration of this
|
||||
* instance is supported by it, {@code false} otherwise
|
||||
* @since 5.0
|
||||
@@ -460,8 +461,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new BeanDefinitionStoreException(
|
||||
"Failed to read candidate component class: " + resource, ex);
|
||||
throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -44,7 +44,6 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
* public MyService myService() {
|
||||
* return new MyService();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>If the configuration class above is processed, {@code MyHints} will be
|
||||
|
||||
+1
-1
@@ -203,7 +203,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
/** Flag that indicates whether this context has been closed already. */
|
||||
private final AtomicBoolean closed = new AtomicBoolean();
|
||||
|
||||
/** Synchronization monitor for the "refresh" and "destroy". */
|
||||
/** Synchronization monitor for "refresh" and "close". */
|
||||
private final Object startupShutdownMonitor = new Object();
|
||||
|
||||
/** Reference to the JVM shutdown hook, if registered. */
|
||||
|
||||
+9
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,12 +18,13 @@ package org.springframework.ejb.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.jndi.JndiObjectFactoryBean;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
|
||||
* implementation for parsing '{@code local-slsb}' tags and
|
||||
* creating plain {@link JndiObjectFactoryBean} definitions.
|
||||
* creating plain {@link JndiObjectFactoryBean} definitions on 6.0.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Juergen Hoeller
|
||||
@@ -36,4 +37,10 @@ class LocalStatelessSessionBeanDefinitionParser extends AbstractJndiLocatingBean
|
||||
return JndiObjectFactoryBean.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEligibleAttribute(String attributeName) {
|
||||
return (super.isEligibleAttribute(attributeName) &&
|
||||
BeanUtils.getPropertyDescriptor(JndiObjectFactoryBean.class, extractPropertyName(attributeName)) != null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,12 +18,13 @@ package org.springframework.ejb.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.jndi.JndiObjectFactoryBean;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
|
||||
* implementation for parsing '{@code remote-slsb}' tags and
|
||||
* creating plain {@link JndiObjectFactoryBean} definitions.
|
||||
* creating plain {@link JndiObjectFactoryBean} definitions as of 6.0.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Juergen Hoeller
|
||||
@@ -36,4 +37,10 @@ class RemoteStatelessSessionBeanDefinitionParser extends AbstractJndiLocatingBea
|
||||
return JndiObjectFactoryBean.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEligibleAttribute(String attributeName) {
|
||||
return (super.isEligibleAttribute(attributeName) &&
|
||||
BeanUtils.getPropertyDescriptor(JndiObjectFactoryBean.class, extractPropertyName(attributeName)) != null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -50,6 +50,9 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class MetadataNamingStrategy implements ObjectNamingStrategy, InitializingBean {
|
||||
|
||||
private static final char[] QUOTABLE_CHARS = new char[] {',', '=', ':', '"'};
|
||||
|
||||
|
||||
/**
|
||||
* The {@code JmxAttributeSource} implementation to use for reading metadata.
|
||||
*/
|
||||
@@ -132,10 +135,23 @@ public class MetadataNamingStrategy implements ObjectNamingStrategy, Initializin
|
||||
}
|
||||
Hashtable<String, String> properties = new Hashtable<>();
|
||||
properties.put("type", ClassUtils.getShortName(managedClass));
|
||||
properties.put("name", beanKey);
|
||||
properties.put("name", quoteIfNecessary(beanKey));
|
||||
return ObjectNameManager.getInstance(domain, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String quoteIfNecessary(String value) {
|
||||
return shouldQuote(value) ? ObjectName.quote(value) : value;
|
||||
}
|
||||
|
||||
private static boolean shouldQuote(String value) {
|
||||
for (char quotableChar : QUOTABLE_CHARS) {
|
||||
if (value.indexOf(quotableChar) != -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="local-slsb" type="jndiLocatingType">
|
||||
<xsd:element name="local-slsb" type="ejbType">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:org.springframework.jndi.JndiObjectFactoryBean"><![CDATA[
|
||||
Exposes a reference to a local EJB Stateless SessionBean.
|
||||
@@ -103,15 +103,56 @@
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="remote-slsb" type="jndiLocatingType">
|
||||
<xsd:element name="remote-slsb">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:org.springframework.jndi.JndiObjectFactoryBean"><![CDATA[
|
||||
Exposes a reference to a remote EJB Stateless SessionBean.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="ejbType">
|
||||
<xsd:attribute name="home-interface" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:java.lang.Class"><![CDATA[
|
||||
The home interface that will be narrowed to before performing the
|
||||
parameterless SLSB create() call that returns the actual SLSB proxy.
|
||||
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="refresh-home-on-connect-failure" type="xsd:boolean" default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Controls whether to refresh the EJB home on connect failure.
|
||||
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
|
||||
|
||||
Can be turned on to allow for hot restart of the EJB server.
|
||||
If a cached EJB home throws an RMI exception that indicates a
|
||||
remote connect failure, a fresh home will be fetched and the
|
||||
invocation will be retried.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="cache-session-bean" type="xsd:boolean" default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Controls whether to cache the actual session bean object.
|
||||
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
|
||||
|
||||
Off by default for standard EJB compliance. Turn this flag
|
||||
on to optimize session bean access for servers that are
|
||||
known to allow for caching the actual session bean object.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:complexType name="jndiLocatingType">
|
||||
<!-- base types -->
|
||||
<xsd:complexType name="jndiLocatingType" abstract="true">
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="beans:identifiedType">
|
||||
<xsd:sequence>
|
||||
@@ -183,6 +224,40 @@
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="ejbType">
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="jndiLocatingType">
|
||||
<xsd:attribute name="lookup-home-on-startup" type="xsd:boolean" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Controls whether the lookup of the EJB home object is performed
|
||||
immediately on startup (if true, the default), or on first access
|
||||
(if false).
|
||||
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="cache-home" type="xsd:boolean" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Controls whether the EJB home object is cached once it has been located.
|
||||
On by default; turn this flag off to always reobtain fresh home objects.
|
||||
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="business-interface" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:java.lang.Class"><![CDATA[
|
||||
The business interface of the EJB being proxied.
|
||||
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:simpleType name="environmentRefType">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
|
||||
+13
-14
@@ -1086,8 +1086,8 @@ public abstract class AbstractAopProxyTests {
|
||||
// NameReverter saved it back
|
||||
assertThat(it.getName()).isEqualTo(name1);
|
||||
assertThat(saver.names).hasSize(2);
|
||||
assertThat(saver.names.get(0)).isEqualTo(name2);
|
||||
assertThat(saver.names.get(1)).isEqualTo(name1);
|
||||
assertThat(saver.names).element(0).isEqualTo(name2);
|
||||
assertThat(saver.names).element(1).isEqualTo(name1);
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
@@ -1178,7 +1178,7 @@ public abstract class AbstractAopProxyTests {
|
||||
assertThat(i2).isEqualTo(i1);
|
||||
assertThat(proxyB).isEqualTo(proxyA);
|
||||
assertThat(proxyB.hashCode()).isEqualTo(proxyA.hashCode());
|
||||
assertThat(proxyA.equals(a)).isFalse();
|
||||
assertThat(proxyA).isNotEqualTo(a);
|
||||
|
||||
// Equality checks were handled by the proxy
|
||||
assertThat(i1.getCount()).isEqualTo(0);
|
||||
@@ -1187,7 +1187,7 @@ public abstract class AbstractAopProxyTests {
|
||||
// and won't think it's equal to B's NopInterceptor
|
||||
proxyA.absquatulate();
|
||||
assertThat(i1.getCount()).isEqualTo(1);
|
||||
assertThat(proxyA.equals(proxyB)).isFalse();
|
||||
assertThat(proxyA).isNotEqualTo(proxyB);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1874,6 +1874,14 @@ public abstract class AbstractAopProxyTests {
|
||||
return target.getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.aop.TargetSource#isStatic()
|
||||
*/
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.aop.TargetSource#getTarget()
|
||||
*/
|
||||
@@ -1903,19 +1911,10 @@ public abstract class AbstractAopProxyTests {
|
||||
throw new RuntimeException("Expectation failed: " + gets + " gets and " + releases + " releases");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.aop.TargetSource#isStatic()
|
||||
*/
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static abstract class ExposedInvocationTestBean extends TestBean {
|
||||
abstract static class ExposedInvocationTestBean extends TestBean {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
|
||||
+33
@@ -22,8 +22,13 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -56,6 +61,8 @@ import org.springframework.beans.testfixture.beans.ITestBean;
|
||||
import org.springframework.beans.testfixture.beans.IndexedTestBean;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.DummyFactory;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
@@ -1336,6 +1343,15 @@ class XmlBeanFactoryTests {
|
||||
assertThat(dos.lastArg).isEqualTo(s2);
|
||||
}
|
||||
|
||||
@Test // gh-31826
|
||||
void replaceNonOverloadedInterfaceMethodWithoutSpecifyingExplicitArgTypes() {
|
||||
try (ConfigurableApplicationContext context =
|
||||
new ClassPathXmlApplicationContext(DELEGATION_OVERRIDES_CONTEXT.getPath())) {
|
||||
EchoService echoService = context.getBean(EchoService.class);
|
||||
assertThat(echoService.echo("foo", "bar")).containsExactly("bar", "foo");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void lookupOverrideOneMethodWithConstructorInjection() {
|
||||
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
|
||||
@@ -1891,3 +1907,20 @@ class XmlBeanFactoryTests {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface EchoService {
|
||||
|
||||
String[] echo(Object... objects);
|
||||
}
|
||||
|
||||
class ReverseArrayMethodReplacer implements MethodReplacer {
|
||||
|
||||
@Override
|
||||
public Object reimplement(Object obj, Method method, Object[] args) {
|
||||
List<String> list = Arrays.stream((Object[]) args[0])
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
Collections.reverse(list);
|
||||
return list.toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -54,7 +54,7 @@ class EnableCachingIntegrationTests {
|
||||
|
||||
|
||||
@AfterEach
|
||||
public void closeContext() {
|
||||
void closeContext() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
Vendored
+16
-13
@@ -60,6 +60,7 @@ class CacheErrorHandlerTests {
|
||||
|
||||
private SimpleService simpleService;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.context = new AnnotationConfigApplicationContext(Config.class);
|
||||
@@ -69,11 +70,13 @@ class CacheErrorHandlerTests {
|
||||
this.simpleService = context.getBean(SimpleService.class);
|
||||
}
|
||||
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
void closeContext() {
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void getFail() {
|
||||
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get");
|
||||
@@ -107,9 +110,9 @@ class CacheErrorHandlerTests {
|
||||
|
||||
this.cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
|
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
|
||||
this.simpleService.get(0L))
|
||||
.withMessage("Test exception on get");
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(() -> this.simpleService.get(0L))
|
||||
.withMessage("Test exception on get");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -128,9 +131,9 @@ class CacheErrorHandlerTests {
|
||||
|
||||
this.cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
|
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
|
||||
this.simpleService.put(0L))
|
||||
.withMessage("Test exception on put");
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(() -> this.simpleService.put(0L))
|
||||
.withMessage("Test exception on put");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -149,9 +152,9 @@ class CacheErrorHandlerTests {
|
||||
|
||||
this.cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
|
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
|
||||
this.simpleService.evict(0L))
|
||||
.withMessage("Test exception on evict");
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(() -> this.simpleService.evict(0L))
|
||||
.withMessage("Test exception on evict");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -170,9 +173,9 @@ class CacheErrorHandlerTests {
|
||||
|
||||
this.cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
|
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
|
||||
this.simpleService.clear())
|
||||
.withMessage("Test exception on clear");
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(() -> this.simpleService.clear())
|
||||
.withMessage("Test exception on clear");
|
||||
}
|
||||
|
||||
|
||||
|
||||
Vendored
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -51,6 +51,7 @@ public class CachePutEvaluationTests {
|
||||
|
||||
private SimpleService service;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.context = new AnnotationConfigApplicationContext(Config.class);
|
||||
@@ -59,12 +60,11 @@ public class CachePutEvaluationTests {
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
public void closeContext() {
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void mutualGetPutExclusion() {
|
||||
String key = "1";
|
||||
|
||||
+9
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -72,7 +72,7 @@ class CacheResolverCustomizationTests {
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
void closeContext() {
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
@@ -142,16 +142,17 @@ class CacheResolverCustomizationTests {
|
||||
@Test
|
||||
void noCacheResolved() {
|
||||
Method method = ReflectionUtils.findMethod(SimpleService.class, "noCacheResolved", Object.class);
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
this.simpleService.noCacheResolved(new Object()))
|
||||
.withMessageContaining(method.toString());
|
||||
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.simpleService.noCacheResolved(new Object()))
|
||||
.withMessageContaining(method.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void unknownCacheResolver() {
|
||||
assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(() ->
|
||||
this.simpleService.unknownCacheResolver(new Object()))
|
||||
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("unknownCacheResolver"));
|
||||
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
|
||||
.isThrownBy(() -> this.simpleService.unknownCacheResolver(new Object()))
|
||||
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("unknownCacheResolver"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
+11
-11
@@ -126,16 +126,16 @@ class GenericApplicationContextTests {
|
||||
|
||||
assertThat(context.getBean(String.class)).isSameAs(context.getBean("testBean"));
|
||||
assertThat(context.getAutowireCapableBeanFactory().getBean(String.class))
|
||||
.isSameAs(context.getAutowireCapableBeanFactory().getBean("testBean"));
|
||||
.isSameAs(context.getAutowireCapableBeanFactory().getBean("testBean"));
|
||||
|
||||
context.close();
|
||||
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> context.getBean(String.class));
|
||||
.isThrownBy(() -> context.getBean(String.class));
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> context.getAutowireCapableBeanFactory().getBean(String.class));
|
||||
.isThrownBy(() -> context.getAutowireCapableBeanFactory().getBean(String.class));
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> context.getAutowireCapableBeanFactory().getBean("testBean"));
|
||||
.isThrownBy(() -> context.getAutowireCapableBeanFactory().getBean("testBean"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -236,9 +236,9 @@ class GenericApplicationContextTests {
|
||||
assertThat(context.getBeanNamesForType(BeanB.class)).containsExactly("b");
|
||||
assertThat(context.getBeanNamesForType(BeanC.class)).containsExactly("c");
|
||||
assertThat(context.getBeansOfType(BeanA.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BeanB.class).values().iterator().next())
|
||||
assertThat(context.getBeansOfType(BeanB.class).values()).element(0)
|
||||
.isSameAs(context.getBean(BeanB.class));
|
||||
assertThat(context.getBeansOfType(BeanC.class).values().iterator().next())
|
||||
assertThat(context.getBeansOfType(BeanC.class).values()).element(0)
|
||||
.isSameAs(context.getBean(BeanC.class));
|
||||
}
|
||||
|
||||
@@ -281,8 +281,8 @@ class GenericApplicationContextTests {
|
||||
// java.nio.file.InvalidPathException: Illegal char <:> at index 4: ping:foo
|
||||
if (resourceLoader instanceof FileSystemResourceLoader && OS.WINDOWS.isCurrentOs()) {
|
||||
assertThatExceptionOfType(InvalidPathException.class)
|
||||
.isThrownBy(() -> context.getResource(pingLocation))
|
||||
.withMessageContaining(pingLocation);
|
||||
.isThrownBy(() -> context.getResource(pingLocation))
|
||||
.withMessageContaining(pingLocation);
|
||||
}
|
||||
else {
|
||||
resource = context.getResource(pingLocation);
|
||||
@@ -297,8 +297,8 @@ class GenericApplicationContextTests {
|
||||
assertThat(resource).isInstanceOf(FileUrlResource.class);
|
||||
resource = context.getResource(pingLocation);
|
||||
assertThat(resource).asInstanceOf(type(ByteArrayResource.class))
|
||||
.extracting(bar -> new String(bar.getByteArray(), UTF_8))
|
||||
.isEqualTo("pong:foo");
|
||||
.extracting(bar -> new String(bar.getByteArray(), UTF_8))
|
||||
.isEqualTo("pong:foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -536,7 +536,7 @@ class GenericApplicationContextTests {
|
||||
}
|
||||
}
|
||||
|
||||
static class BeanB implements ApplicationContextAware {
|
||||
static class BeanB implements ApplicationContextAware {
|
||||
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
|
||||
+35
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,9 +16,12 @@
|
||||
|
||||
package org.springframework.ejb.config;
|
||||
|
||||
import javax.naming.NoInitialContextException;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
@@ -29,6 +32,7 @@ import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.jndi.JndiObjectFactoryBean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* @author Rob Harrop
|
||||
@@ -93,6 +97,10 @@ public class JeeNamespaceHandlerTests {
|
||||
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition("simpleLocalEjb");
|
||||
assertThat(beanDefinition.getBeanClassName()).isEqualTo(JndiObjectFactoryBean.class.getName());
|
||||
assertPropertyValue(beanDefinition, "jndiName", "ejb/MyLocalBean");
|
||||
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.beanFactory.getBean("simpleLocalEjb"))
|
||||
.withCauseInstanceOf(NoInitialContextException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -100,6 +108,32 @@ public class JeeNamespaceHandlerTests {
|
||||
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition("simpleRemoteEjb");
|
||||
assertThat(beanDefinition.getBeanClassName()).isEqualTo(JndiObjectFactoryBean.class.getName());
|
||||
assertPropertyValue(beanDefinition, "jndiName", "ejb/MyRemoteBean");
|
||||
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.beanFactory.getBean("simpleRemoteEjb"))
|
||||
.withCauseInstanceOf(NoInitialContextException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplexLocalSlsb() {
|
||||
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition("complexLocalEjb");
|
||||
assertThat(beanDefinition.getBeanClassName()).isEqualTo(JndiObjectFactoryBean.class.getName());
|
||||
assertPropertyValue(beanDefinition, "jndiName", "ejb/MyLocalBean");
|
||||
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.beanFactory.getBean("complexLocalEjb"))
|
||||
.withCauseInstanceOf(NoInitialContextException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplexRemoteSlsb() {
|
||||
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition("complexRemoteEjb");
|
||||
assertThat(beanDefinition.getBeanClassName()).isEqualTo(JndiObjectFactoryBean.class.getName());
|
||||
assertPropertyValue(beanDefinition, "jndiName", "ejb/MyRemoteBean");
|
||||
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.beanFactory.getBean("complexRemoteEjb"))
|
||||
.withCauseInstanceOf(NoInitialContextException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jmx.export.naming;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.management.MalformedObjectNameException;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for {@link MetadataNamingStrategy}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class MetadataNamingStrategyTests {
|
||||
|
||||
private static final TestBean TEST_BEAN = new TestBean();
|
||||
|
||||
private final MetadataNamingStrategy strategy;
|
||||
|
||||
MetadataNamingStrategyTests() {
|
||||
this.strategy = new MetadataNamingStrategy();
|
||||
this.strategy.setDefaultDomain("com.example");
|
||||
this.strategy.setAttributeSource(new AnnotationJmxAttributeSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getObjectNameWhenBeanNameIsSimple() throws MalformedObjectNameException {
|
||||
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "myBean");
|
||||
assertThat(name.getDomain()).isEqualTo("com.example");
|
||||
assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "myBean"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getObjectNameWhenBeanNameIsValidObjectName() throws MalformedObjectNameException {
|
||||
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "com.another:name=myBean");
|
||||
assertThat(name.getDomain()).isEqualTo("com.another");
|
||||
assertThat(name.getKeyPropertyList()).containsOnly(entry("name", "myBean"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getObjectNameWhenBeanNamContainsComma() throws MalformedObjectNameException {
|
||||
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "myBean,");
|
||||
assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"myBean,\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getObjectNameWhenBeanNamContainsEquals() throws MalformedObjectNameException {
|
||||
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "my=Bean");
|
||||
assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"my=Bean\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getObjectNameWhenBeanNamContainsColon() throws MalformedObjectNameException {
|
||||
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "my:Bean");
|
||||
assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"my:Bean\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getObjectNameWhenBeanNamContainsQuote() throws MalformedObjectNameException {
|
||||
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "\"myBean\"");
|
||||
assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"\\\"myBean\\\"\""));
|
||||
}
|
||||
|
||||
private Consumer<ObjectName> hasDefaultProperties(Object instance, String expectedName) {
|
||||
return objectName -> assertThat(objectName.getKeyPropertyList()).containsOnly(
|
||||
entry("type", ClassUtils.getShortName(instance.getClass())),
|
||||
entry("name", expectedName));
|
||||
}
|
||||
|
||||
static class TestBean {}
|
||||
|
||||
}
|
||||
+12
-21
@@ -3,9 +3,6 @@
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
|
||||
|
||||
<!--
|
||||
Not yet in use: illustration of possible approach
|
||||
-->
|
||||
<bean id="overrideOneMethod" class="org.springframework.beans.factory.xml.OverrideOneMethod">
|
||||
|
||||
<lookup-method name="getPrototypeDependency" bean="jenny"/>
|
||||
@@ -27,39 +24,34 @@
|
||||
|
||||
<lookup-method name="protectedOverrideSingleton" bean="david"/>
|
||||
|
||||
<!--
|
||||
This method is not overloaded, so we don't need to specify any arg types
|
||||
-->
|
||||
<!-- This method is not overloaded, so we don't need to specify any arg types -->
|
||||
<replaced-method name="doSomething" replacer="doSomethingReplacer"/>
|
||||
|
||||
</bean>
|
||||
|
||||
<bean id="replaceVoidMethod" parent="someParent"
|
||||
class="org.springframework.beans.factory.xml.OverrideOneMethodSubclass">
|
||||
<bean id="replaceVoidMethod" parent="someParent" class="org.springframework.beans.factory.xml.OverrideOneMethodSubclass"/>
|
||||
|
||||
<bean id="replaceEchoMethod" class="org.springframework.beans.factory.xml.EchoService">
|
||||
<!-- This method is not overloaded, so we don't need to specify any arg types -->
|
||||
<replaced-method name="echo" replacer="reverseArrayReplacer" />
|
||||
</bean>
|
||||
|
||||
<bean id="reverseReplacer"
|
||||
class="org.springframework.beans.factory.xml.ReverseMethodReplacer"/>
|
||||
<bean id="reverseReplacer" class="org.springframework.beans.factory.xml.ReverseMethodReplacer"/>
|
||||
|
||||
<bean id="fixedReplacer"
|
||||
class="org.springframework.beans.factory.xml.FixedMethodReplacer"/>
|
||||
<bean id="reverseArrayReplacer" class="org.springframework.beans.factory.xml.ReverseArrayMethodReplacer"/>
|
||||
|
||||
<bean id="doSomethingReplacer"
|
||||
class="org.springframework.beans.factory.xml.XmlBeanFactoryTests$DoSomethingReplacer"/>
|
||||
<bean id="fixedReplacer" class="org.springframework.beans.factory.xml.FixedMethodReplacer"/>
|
||||
|
||||
<bean id="serializableReplacer"
|
||||
class="org.springframework.beans.factory.xml.SerializableMethodReplacerCandidate">
|
||||
<bean id="doSomethingReplacer" class="org.springframework.beans.factory.xml.XmlBeanFactoryTests$DoSomethingReplacer"/>
|
||||
|
||||
<bean id="serializableReplacer" class="org.springframework.beans.factory.xml.SerializableMethodReplacerCandidate">
|
||||
<!-- Arbitrary method replacer -->
|
||||
<replaced-method name="replaceMe" replacer="reverseReplacer">
|
||||
<arg-type>String</arg-type>
|
||||
</replaced-method>
|
||||
|
||||
</bean>
|
||||
|
||||
<bean id="jenny" class="org.springframework.beans.testfixture.beans.TestBean"
|
||||
scope="prototype">
|
||||
<bean id="jenny" class="org.springframework.beans.testfixture.beans.TestBean" scope="prototype">
|
||||
<property name="name"><value>Jenny</value></property>
|
||||
<property name="age"><value>30</value></property>
|
||||
<property name="spouse">
|
||||
@@ -68,8 +60,7 @@
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="david" class="org.springframework.beans.testfixture.beans.TestBean"
|
||||
scope="singleton">
|
||||
<bean id="david" class="org.springframework.beans.testfixture.beans.TestBean" scope="singleton">
|
||||
<description>
|
||||
Simple bean, without any collections.
|
||||
</description>
|
||||
|
||||
+32
-5
@@ -40,14 +40,41 @@
|
||||
</util:properties>
|
||||
|
||||
<!-- Local EJB Tests -->
|
||||
<jee:local-slsb id="simpleLocalEjb" jndi-name="ejb/MyLocalBean"/>
|
||||
<jee:local-slsb id="simpleLocalEjb" jndi-name="ejb/MyLocalBean"
|
||||
business-interface="org.springframework.beans.testfixture.beans.ITestBean"/>
|
||||
|
||||
<jee:local-slsb id="complexLocalEjb"
|
||||
jndi-name="ejb/MyLocalBean"
|
||||
business-interface="org.springframework.beans.testfixture.beans.ITestBean"
|
||||
cache-home="true"
|
||||
lookup-home-on-startup="true"
|
||||
resource-ref="true">
|
||||
<jee:environment>foo=bar</jee:environment>
|
||||
</jee:local-slsb>
|
||||
|
||||
<!-- Remote EJB Tests -->
|
||||
<jee:remote-slsb id="simpleRemoteEjb" jndi-name="ejb/MyRemoteBean"/>
|
||||
<jee:remote-slsb id="simpleRemoteEjb" jndi-name="ejb/MyRemoteBean"
|
||||
business-interface="org.springframework.beans.testfixture.beans.ITestBean"/>
|
||||
|
||||
<!-- Lazy beans Tests-->
|
||||
<jee:remote-slsb id="complexRemoteEjb"
|
||||
jndi-name="ejb/MyRemoteBean"
|
||||
business-interface="org.springframework.beans.testfixture.beans.ITestBean"
|
||||
cache-home="true"
|
||||
lookup-home-on-startup="true"
|
||||
resource-ref="true"
|
||||
home-interface="org.springframework.beans.testfixture.beans.ITestBean"
|
||||
refresh-home-on-connect-failure="true"
|
||||
cache-session-bean="true">
|
||||
<jee:environment>foo=bar</jee:environment>
|
||||
</jee:remote-slsb>
|
||||
|
||||
<!-- Lazy Lookup Tests-->
|
||||
<jee:jndi-lookup id="lazyDataSource" jndi-name="jdbc/MyDataSource" lazy-init="true"/>
|
||||
<jee:local-slsb id="lazyLocalBean" jndi-name="ejb/MyLocalBean" lazy-init="true"/>
|
||||
<jee:remote-slsb id="lazyRemoteBean" jndi-name="ejb/MyRemoteBean" lazy-init="true"/>
|
||||
|
||||
<jee:local-slsb id="lazyLocalBean" jndi-name="ejb/MyLocalBean"
|
||||
business-interface="org.springframework.beans.testfixture.beans.ITestBean" lazy-init="true"/>
|
||||
|
||||
<jee:remote-slsb id="lazyRemoteBean" jndi-name="ejb/MyRemoteBean"
|
||||
business-interface="org.springframework.beans.testfixture.beans.ITestBean" lazy-init="true"/>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.springframework.core.io.InputStreamSource;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.function.ThrowingConsumer;
|
||||
|
||||
/**
|
||||
@@ -43,6 +44,7 @@ public interface GeneratedFiles {
|
||||
* @param javaFile the java file to add
|
||||
*/
|
||||
default void addSourceFile(JavaFile javaFile) {
|
||||
validatePackage(javaFile.packageName, javaFile.typeSpec.name);
|
||||
String className = javaFile.packageName + "." + javaFile.typeSpec.name;
|
||||
addSourceFile(className, javaFile::writeTo);
|
||||
}
|
||||
@@ -161,11 +163,20 @@ public interface GeneratedFiles {
|
||||
|
||||
private static String getClassNamePath(String className) {
|
||||
Assert.hasLength(className, "'className' must not be empty");
|
||||
validatePackage(ClassUtils.getPackageName(className), className);
|
||||
Assert.isTrue(isJavaIdentifier(className),
|
||||
"'className' must be a valid identifier, got '" + className + "'");
|
||||
return ClassUtils.convertClassNameToResourcePath(className) + ".java";
|
||||
}
|
||||
|
||||
private static void validatePackage(String packageName, String className) {
|
||||
if (!StringUtils.hasLength(packageName)) {
|
||||
throw new IllegalArgumentException("Could not add '" + className + "', "
|
||||
+ "processing classes in the default package is not supported. "
|
||||
+ "Did you forget to add a package statement?");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isJavaIdentifier(String className) {
|
||||
char[] chars = className.toCharArray();
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
|
||||
+3
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -336,11 +336,10 @@ abstract class AnnotationsScanner {
|
||||
|
||||
Method[] methods = baseTypeMethodsCache.get(baseType);
|
||||
if (methods == null) {
|
||||
boolean isInterface = baseType.isInterface();
|
||||
methods = isInterface ? baseType.getMethods() : ReflectionUtils.getDeclaredMethods(baseType);
|
||||
methods = ReflectionUtils.getDeclaredMethods(baseType);
|
||||
int cleared = 0;
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if ((!isInterface && Modifier.isPrivate(methods[i].getModifiers())) ||
|
||||
if (Modifier.isPrivate(methods[i].getModifiers()) ||
|
||||
hasPlainJavaAnnotationsOnly(methods[i]) ||
|
||||
getDeclaredAnnotations(methods[i], false).length == 0) {
|
||||
methods[i] = null;
|
||||
|
||||
@@ -475,7 +475,7 @@ public class TypeDescriptor implements Serializable {
|
||||
ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), otherDesc.getMapValueTypeDescriptor()));
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
return Arrays.equals(getResolvableType().getGenerics(), otherDesc.getResolvableType().getGenerics());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -330,10 +330,10 @@ public abstract class MimeTypeUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the given list of {@code MimeType} objects.
|
||||
* @param mimeTypes the string to parse
|
||||
* @return the list of mime types
|
||||
* @throws IllegalArgumentException if the String cannot be parsed
|
||||
* Generate a string representation of the given collection of {@link MimeType}
|
||||
* objects.
|
||||
* @param mimeTypes the {@code MimeType} objects
|
||||
* @return a string representation of the {@code MimeType} objects
|
||||
*/
|
||||
public static String toString(Collection<? extends MimeType> mimeTypes) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
@@ -348,21 +348,19 @@ public abstract class MimeTypeUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the given list of {@code MimeType} objects by
|
||||
* Sort the given list of {@code MimeType} objects by
|
||||
* {@linkplain MimeType#isMoreSpecific(MimeType) specificity}.
|
||||
*
|
||||
* <p>Because of the computational cost, this method throws an exception
|
||||
* when the given list contains too many elements.
|
||||
* <p>Because of the computational cost, this method throws an exception if
|
||||
* the given list contains too many elements.
|
||||
* @param mimeTypes the list of mime types to be sorted
|
||||
* @throws IllegalArgumentException if {@code mimeTypes} contains more
|
||||
* than 50 elements
|
||||
* @throws InvalidMimeTypeException if {@code mimeTypes} contains more than 50 elements
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.2">HTTP 1.1: Semantics
|
||||
* and Content, section 5.3.2</a>
|
||||
* @see MimeType#isMoreSpecific(MimeType)
|
||||
*/
|
||||
public static <T extends MimeType> void sortBySpecificity(List<T> mimeTypes) {
|
||||
Assert.notNull(mimeTypes, "'mimeTypes' must not be null");
|
||||
if (mimeTypes.size() >= 50) {
|
||||
if (mimeTypes.size() > 50) {
|
||||
throw new InvalidMimeTypeException(mimeTypes.toString(), "Too many elements");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,8 +23,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -133,9 +131,8 @@ public abstract class StreamUtils {
|
||||
Assert.notNull(charset, "No Charset specified");
|
||||
Assert.notNull(out, "No OutputStream specified");
|
||||
|
||||
Writer writer = new OutputStreamWriter(out, charset);
|
||||
writer.write(in);
|
||||
writer.flush();
|
||||
out.write(in.getBytes(charset));
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,6 +60,15 @@ class GeneratedFilesTests {
|
||||
.contains("Hello, World!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSourceFileWithJavaFileInTheDefaultPackageThrowsException() {
|
||||
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld").build();
|
||||
JavaFile javaFile = JavaFile.builder("", helloWorld).build();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.generatedFiles.addSourceFile(javaFile))
|
||||
.withMessage("Could not add 'HelloWorld', processing classes in the "
|
||||
+ "default package is not supported. Did you forget to add a package statement?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSourceFileWithCharSequenceAddsFile() throws Exception {
|
||||
this.generatedFiles.addSourceFile("com.example.HelloWorld", "{}");
|
||||
@@ -73,6 +82,14 @@ class GeneratedFilesTests {
|
||||
.withMessage("'className' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSourceFileWithCharSequenceWhenClassNameIsInTheDefaultPackageThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedFiles.addSourceFile("HelloWorld", "{}"))
|
||||
.withMessage("Could not add 'HelloWorld', processing classes in the "
|
||||
+ "default package is not supported. Did you forget to add a package statement?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSourceFileWithCharSequenceWhenClassNameIsInvalidThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
|
||||
+36
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -197,7 +197,7 @@ class AnnotationsScannerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeHierarchyStrategyOnClassWhenHasInterfaceDoesNotIncludeInterfaces() {
|
||||
void typeHierarchyStrategyOnClassWhenHasSingleInterfaceScansInterfaces() {
|
||||
Class<?> source = WithSingleInterface.class;
|
||||
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY)).containsExactly(
|
||||
"0:TestAnnotation1", "1:TestAnnotation2", "1:TestInheritedAnnotation2");
|
||||
@@ -353,10 +353,19 @@ class AnnotationsScannerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeHierarchyStrategyOnMethodWhenHasInterfaceDoesNotIncludeInterfaces() {
|
||||
void typeHierarchyStrategyOnMethodWhenHasInterfaceScansInterfaces() {
|
||||
Method source = methodFrom(WithSingleInterface.class);
|
||||
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY)).containsExactly(
|
||||
"0:TestAnnotation1", "1:TestAnnotation2", "1:TestInheritedAnnotation2");
|
||||
|
||||
source = methodFrom(Hello1Impl.class);
|
||||
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY)).containsExactly("1:TestAnnotation1");
|
||||
}
|
||||
|
||||
@Test // gh-31803
|
||||
void typeHierarchyStrategyOnMethodWhenHasInterfaceHierarchyScansInterfacesOnlyOnce() {
|
||||
Method source = methodFrom(Hello2Impl.class);
|
||||
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY)).containsExactly("1:TestAnnotation1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -691,6 +700,30 @@ class AnnotationsScannerTests {
|
||||
}
|
||||
}
|
||||
|
||||
interface Hello1 {
|
||||
|
||||
@TestAnnotation1
|
||||
void method();
|
||||
}
|
||||
|
||||
interface Hello2 extends Hello1 {
|
||||
}
|
||||
|
||||
static class Hello1Impl implements Hello1 {
|
||||
|
||||
@Override
|
||||
public void method() {
|
||||
}
|
||||
}
|
||||
|
||||
static class Hello2Impl implements Hello2 {
|
||||
|
||||
@Override
|
||||
public void method() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@TestAnnotation2
|
||||
@TestInheritedAnnotation2
|
||||
static class HierarchySuperclass extends HierarchySuperSuperclass {
|
||||
|
||||
+31
-25
@@ -40,6 +40,8 @@ import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationsScannerTests.Hello2Impl;
|
||||
import org.springframework.core.annotation.AnnotationsScannerTests.TestAnnotation1;
|
||||
import org.springframework.core.annotation.MergedAnnotation.Adapt;
|
||||
import org.springframework.core.annotation.MergedAnnotations.Search;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
@@ -679,16 +681,23 @@ class MergedAnnotationsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithTypeHierarchyInheritedFromInterfaceMethod()
|
||||
throws NoSuchMethodException {
|
||||
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod(
|
||||
"handleFromInterface");
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(method,
|
||||
SearchStrategy.TYPE_HIERARCHY).get(Order.class);
|
||||
void getWithTypeHierarchyInheritedFromInterfaceMethod() throws Exception {
|
||||
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleFromInterface");
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(method,SearchStrategy.TYPE_HIERARCHY).get(Order.class);
|
||||
assertThat(annotation.isPresent()).isTrue();
|
||||
assertThat(annotation.getAggregateIndex()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test // gh-31803
|
||||
void streamWithTypeHierarchyInheritedFromSuperInterfaceMethod() throws Exception {
|
||||
Method method = Hello2Impl.class.getMethod("method");
|
||||
long count = MergedAnnotations.search(SearchStrategy.TYPE_HIERARCHY)
|
||||
.from(method)
|
||||
.stream(TestAnnotation1.class)
|
||||
.count();
|
||||
assertThat(count).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithTypeHierarchyInheritedFromAbstractMethod() throws NoSuchMethodException {
|
||||
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handle");
|
||||
@@ -1384,7 +1393,7 @@ class MergedAnnotationsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRepeatableDeclaredOnMethod() throws Exception {
|
||||
void streamRepeatableDeclaredOnMethod() throws Exception {
|
||||
Method method = InterfaceWithRepeated.class.getMethod("foo");
|
||||
Stream<MergedAnnotation<MyRepeatable>> annotations = MergedAnnotations.from(
|
||||
method, SearchStrategy.TYPE_HIERARCHY).stream(MyRepeatable.class);
|
||||
@@ -1395,7 +1404,7 @@ class MergedAnnotationsTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void getRepeatableDeclaredOnClassWithAttributeAliases() {
|
||||
void streamRepeatableDeclaredOnClassWithAttributeAliases() {
|
||||
assertThat(MergedAnnotations.from(HierarchyClass.class).stream(
|
||||
TestConfiguration.class)).isEmpty();
|
||||
RepeatableContainers containers = RepeatableContainers.of(TestConfiguration.class,
|
||||
@@ -1409,7 +1418,7 @@ class MergedAnnotationsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRepeatableDeclaredOnClass() {
|
||||
void streamRepeatableDeclaredOnClass() {
|
||||
Class<?> element = MyRepeatableClass.class;
|
||||
String[] expectedValuesJava = { "A", "B", "C" };
|
||||
String[] expectedValuesSpring = { "A", "B", "C", "meta1" };
|
||||
@@ -1417,7 +1426,7 @@ class MergedAnnotationsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRepeatableDeclaredOnSuperclass() {
|
||||
void streamRepeatableDeclaredOnSuperclass() {
|
||||
Class<?> element = SubMyRepeatableClass.class;
|
||||
String[] expectedValuesJava = { "A", "B", "C" };
|
||||
String[] expectedValuesSpring = { "A", "B", "C", "meta1" };
|
||||
@@ -1425,7 +1434,7 @@ class MergedAnnotationsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRepeatableDeclaredOnClassAndSuperclass() {
|
||||
void streamRepeatableDeclaredOnClassAndSuperclass() {
|
||||
Class<?> element = SubMyRepeatableWithAdditionalLocalDeclarationsClass.class;
|
||||
String[] expectedValuesJava = { "X", "Y", "Z" };
|
||||
String[] expectedValuesSpring = { "X", "Y", "Z", "meta2" };
|
||||
@@ -1433,7 +1442,7 @@ class MergedAnnotationsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRepeatableDeclaredOnMultipleSuperclasses() {
|
||||
void streamRepeatableDeclaredOnMultipleSuperclasses() {
|
||||
Class<?> element = SubSubMyRepeatableWithAdditionalLocalDeclarationsClass.class;
|
||||
String[] expectedValuesJava = { "X", "Y", "Z" };
|
||||
String[] expectedValuesSpring = { "X", "Y", "Z", "meta2" };
|
||||
@@ -1441,7 +1450,7 @@ class MergedAnnotationsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDirectRepeatablesDeclaredOnClass() {
|
||||
void streamDirectRepeatablesDeclaredOnClass() {
|
||||
Class<?> element = MyRepeatableClass.class;
|
||||
String[] expectedValuesJava = { "A", "B", "C" };
|
||||
String[] expectedValuesSpring = { "A", "B", "C", "meta1" };
|
||||
@@ -1449,7 +1458,7 @@ class MergedAnnotationsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDirectRepeatablesDeclaredOnSuperclass() {
|
||||
void streamDirectRepeatablesDeclaredOnSuperclass() {
|
||||
Class<?> element = SubMyRepeatableClass.class;
|
||||
String[] expectedValuesJava = {};
|
||||
String[] expectedValuesSpring = {};
|
||||
@@ -1476,20 +1485,17 @@ class MergedAnnotationsTests {
|
||||
MergedAnnotations annotations = MergedAnnotations.from(element, searchStrategy,
|
||||
RepeatableContainers.of(MyRepeatable.class, MyRepeatableContainer.class),
|
||||
AnnotationFilter.PLAIN);
|
||||
assertThat(annotations.stream(MyRepeatable.class).filter(
|
||||
MergedAnnotationPredicates.firstRunOf(
|
||||
MergedAnnotation::getAggregateIndex)).map(
|
||||
annotation -> annotation.getString(
|
||||
"value"))).containsExactly(expected);
|
||||
Stream<String> values = annotations.stream(MyRepeatable.class)
|
||||
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
|
||||
.map(annotation -> annotation.getString("value"));
|
||||
assertThat(values).containsExactly(expected);
|
||||
}
|
||||
|
||||
private void testStandardRepeatables(SearchStrategy searchStrategy, Class<?> element, String[] expected) {
|
||||
MergedAnnotations annotations = MergedAnnotations.from(element, searchStrategy);
|
||||
assertThat(annotations.stream(MyRepeatable.class).filter(
|
||||
MergedAnnotationPredicates.firstRunOf(
|
||||
MergedAnnotation::getAggregateIndex)).map(
|
||||
annotation -> annotation.getString(
|
||||
"value"))).containsExactly(expected);
|
||||
Stream<String> values = MergedAnnotations.from(element, searchStrategy).stream(MyRepeatable.class)
|
||||
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
|
||||
.map(annotation -> annotation.getString("value"));
|
||||
assertThat(values).containsExactly(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+23
-10
@@ -34,11 +34,13 @@ import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
@@ -663,12 +665,12 @@ class TypeDescriptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void upCast() throws Exception {
|
||||
void upcast() throws Exception {
|
||||
Property property = new Property(getClass(), getClass().getMethod("getProperty"),
|
||||
getClass().getMethod("setProperty", Map.class));
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(property);
|
||||
TypeDescriptor upCast = typeDescriptor.upcast(Object.class);
|
||||
assertThat(upCast.getAnnotation(MethodAnnotation1.class)).isNotNull();
|
||||
TypeDescriptor upcast = typeDescriptor.upcast(Object.class);
|
||||
assertThat(upcast.getAnnotation(MethodAnnotation1.class)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -682,7 +684,7 @@ class TypeDescriptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void elementTypeForCollectionSubclass() throws Exception {
|
||||
void elementTypeForCollectionSubclass() {
|
||||
@SuppressWarnings("serial")
|
||||
class CustomSet extends HashSet<String> {
|
||||
}
|
||||
@@ -692,7 +694,7 @@ class TypeDescriptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void elementTypeForMapSubclass() throws Exception {
|
||||
void elementTypeForMapSubclass() {
|
||||
@SuppressWarnings("serial")
|
||||
class CustomMap extends HashMap<String, Integer> {
|
||||
}
|
||||
@@ -704,7 +706,7 @@ class TypeDescriptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createMapArray() throws Exception {
|
||||
void createMapArray() {
|
||||
TypeDescriptor mapType = TypeDescriptor.map(
|
||||
LinkedHashMap.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class));
|
||||
TypeDescriptor arrayType = TypeDescriptor.array(mapType);
|
||||
@@ -713,13 +715,13 @@ class TypeDescriptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createStringArray() throws Exception {
|
||||
void createStringArray() {
|
||||
TypeDescriptor arrayType = TypeDescriptor.array(TypeDescriptor.valueOf(String.class));
|
||||
assertThat(TypeDescriptor.valueOf(String[].class)).isEqualTo(arrayType);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createNullArray() throws Exception {
|
||||
void createNullArray() {
|
||||
assertThat((Object) TypeDescriptor.array(null)).isNull();
|
||||
}
|
||||
|
||||
@@ -736,13 +738,13 @@ class TypeDescriptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createCollectionWithNullElement() throws Exception {
|
||||
void createCollectionWithNullElement() {
|
||||
TypeDescriptor typeDescriptor = TypeDescriptor.collection(List.class, null);
|
||||
assertThat(typeDescriptor.getElementTypeDescriptor()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createMapWithNullElements() throws Exception {
|
||||
void createMapWithNullElements() {
|
||||
TypeDescriptor typeDescriptor = TypeDescriptor.map(LinkedHashMap.class, null, null);
|
||||
assertThat(typeDescriptor.getMapKeyTypeDescriptor()).isNull();
|
||||
assertThat(typeDescriptor.getMapValueTypeDescriptor()).isNull();
|
||||
@@ -757,6 +759,17 @@ class TypeDescriptorTests {
|
||||
assertThat(TypeDescriptor.valueOf(Integer.class).getSource()).isEqualTo(Integer.class);
|
||||
}
|
||||
|
||||
@Test // gh-31672
|
||||
void equalityWithGenerics() {
|
||||
ResolvableType rt1 = ResolvableType.forClassWithGenerics(Optional.class, Integer.class);
|
||||
ResolvableType rt2 = ResolvableType.forClassWithGenerics(Optional.class, String.class);
|
||||
|
||||
TypeDescriptor td1 = new TypeDescriptor(rt1, null, null);
|
||||
TypeDescriptor td2 = new TypeDescriptor(rt2, null, null);
|
||||
|
||||
assertThat(td1).isNotEqualTo(td2);
|
||||
}
|
||||
|
||||
|
||||
// Methods designed for test introspection
|
||||
|
||||
|
||||
+35
-36
@@ -117,6 +117,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
||||
TypeConverter typeConverter = context.getTypeConverter();
|
||||
Class<?> type = (targetObject instanceof Class<?> clazz ? clazz : targetObject.getClass());
|
||||
ArrayList<Method> methods = new ArrayList<>(getMethods(type, targetObject));
|
||||
methods.removeIf(method -> !method.getName().equals(name));
|
||||
|
||||
// If a filter is registered for this type, call it
|
||||
MethodFilter filter = (this.filters != null ? this.filters.get(type) : null);
|
||||
@@ -160,48 +161,46 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
||||
boolean multipleOptions = false;
|
||||
|
||||
for (Method method : methodsToIterate) {
|
||||
if (method.getName().equals(name)) {
|
||||
int paramCount = method.getParameterCount();
|
||||
List<TypeDescriptor> paramDescriptors = new ArrayList<>(paramCount);
|
||||
for (int i = 0; i < paramCount; i++) {
|
||||
paramDescriptors.add(new TypeDescriptor(new MethodParameter(method, i)));
|
||||
int paramCount = method.getParameterCount();
|
||||
List<TypeDescriptor> paramDescriptors = new ArrayList<>(paramCount);
|
||||
for (int i = 0; i < paramCount; i++) {
|
||||
paramDescriptors.add(new TypeDescriptor(new MethodParameter(method, i)));
|
||||
}
|
||||
ReflectionHelper.ArgumentsMatchInfo matchInfo = null;
|
||||
if (method.isVarArgs() && argumentTypes.size() >= (paramCount - 1)) {
|
||||
// *sigh* complicated
|
||||
matchInfo = ReflectionHelper.compareArgumentsVarargs(paramDescriptors, argumentTypes, typeConverter);
|
||||
}
|
||||
else if (paramCount == argumentTypes.size()) {
|
||||
// Name and parameter number match, check the arguments
|
||||
matchInfo = ReflectionHelper.compareArguments(paramDescriptors, argumentTypes, typeConverter);
|
||||
}
|
||||
if (matchInfo != null) {
|
||||
if (matchInfo.isExactMatch()) {
|
||||
return new ReflectiveMethodExecutor(method, type);
|
||||
}
|
||||
ReflectionHelper.ArgumentsMatchInfo matchInfo = null;
|
||||
if (method.isVarArgs() && argumentTypes.size() >= (paramCount - 1)) {
|
||||
// *sigh* complicated
|
||||
matchInfo = ReflectionHelper.compareArgumentsVarargs(paramDescriptors, argumentTypes, typeConverter);
|
||||
}
|
||||
else if (paramCount == argumentTypes.size()) {
|
||||
// Name and parameter number match, check the arguments
|
||||
matchInfo = ReflectionHelper.compareArguments(paramDescriptors, argumentTypes, typeConverter);
|
||||
}
|
||||
if (matchInfo != null) {
|
||||
if (matchInfo.isExactMatch()) {
|
||||
return new ReflectiveMethodExecutor(method, type);
|
||||
}
|
||||
else if (matchInfo.isCloseMatch()) {
|
||||
if (this.useDistance) {
|
||||
int matchDistance = ReflectionHelper.getTypeDifferenceWeight(paramDescriptors, argumentTypes);
|
||||
if (closeMatch == null || matchDistance < closeMatchDistance) {
|
||||
// This is a better match...
|
||||
closeMatch = method;
|
||||
closeMatchDistance = matchDistance;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Take this as a close match if there isn't one already
|
||||
if (closeMatch == null) {
|
||||
closeMatch = method;
|
||||
}
|
||||
else if (matchInfo.isCloseMatch()) {
|
||||
if (this.useDistance) {
|
||||
int matchDistance = ReflectionHelper.getTypeDifferenceWeight(paramDescriptors, argumentTypes);
|
||||
if (closeMatch == null || matchDistance < closeMatchDistance) {
|
||||
// This is a better match...
|
||||
closeMatch = method;
|
||||
closeMatchDistance = matchDistance;
|
||||
}
|
||||
}
|
||||
else if (matchInfo.isMatchRequiringConversion()) {
|
||||
if (matchRequiringConversion != null) {
|
||||
multipleOptions = true;
|
||||
else {
|
||||
// Take this as a close match if there isn't one already
|
||||
if (closeMatch == null) {
|
||||
closeMatch = method;
|
||||
}
|
||||
matchRequiringConversion = method;
|
||||
}
|
||||
}
|
||||
else if (matchInfo.isMatchRequiringConversion()) {
|
||||
if (matchRequiringConversion != null) {
|
||||
multipleOptions = true;
|
||||
}
|
||||
matchRequiringConversion = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (closeMatch != null) {
|
||||
|
||||
+4
-3
@@ -120,9 +120,10 @@ public class StandardTypeLocator implements TypeLocator {
|
||||
return cachedType;
|
||||
}
|
||||
Class<?> loadedType = loadType(typeName);
|
||||
if (loadedType != null &&
|
||||
!(this.classLoader instanceof SmartClassLoader scl && scl.isClassReloadable(loadedType))) {
|
||||
this.typeCache.put(typeName, loadedType);
|
||||
if (loadedType != null) {
|
||||
if (!(this.classLoader instanceof SmartClassLoader scl && scl.isClassReloadable(loadedType))) {
|
||||
this.typeCache.put(typeName, loadedType);
|
||||
}
|
||||
return loadedType;
|
||||
}
|
||||
throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
|
||||
|
||||
@@ -60,7 +60,7 @@ import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* <b>This is the central class in the JDBC core package.</b>
|
||||
* <b>This is the central delegate in the JDBC core package.</b>
|
||||
* It simplifies the use of JDBC and helps to avoid common errors.
|
||||
* It executes core JDBC workflow, leaving application code to provide SQL
|
||||
* and extract results. This class executes SQL queries or updates, initiating
|
||||
|
||||
+20
-20
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -43,7 +43,7 @@ public interface CallMetaDataProvider {
|
||||
|
||||
/**
|
||||
* Initialize the database specific management of procedure column meta-data.
|
||||
* This is only called for databases that are supported. This initialization
|
||||
* <p>This is only called for databases that are supported. This initialization
|
||||
* can be turned off by specifying that column meta-data should not be used.
|
||||
* @param databaseMetaData used to retrieve database specific information
|
||||
* @param catalogName name of catalog to use (or {@code null} if none)
|
||||
@@ -55,30 +55,36 @@ public interface CallMetaDataProvider {
|
||||
void initializeWithProcedureColumnMetaData(DatabaseMetaData databaseMetaData, @Nullable String catalogName,
|
||||
@Nullable String schemaName, @Nullable String procedureName) throws SQLException;
|
||||
|
||||
/**
|
||||
* Get the call parameter meta-data that is currently used.
|
||||
* @return a List of {@link CallParameterMetaData}
|
||||
*/
|
||||
List<CallParameterMetaData> getCallParameterMetaData();
|
||||
|
||||
/**
|
||||
* Provide any modification of the procedure name passed in to match the meta-data currently used.
|
||||
* This could include altering the case.
|
||||
* <p>This could include altering the case.
|
||||
*/
|
||||
@Nullable
|
||||
String procedureNameToUse(@Nullable String procedureName);
|
||||
|
||||
/**
|
||||
* Provide any modification of the catalog name passed in to match the meta-data currently used.
|
||||
* This could include altering the case.
|
||||
* <p>This could include altering the case.
|
||||
*/
|
||||
@Nullable
|
||||
String catalogNameToUse(@Nullable String catalogName);
|
||||
|
||||
/**
|
||||
* Provide any modification of the schema name passed in to match the meta-data currently used.
|
||||
* This could include altering the case.
|
||||
* <p>This could include altering the case.
|
||||
*/
|
||||
@Nullable
|
||||
String schemaNameToUse(@Nullable String schemaName);
|
||||
|
||||
/**
|
||||
* Provide any modification of the catalog name passed in to match the meta-data currently used.
|
||||
* The returned value will be used for meta-data lookups. This could include altering the case
|
||||
* <p>The returned value will be used for meta-data lookups. This could include altering the case
|
||||
* used or providing a base catalog if none is provided.
|
||||
*/
|
||||
@Nullable
|
||||
@@ -86,7 +92,7 @@ public interface CallMetaDataProvider {
|
||||
|
||||
/**
|
||||
* Provide any modification of the schema name passed in to match the meta-data currently used.
|
||||
* The returned value will be used for meta-data lookups. This could include altering the case
|
||||
* <p>The returned value will be used for meta-data lookups. This could include altering the case
|
||||
* used or providing a base schema if none is provided.
|
||||
*/
|
||||
@Nullable
|
||||
@@ -94,7 +100,7 @@ public interface CallMetaDataProvider {
|
||||
|
||||
/**
|
||||
* Provide any modification of the column name passed in to match the meta-data currently used.
|
||||
* This could include altering the case.
|
||||
* <p>This could include altering the case.
|
||||
* @param parameterName name of the parameter of column
|
||||
*/
|
||||
@Nullable
|
||||
@@ -102,7 +108,7 @@ public interface CallMetaDataProvider {
|
||||
|
||||
/**
|
||||
* Create a default out parameter based on the provided meta-data.
|
||||
* This is used when no explicit parameter declaration has been made.
|
||||
* <p>This is used when no explicit parameter declaration has been made.
|
||||
* @param parameterName the name of the parameter
|
||||
* @param meta meta-data used for this call
|
||||
* @return the configured SqlOutParameter
|
||||
@@ -111,7 +117,7 @@ public interface CallMetaDataProvider {
|
||||
|
||||
/**
|
||||
* Create a default in/out parameter based on the provided meta-data.
|
||||
* This is used when no explicit parameter declaration has been made.
|
||||
* <p>This is used when no explicit parameter declaration has been made.
|
||||
* @param parameterName the name of the parameter
|
||||
* @param meta meta-data used for this call
|
||||
* @return the configured SqlInOutParameter
|
||||
@@ -120,7 +126,7 @@ public interface CallMetaDataProvider {
|
||||
|
||||
/**
|
||||
* Create a default in parameter based on the provided meta-data.
|
||||
* This is used when no explicit parameter declaration has been made.
|
||||
* <p>This is used when no explicit parameter declaration has been made.
|
||||
* @param parameterName the name of the parameter
|
||||
* @param meta meta-data used for this call
|
||||
* @return the configured SqlParameter
|
||||
@@ -142,7 +148,7 @@ public interface CallMetaDataProvider {
|
||||
|
||||
/**
|
||||
* Does this database support returning ResultSets as ref cursors to be retrieved with
|
||||
* {@link java.sql.CallableStatement#getObject(int)} for the specified column.
|
||||
* {@link java.sql.CallableStatement#getObject(int)} for the specified column?
|
||||
*/
|
||||
boolean isRefCursorSupported();
|
||||
|
||||
@@ -158,18 +164,12 @@ public interface CallMetaDataProvider {
|
||||
boolean isProcedureColumnMetaDataUsed();
|
||||
|
||||
/**
|
||||
* Should we bypass the return parameter with the specified name.
|
||||
* This allows the database specific implementation to skip the processing
|
||||
* Should we bypass the return parameter with the specified name?
|
||||
* <p>This allows the database specific implementation to skip the processing
|
||||
* for specific results returned by the database call.
|
||||
*/
|
||||
boolean byPassReturnParameter(String parameterName);
|
||||
|
||||
/**
|
||||
* Get the call parameter meta-data that is currently used.
|
||||
* @return a List of {@link CallParameterMetaData}
|
||||
*/
|
||||
List<CallParameterMetaData> getCallParameterMetaData();
|
||||
|
||||
/**
|
||||
* Does the database support the use of catalog name in procedure calls?
|
||||
*/
|
||||
|
||||
+5
-5
@@ -168,11 +168,6 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
|
||||
return identifierNameToUse(parameterName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean byPassReturnParameter(String parameterName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlParameter createDefaultOutParameter(String parameterName, CallParameterMetaData meta) {
|
||||
return new SqlOutParameter(parameterName, meta.getSqlType());
|
||||
@@ -213,6 +208,11 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
|
||||
return this.procedureColumnMetaDataUsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean byPassReturnParameter(String parameterName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify whether the database supports the use of catalog name in procedure calls.
|
||||
|
||||
+5
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -75,10 +75,10 @@ public abstract class AbstractDataSource implements DataSource {
|
||||
throw new UnsupportedOperationException("setLogWriter");
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Implementation of JDBC 4.0's Wrapper interface
|
||||
//---------------------------------------------------------------------
|
||||
@Override
|
||||
public Logger getParentLogger() {
|
||||
return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -95,14 +95,4 @@ public abstract class AbstractDataSource implements DataSource {
|
||||
return iface.isInstance(this);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Implementation of JDBC 4.1's getParentLogger method
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public Logger getParentLogger() {
|
||||
return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
-23
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -104,6 +104,16 @@ public class DelegatingDataSource implements DataSource, InitializingBean {
|
||||
return obtainTargetDataSource().getConnection(username, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLoginTimeout() throws SQLException {
|
||||
return obtainTargetDataSource().getLoginTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoginTimeout(int seconds) throws SQLException {
|
||||
obtainTargetDataSource().setLoginTimeout(seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getLogWriter() throws SQLException {
|
||||
return obtainTargetDataSource().getLogWriter();
|
||||
@@ -115,20 +125,10 @@ public class DelegatingDataSource implements DataSource, InitializingBean {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLoginTimeout() throws SQLException {
|
||||
return obtainTargetDataSource().getLoginTimeout();
|
||||
public Logger getParentLogger() {
|
||||
return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoginTimeout(int seconds) throws SQLException {
|
||||
obtainTargetDataSource().setLoginTimeout(seconds);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Implementation of JDBC 4.0's Wrapper interface
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||
@@ -143,14 +143,4 @@ public class DelegatingDataSource implements DataSource, InitializingBean {
|
||||
return (iface.isInstance(this) || obtainTargetDataSource().isWrapperFor(iface));
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Implementation of JDBC 4.1's getParentLogger method
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public Logger getParentLogger() {
|
||||
return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -186,7 +186,7 @@ public class TransactionAwareDataSourceProxy extends DelegatingDataSource {
|
||||
// Allow for differentiating between the proxy and the raw Connection.
|
||||
StringBuilder sb = new StringBuilder("Transaction-aware proxy for target Connection ");
|
||||
if (this.target != null) {
|
||||
sb.append('[').append(this.target.toString()).append(']');
|
||||
sb.append('[').append(this.target).append(']');
|
||||
}
|
||||
else {
|
||||
sb.append(" from DataSource [").append(this.targetDataSource).append(']');
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -61,7 +61,7 @@ public abstract class AbstractRoutingDataSource extends AbstractDataSource imple
|
||||
|
||||
/**
|
||||
* Specify the map of target DataSources, with the lookup key as key.
|
||||
* The mapped value can either be a corresponding {@link javax.sql.DataSource}
|
||||
* <p>The mapped value can either be a corresponding {@link javax.sql.DataSource}
|
||||
* instance or a data source name String (to be resolved via a
|
||||
* {@link #setDataSourceLookup DataSourceLookup}).
|
||||
* <p>The key can be of arbitrary type; this class implements the
|
||||
@@ -213,6 +213,7 @@ public abstract class AbstractRoutingDataSource extends AbstractDataSource imple
|
||||
return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the current target DataSource. Determines the
|
||||
* {@link #determineCurrentLookupKey() current lookup key}, performs
|
||||
|
||||
@@ -88,6 +88,7 @@ public abstract class SharedEntityManagerCreator {
|
||||
"getResultStream", // jakarta.persistence.Query.getResultStream()
|
||||
"getResultList", // jakarta.persistence.Query.getResultList()
|
||||
"list", // org.hibernate.query.Query.list()
|
||||
"scroll", // org.hibernate.query.Query.scroll()
|
||||
"stream", // org.hibernate.query.Query.stream()
|
||||
"uniqueResult", // org.hibernate.query.Query.uniqueResult()
|
||||
"uniqueResultOptional" // org.hibernate.query.Query.uniqueResultOptional()
|
||||
|
||||
+5
-5
@@ -353,6 +353,11 @@ public class PersistenceAnnotationBeanPostProcessor implements InstantiationAwar
|
||||
findInjectionMetadata(beanDefinition, beanType, beanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBeanDefinition(String beanName) {
|
||||
this.injectionMetadataCache.remove(beanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
@@ -373,11 +378,6 @@ public class PersistenceAnnotationBeanPostProcessor implements InstantiationAwar
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBeanDefinition(String beanName) {
|
||||
this.injectionMetadataCache.remove(beanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
|
||||
InjectionMetadata metadata = findPersistenceMetadata(beanName, bean.getClass(), pvs);
|
||||
|
||||
@@ -81,6 +81,7 @@ dependencies {
|
||||
testImplementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8")
|
||||
testImplementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
||||
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
testImplementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv")
|
||||
testImplementation("com.squareup.okhttp3:mockwebserver")
|
||||
testImplementation("io.micrometer:micrometer-observation-test")
|
||||
testImplementation("io.projectreactor:reactor-test")
|
||||
|
||||
+1
-1
@@ -205,8 +205,8 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple
|
||||
.doOnNext(dataBuffer -> Hints.touchDataBuffer(dataBuffer, hintsToUse, logger))
|
||||
.doAfterTerminate(() -> {
|
||||
try {
|
||||
byteBuilder.release();
|
||||
generator.close();
|
||||
byteBuilder.release();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.error("Could not close Encoder resources", ex);
|
||||
|
||||
+11
-10
@@ -91,10 +91,12 @@ final class Jackson2Tokenizer {
|
||||
private List<TokenBuffer> tokenize(DataBuffer dataBuffer) {
|
||||
try {
|
||||
int bufferSize = dataBuffer.readableByteCount();
|
||||
List<TokenBuffer> tokens = new ArrayList<>();
|
||||
if (this.inputFeeder instanceof ByteBufferFeeder byteBufferFeeder) {
|
||||
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) {
|
||||
while (iterator.hasNext()) {
|
||||
byteBufferFeeder.feedInput(iterator.next());
|
||||
parseTokens(tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,10 +104,10 @@ final class Jackson2Tokenizer {
|
||||
byte[] bytes = new byte[bufferSize];
|
||||
dataBuffer.read(bytes);
|
||||
byteArrayFeeder.feedInput(bytes, 0, bufferSize);
|
||||
parseTokens(tokens);
|
||||
}
|
||||
List<TokenBuffer> result = parseTokenBufferFlux();
|
||||
assertInMemorySize(bufferSize, result);
|
||||
return result;
|
||||
assertInMemorySize(bufferSize, tokens);
|
||||
return tokens;
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new DecodingException("JSON decoding error: " + ex.getOriginalMessage(), ex);
|
||||
@@ -122,7 +124,9 @@ final class Jackson2Tokenizer {
|
||||
return Flux.defer(() -> {
|
||||
this.inputFeeder.endOfInput();
|
||||
try {
|
||||
return Flux.fromIterable(parseTokenBufferFlux());
|
||||
List<TokenBuffer> tokens = new ArrayList<>();
|
||||
parseTokens(tokens);
|
||||
return Flux.fromIterable(tokens);
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new DecodingException("JSON decoding error: " + ex.getOriginalMessage(), ex);
|
||||
@@ -133,9 +137,7 @@ final class Jackson2Tokenizer {
|
||||
});
|
||||
}
|
||||
|
||||
private List<TokenBuffer> parseTokenBufferFlux() throws IOException {
|
||||
List<TokenBuffer> result = new ArrayList<>();
|
||||
|
||||
private void parseTokens(List<TokenBuffer> tokens) throws IOException {
|
||||
// SPR-16151: Smile data format uses null to separate documents
|
||||
boolean previousNull = false;
|
||||
while (!this.parser.isClosed()) {
|
||||
@@ -153,13 +155,12 @@ final class Jackson2Tokenizer {
|
||||
}
|
||||
updateDepth(token);
|
||||
if (!this.tokenizeArrayElements) {
|
||||
processTokenNormal(token, result);
|
||||
processTokenNormal(token, tokens);
|
||||
}
|
||||
else {
|
||||
processTokenArray(token, result);
|
||||
processTokenArray(token, tokens);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void updateDepth(JsonToken token) {
|
||||
|
||||
+1
-1
@@ -540,7 +540,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
|
||||
while ((prev = this.queue.pollLast()) != null) {
|
||||
int prevByteCount = prev.readableByteCount();
|
||||
int prevLen = prevByteCount + len;
|
||||
if (prevLen > 0) {
|
||||
if (prevLen >= 0) {
|
||||
// slice body part of previous buffer, and flush it
|
||||
DataBuffer body = prev.split(prevLen + prev.readPosition());
|
||||
DataBufferUtils.release(prev);
|
||||
|
||||
+19
-14
@@ -121,16 +121,17 @@ public class ServerHttpObservationFilter implements WebFilter {
|
||||
DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doOnSubscription() throws Throwable {
|
||||
this.observation.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context addToContext(Context originalContext) {
|
||||
return originalContext.put(ObservationThreadLocalAccessor.KEY, this.observation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFirst() throws Throwable {
|
||||
this.observation.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doOnCancel() throws Throwable {
|
||||
if (this.observationRecorded.compareAndSet(false, true)) {
|
||||
@@ -142,16 +143,7 @@ public class ServerHttpObservationFilter implements WebFilter {
|
||||
@Override
|
||||
public void doOnComplete() throws Throwable {
|
||||
if (this.observationRecorded.compareAndSet(false, true)) {
|
||||
ServerHttpResponse response = this.observationContext.getResponse();
|
||||
if (response.isCommitted()) {
|
||||
this.observation.stop();
|
||||
}
|
||||
else {
|
||||
response.beforeCommit(() -> {
|
||||
this.observation.stop();
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
doOnTerminate(this.observationContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,8 +154,21 @@ public class ServerHttpObservationFilter implements WebFilter {
|
||||
this.observationContext.setConnectionAborted(true);
|
||||
}
|
||||
this.observationContext.setError(error);
|
||||
doOnTerminate(this.observationContext);
|
||||
}
|
||||
}
|
||||
|
||||
private void doOnTerminate(ServerRequestObservationContext context) {
|
||||
ServerHttpResponse response = context.getResponse();
|
||||
if (response.isCommitted()) {
|
||||
this.observation.stop();
|
||||
}
|
||||
else {
|
||||
response.beforeCommit(() -> {
|
||||
this.observation.stop();
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+40
-10
@@ -27,6 +27,10 @@ import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.core.TreeNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.util.TokenBuffer;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -39,6 +43,7 @@ import reactor.test.StepVerifier;
|
||||
import org.springframework.core.codec.DecodingException;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferLimitException;
|
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory;
|
||||
import org.springframework.core.testfixture.io.buffer.AbstractLeakCheckingTests;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
@@ -345,22 +350,47 @@ public class Jackson2TokenizerTests extends AbstractLeakCheckingTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
// gh-31747
|
||||
@Test
|
||||
public void compositeNettyBuffer() {
|
||||
ByteBufAllocator allocator = UnpooledByteBufAllocator.DEFAULT;
|
||||
ByteBuf firstByteBuf = allocator.buffer();
|
||||
firstByteBuf.writeBytes("{\"foo\": \"foofoo\"".getBytes(StandardCharsets.UTF_8));
|
||||
ByteBuf secondBuf = allocator.buffer();
|
||||
secondBuf.writeBytes(", \"bar\": \"barbar\"}".getBytes(StandardCharsets.UTF_8));
|
||||
CompositeByteBuf composite = allocator.compositeBuffer();
|
||||
composite.addComponent(true, firstByteBuf);
|
||||
composite.addComponent(true, secondBuf);
|
||||
|
||||
NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(allocator);
|
||||
Flux<DataBuffer> source = Flux.just(bufferFactory.wrap(composite));
|
||||
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(source, this.jsonFactory, this.objectMapper, false, false, -1);
|
||||
|
||||
Flux<String> strings = tokens.map(this::tokenToString);
|
||||
|
||||
StepVerifier.create(strings)
|
||||
.assertNext(s -> assertThat(s).isEqualTo("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}"))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
|
||||
private Flux<String> decode(List<String> source, boolean tokenize, int maxInMemorySize) {
|
||||
|
||||
Flux<TokenBuffer> tokens = Jackson2Tokenizer.tokenize(
|
||||
Flux.fromIterable(source).map(this::stringBuffer),
|
||||
this.jsonFactory, this.objectMapper, tokenize, false, maxInMemorySize);
|
||||
|
||||
return tokens
|
||||
.map(tokenBuffer -> {
|
||||
try {
|
||||
TreeNode root = this.objectMapper.readTree(tokenBuffer.asParser());
|
||||
return this.objectMapper.writeValueAsString(root);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
});
|
||||
return tokens.map(this::tokenToString);
|
||||
}
|
||||
|
||||
private String tokenToString(TokenBuffer tokenBuffer) {
|
||||
try {
|
||||
TreeNode root = this.objectMapper.readTree(tokenBuffer.asParser());
|
||||
return this.objectMapper.writeValueAsString(root);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private DataBuffer stringBuffer(String value) {
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.codec.json;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.testfixture.codec.AbstractEncoderTests;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.web.testfixture.xml.Pojo;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link AbstractJackson2Encoder} for the CSV variant and how resources are managed.
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class JacksonCsvEncoderTests extends AbstractEncoderTests<org.springframework.http.codec.json.JacksonCsvEncoderTests.JacksonCsvEncoder> {
|
||||
|
||||
public JacksonCsvEncoderTests() {
|
||||
super(new JacksonCsvEncoder());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void canEncode() throws Exception {
|
||||
ResolvableType pojoType = ResolvableType.forClass(Pojo.class);
|
||||
assertThat(this.encoder.canEncode(pojoType, JacksonCsvEncoder.TEXT_CSV)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void encode() throws Exception {
|
||||
Flux<Object> input = Flux.just(new Pojo("spring", "framework"),
|
||||
new Pojo("spring", "data"),
|
||||
new Pojo("spring", "boot"));
|
||||
|
||||
testEncode(input, Pojo.class, step -> step
|
||||
.consumeNextWith(expectString("bar,foo\nframework,spring\n"))
|
||||
.consumeNextWith(expectString("data,spring\n"))
|
||||
.consumeNextWith(expectString("boot,spring\n"))
|
||||
.verifyComplete());
|
||||
}
|
||||
|
||||
@Test
|
||||
// See gh-30493
|
||||
// this test did not fail directly but logged a NullPointerException dropped by the reactive pipeline
|
||||
void encodeEmptyFlux() {
|
||||
Flux<Object> input = Flux.empty();
|
||||
testEncode(input, Pojo.class, step -> step.verifyComplete());
|
||||
}
|
||||
|
||||
static class JacksonCsvEncoder extends AbstractJackson2Encoder {
|
||||
public static final MediaType TEXT_CSV = new MediaType("text", "csv");
|
||||
|
||||
public JacksonCsvEncoder() {
|
||||
this(CsvMapper.builder().build(), TEXT_CSV);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] getStreamingMediaTypeSeparator(MimeType mimeType) {
|
||||
// CsvMapper emits newlines
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public JacksonCsvEncoder(ObjectMapper mapper, MimeType... mimeTypes) {
|
||||
super(mapper, mimeTypes);
|
||||
Assert.isInstanceOf(CsvMapper.class, mapper);
|
||||
setStreamingMediaTypes(List.of(TEXT_CSV));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObjectWriter customizeWriter(ObjectWriter writer, MimeType mimeType, ResolvableType elementType, Map<String, Object> hints) {
|
||||
var mapper = (CsvMapper) getObjectMapper();
|
||||
return writer.with(mapper.schemaFor(elementType.toClass()).withHeader());
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -301,6 +301,23 @@ class DefaultPartHttpMessageReaderTests {
|
||||
latch.await();
|
||||
}
|
||||
|
||||
@ParameterizedDefaultPartHttpMessageReaderTest
|
||||
void emptyLastPart(DefaultPartHttpMessageReader reader) throws InterruptedException {
|
||||
MockServerHttpRequest request = createRequest(
|
||||
new ClassPathResource("empty-part.multipart", getClass()), "LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD");
|
||||
|
||||
Flux<Part> result = reader.read(forClass(Part.class), request, emptyMap());
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(part -> testPart(part, null, "", latch))
|
||||
.consumeNextWith(part -> testPart(part, null, "", latch))
|
||||
.verifyComplete();
|
||||
|
||||
latch.await();
|
||||
}
|
||||
|
||||
|
||||
private void testBrowser(DefaultPartHttpMessageReader reader, Resource resource, String boundary)
|
||||
throws InterruptedException {
|
||||
|
||||
|
||||
+26
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.web.filter.reactive;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
@@ -27,6 +28,7 @@ import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.http.server.reactive.observation.ServerRequestObservationContext;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
@@ -65,7 +67,10 @@ class ServerHttpObservationFilterTests {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource"));
|
||||
exchange.getResponse().setRawStatusCode(200);
|
||||
WebFilterChain filterChain = webExchange -> Mono.deferContextual(contextView -> {
|
||||
assertThat(contextView.getOrEmpty(ObservationThreadLocalAccessor.KEY)).isPresent();
|
||||
Observation observation = contextView.get(ObservationThreadLocalAccessor.KEY);
|
||||
assertThat(observation).isNotNull();
|
||||
// check that the observation was started
|
||||
assertThat(observation.getContext().getLowCardinalityKeyValue("outcome")).isNotNull();
|
||||
return Mono.empty();
|
||||
});
|
||||
this.filter.filter(exchange, filterChain).block();
|
||||
@@ -99,6 +104,25 @@ class ServerHttpObservationFilterTests {
|
||||
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "UNKNOWN");
|
||||
}
|
||||
|
||||
@Test
|
||||
void filterShouldStopObservationOnResponseCommit() {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource"));
|
||||
WebFilterChain filterChain = createFilterChain(filterExchange -> {
|
||||
throw new IllegalArgumentException("server error");
|
||||
});
|
||||
StepVerifier.create(this.filter.filter(exchange, filterChain).doOnError(throwable -> {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
response.setRawStatusCode(500);
|
||||
response.setComplete().block();
|
||||
}))
|
||||
.expectError(IllegalArgumentException.class)
|
||||
.verify();
|
||||
Optional<ServerRequestObservationContext> observationContext = ServerHttpObservationFilter.findObservationContext(exchange);
|
||||
assertThat(observationContext.get().getError()).isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SERVER_ERROR");
|
||||
}
|
||||
|
||||
|
||||
private WebFilterChain createFilterChain(ThrowingConsumer<ServerWebExchange> exchangeConsumer) {
|
||||
return filterExchange -> {
|
||||
try {
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
--LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD
|
||||
Content-Disposition: form-data; name="files"; filename="file17312898095703516893.tmp"
|
||||
Content-Type: application/octet-stream
|
||||
Content-Length: 0
|
||||
|
||||
|
||||
--LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD
|
||||
Content-Disposition: form-data; name="files"; filename="file14790463448453253614.tmp"
|
||||
Content-Type: application/octet-stream
|
||||
Content-Length: 0
|
||||
|
||||
|
||||
--LiG0chJ0k7YtLt-FzTklYFgz50i88xJCW5jD--
|
||||
+20
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.web.reactive.function.client;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import io.micrometer.observation.transport.RequestReplySenderContext;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -32,6 +34,13 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public class ClientRequestObservationContext extends RequestReplySenderContext<ClientRequest.Builder, ClientResponse> {
|
||||
|
||||
/**
|
||||
* Name of the request attribute holding the {@link ClientRequestObservationContext context}
|
||||
* for the current observation.
|
||||
* @since 6.1.1
|
||||
*/
|
||||
public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = ClientRequestObservationContext.class.getName();
|
||||
|
||||
@Nullable
|
||||
private String uriTemplate;
|
||||
|
||||
@@ -96,4 +105,15 @@ public class ClientRequestObservationContext extends RequestReplySenderContext<C
|
||||
public void setRequest(ClientRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current {@link ClientRequestObservationContext observation context}
|
||||
* from the given request, if available.
|
||||
* @param request the current client request
|
||||
* @return the current observation context
|
||||
* @since 6.1.2
|
||||
*/
|
||||
public static Optional<ClientRequestObservationContext> findCurrent(ClientRequest request) {
|
||||
return Optional.ofNullable((ClientRequestObservationContext) request.attributes().get(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE));
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -450,7 +450,9 @@ final class DefaultWebClient implements WebClient {
|
||||
if (filterFunctions != null) {
|
||||
filterFunction = filterFunctions.andThen(filterFunction);
|
||||
}
|
||||
ClientRequest request = requestBuilder.build();
|
||||
ClientRequest request = requestBuilder
|
||||
.attribute(ClientRequestObservationContext.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observation.getContext())
|
||||
.build();
|
||||
observationContext.setUriTemplate((String) request.attribute(URI_TEMPLATE_ATTRIBUTE).orElse(null));
|
||||
observationContext.setRequest(request);
|
||||
Mono<ClientResponse> responseMono = filterFunction.apply(exchangeFunction)
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -123,8 +123,8 @@ public class PathResourceResolver extends AbstractResourceResolver {
|
||||
Resource[] allowed = getAllowedLocations();
|
||||
logger.warn(LogFormatUtils.formatValue(
|
||||
"Resource path \"" + resourcePath + "\" was successfully resolved " +
|
||||
"but resource \"" + resource.getURL() + "\" is neither under the " +
|
||||
"current location \"" + location.getURL() + "\" nor under any of the " +
|
||||
"but resource \"" + resource + "\" is neither under the " +
|
||||
"current location \"" + location + "\" nor under any of the " +
|
||||
"allowed locations " + (allowed != null ? Arrays.asList(allowed) : "[]"), -1, true));
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -259,13 +259,19 @@ class MultipartRouterFunctionIntegrationTests extends AbstractRouterFunctionInte
|
||||
assertThat(data).hasSize(2);
|
||||
|
||||
List<PartEvent> fileData = data.get(0);
|
||||
assertThat(fileData).hasSize(1);
|
||||
assertThat(fileData).hasSize(2);
|
||||
assertThat(fileData.get(0)).isInstanceOf(FilePartEvent.class);
|
||||
FilePartEvent filePartEvent = (FilePartEvent) fileData.get(0);
|
||||
assertThat(filePartEvent.name()).isEqualTo("fooPart");
|
||||
assertThat(filePartEvent.filename()).isEqualTo("foo.txt");
|
||||
DataBufferUtils.release(filePartEvent.content());
|
||||
|
||||
assertThat(fileData.get(1)).isInstanceOf(FilePartEvent.class);
|
||||
filePartEvent = (FilePartEvent) fileData.get(1);
|
||||
assertThat(filePartEvent.name()).isEqualTo("fooPart");
|
||||
assertThat(filePartEvent.filename()).isEqualTo("foo.txt");
|
||||
DataBufferUtils.release(filePartEvent.content());
|
||||
|
||||
List<PartEvent> fieldData = data.get(1);
|
||||
assertThat(fieldData).hasSize(1);
|
||||
assertThat(fieldData.get(0)).isInstanceOf(FormPartEvent.class);
|
||||
|
||||
+20
@@ -152,6 +152,26 @@ class WebClientObservationTests {
|
||||
verifyAndGetRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setsCurrentObservationContextAsRequestAttribute() {
|
||||
ExchangeFilterFunction assertionFilter = new ExchangeFilterFunction() {
|
||||
@Override
|
||||
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction chain) {
|
||||
Optional<ClientRequestObservationContext> observationContext = ClientRequestObservationContext.findCurrent(request);
|
||||
assertThat(observationContext).isPresent();
|
||||
return chain.exchange(request).contextWrite(context -> {
|
||||
Observation currentObservation = context.get(ObservationThreadLocalAccessor.KEY);
|
||||
assertThat(currentObservation.getContext()).isEqualTo(observationContext.get());
|
||||
return context;
|
||||
});
|
||||
}
|
||||
};
|
||||
this.builder.filter(assertionFilter).build().get().uri("/resource/{id}", 42)
|
||||
.retrieve().bodyToMono(Void.class)
|
||||
.block(Duration.ofSeconds(10));
|
||||
verifyAndGetRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void recordsObservationWithResponseDetailsWhenFilterFunctionErrors() {
|
||||
ExchangeFilterFunction errorFunction = (req, next) -> next.exchange(req).then(Mono.error(new IllegalStateException()));
|
||||
|
||||
+9
-5
@@ -191,8 +191,12 @@ public class HandlerMappingIntrospector
|
||||
public Filter createCacheFilter() {
|
||||
return (request, response, chain) -> {
|
||||
CachedResult previous = setCache((HttpServletRequest) request);
|
||||
chain.doFilter(request, response);
|
||||
resetCache(request, previous);
|
||||
try {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
finally {
|
||||
resetCache(request, previous);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -206,7 +210,7 @@ public class HandlerMappingIntrospector
|
||||
* @since 6.0.14
|
||||
*/
|
||||
@Nullable
|
||||
private CachedResult setCache(HttpServletRequest request) {
|
||||
public CachedResult setCache(HttpServletRequest request) {
|
||||
CachedResult previous = (CachedResult) request.getAttribute(CACHED_RESULT_ATTRIBUTE);
|
||||
if (previous == null || !previous.matches(request)) {
|
||||
HttpServletRequest wrapped = new AttributesPreservingRequest(request);
|
||||
@@ -245,7 +249,7 @@ public class HandlerMappingIntrospector
|
||||
* a filter after delegating to the rest of the chain.
|
||||
* @since 6.0.14
|
||||
*/
|
||||
private void resetCache(ServletRequest request, @Nullable CachedResult cachedResult) {
|
||||
public void resetCache(ServletRequest request, @Nullable CachedResult cachedResult) {
|
||||
request.setAttribute(CACHED_RESULT_ATTRIBUTE, cachedResult);
|
||||
}
|
||||
|
||||
@@ -363,7 +367,7 @@ public class HandlerMappingIntrospector
|
||||
* @since 6.0.14
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static final class CachedResult {
|
||||
public static final class CachedResult {
|
||||
|
||||
private final DispatcherType dispatcherType;
|
||||
|
||||
|
||||
+2
-2
@@ -195,8 +195,8 @@ public class PathResourceResolver extends AbstractResourceResolver {
|
||||
Resource[] allowed = getAllowedLocations();
|
||||
logger.warn(LogFormatUtils.formatValue(
|
||||
"Resource path \"" + resourcePath + "\" was successfully resolved " +
|
||||
"but resource \"" + resource.getURL() + "\" is neither under " +
|
||||
"the current location \"" + location.getURL() + "\" nor under any of " +
|
||||
"but resource \"" + resource + "\" is neither under " +
|
||||
"the current location \"" + location + "\" nor under any of " +
|
||||
"the allowed locations " + (allowed != null ? Arrays.asList(allowed) : "[]"), -1, true));
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -1350,12 +1350,12 @@
|
||||
set on the "Access-Control-Allow-Credentials" response header of
|
||||
preflight requests.
|
||||
|
||||
NOTE: Be aware that this option establishes a high
|
||||
level of trust with the configured domains and also increases the surface
|
||||
attack of the web application by exposing sensitive user-specific
|
||||
information such as cookies and CSRF tokens.
|
||||
NOTE: Be aware that this option establishes a high level of trust with
|
||||
the configured domains and also increases the surface attack of the web
|
||||
application by exposing sensitive user-specific information such as
|
||||
cookies and CSRF tokens.
|
||||
|
||||
By default this is not set in which case the
|
||||
By default, this is not set in which case the
|
||||
"Access-Control-Allow-Credentials" header is also not set and
|
||||
credentials are therefore not allowed.
|
||||
]]></xsd:documentation>
|
||||
|
||||
+1
@@ -399,6 +399,7 @@ public class HandlerMappingIntrospectorTests {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class TestServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
|
||||
+44
-37
@@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
|
||||
@@ -53,7 +54,7 @@ import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
*/
|
||||
public class WebSocketMessageBrokerStats {
|
||||
public class WebSocketMessageBrokerStats implements SmartInitializingSingleton {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(WebSocketMessageBrokerStats.class);
|
||||
|
||||
@@ -84,7 +85,49 @@ public class WebSocketMessageBrokerStats {
|
||||
|
||||
public void setSubProtocolWebSocketHandler(SubProtocolWebSocketHandler webSocketHandler) {
|
||||
this.webSocketHandler = webSocketHandler;
|
||||
}
|
||||
|
||||
public void setStompBrokerRelay(StompBrokerRelayMessageHandler stompBrokerRelay) {
|
||||
this.stompBrokerRelay = stompBrokerRelay;
|
||||
}
|
||||
|
||||
public void setInboundChannelExecutor(TaskExecutor inboundChannelExecutor) {
|
||||
this.inboundChannelExecutor = inboundChannelExecutor;
|
||||
}
|
||||
|
||||
public void setOutboundChannelExecutor(TaskExecutor outboundChannelExecutor) {
|
||||
this.outboundChannelExecutor = outboundChannelExecutor;
|
||||
}
|
||||
|
||||
public void setSockJsTaskScheduler(TaskScheduler sockJsTaskScheduler) {
|
||||
this.sockJsTaskScheduler = sockJsTaskScheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the frequency for logging information at INFO level in milliseconds.
|
||||
* If set 0 or less than 0, the logging task is cancelled.
|
||||
* <p>By default this property is set to 30 minutes (30 * 60 * 1000).
|
||||
*/
|
||||
public void setLoggingPeriod(long period) {
|
||||
this.loggingPeriod = period;
|
||||
if (this.loggingTask != null) {
|
||||
this.loggingTask.cancel(true);
|
||||
this.loggingTask = initLoggingTask(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured logging period frequency in milliseconds.
|
||||
*/
|
||||
public long getLoggingPeriod() {
|
||||
return this.loggingPeriod;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
this.stompSubProtocolHandler = initStompSubProtocolHandler();
|
||||
this.loggingTask = initLoggingTask(TimeUnit.MINUTES.toMillis(1));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -104,23 +147,6 @@ public class WebSocketMessageBrokerStats {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setStompBrokerRelay(StompBrokerRelayMessageHandler stompBrokerRelay) {
|
||||
this.stompBrokerRelay = stompBrokerRelay;
|
||||
}
|
||||
|
||||
public void setInboundChannelExecutor(TaskExecutor inboundChannelExecutor) {
|
||||
this.inboundChannelExecutor = inboundChannelExecutor;
|
||||
}
|
||||
|
||||
public void setOutboundChannelExecutor(TaskExecutor outboundChannelExecutor) {
|
||||
this.outboundChannelExecutor = outboundChannelExecutor;
|
||||
}
|
||||
|
||||
public void setSockJsTaskScheduler(TaskScheduler sockJsTaskScheduler) {
|
||||
this.sockJsTaskScheduler = sockJsTaskScheduler;
|
||||
this.loggingTask = initLoggingTask(TimeUnit.MINUTES.toMillis(1));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ScheduledFuture<?> initLoggingTask(long initialDelay) {
|
||||
if (this.sockJsTaskScheduler != null && this.loggingPeriod > 0 && logger.isInfoEnabled()) {
|
||||
@@ -131,25 +157,6 @@ public class WebSocketMessageBrokerStats {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the frequency for logging information at INFO level in milliseconds.
|
||||
* If set 0 or less than 0, the logging task is cancelled.
|
||||
* <p>By default this property is set to 30 minutes (30 * 60 * 1000).
|
||||
*/
|
||||
public void setLoggingPeriod(long period) {
|
||||
if (this.loggingTask != null) {
|
||||
this.loggingTask.cancel(true);
|
||||
}
|
||||
this.loggingPeriod = period;
|
||||
this.loggingTask = initLoggingTask(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured logging period frequency in milliseconds.
|
||||
*/
|
||||
public long getLoggingPeriod() {
|
||||
return this.loggingPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stats about WebSocket sessions.
|
||||
|
||||
+3
-3
@@ -420,7 +420,7 @@
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><![CDATA[
|
||||
Set the core pool size of the ThreadPoolExecutor.
|
||||
NOTE: the core pool size is effectively the max pool size when an unbounded queue-capacity is configured (the default).
|
||||
NOTE: The core pool size is effectively the max pool size when an unbounded queue-capacity is configured (the default).
|
||||
This is essentially the "Unbounded queues" strategy as explained in java.util.concurrent.ThreadPoolExecutor.
|
||||
When this strategy is used, the max pool size is effectively ignored.
|
||||
By default this is set to twice the value of Runtime.availableProcessors().
|
||||
@@ -433,7 +433,7 @@
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><![CDATA[
|
||||
Set the max pool size of the ThreadPoolExecutor.
|
||||
NOTE: when an unbounded queue-capacity is configured (the default), the max pool size is effectively ignored.
|
||||
NOTE: When an unbounded queue-capacity is configured (the default), the max pool size is effectively ignored.
|
||||
See the "Unbounded queues" strategy in java.util.concurrent.ThreadPoolExecutor for more details.
|
||||
By default this is set to Integer.MAX_VALUE.
|
||||
]]></xsd:documentation>
|
||||
@@ -453,7 +453,7 @@
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><![CDATA[
|
||||
Set the queue capacity for the ThreadPoolExecutor.
|
||||
NOTE: when an unbounded queue-capacity is configured (the default) the core pool size is effectively the max pool size.
|
||||
NOTE: When an unbounded queue-capacity is configured (the default) the core pool size is effectively the max pool size.
|
||||
This is essentially the "Unbounded queues" strategy as explained in java.util.concurrent.ThreadPoolExecutor.
|
||||
When this strategy is used, the max pool size is effectively ignored.
|
||||
By default this is set to Integer.MAX_VALUE.
|
||||
|
||||
Reference in New Issue
Block a user