Skip to content

Commit f2b3f1f

Browse files
committed
Make URL- and property-based pooling config mutually exclusive
Closes gh-28144
1 parent 54d0f76 commit f2b3f1f

13 files changed

+420
-77
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java

Lines changed: 71 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,21 @@
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3131
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
32+
import org.springframework.boot.autoconfigure.r2dbc.R2dbcProperties.Pool;
3233
import org.springframework.boot.context.properties.PropertyMapper;
34+
import org.springframework.boot.context.properties.bind.BindResult;
35+
import org.springframework.boot.context.properties.bind.Bindable;
36+
import org.springframework.boot.context.properties.bind.Binder;
3337
import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection;
3438
import org.springframework.context.annotation.Bean;
3539
import org.springframework.context.annotation.Condition;
3640
import org.springframework.context.annotation.ConditionContext;
3741
import org.springframework.context.annotation.Conditional;
3842
import org.springframework.context.annotation.Configuration;
43+
import org.springframework.core.env.Environment;
3944
import org.springframework.core.io.ResourceLoader;
4045
import org.springframework.core.type.AnnotatedTypeMetadata;
46+
import org.springframework.util.ClassUtils;
4147
import org.springframework.util.StringUtils;
4248

4349
/**
@@ -51,39 +57,54 @@ abstract class ConnectionFactoryConfigurations {
5157

5258
protected static ConnectionFactory createConnectionFactory(R2dbcProperties properties, ClassLoader classLoader,
5359
List<ConnectionFactoryOptionsBuilderCustomizer> optionsCustomizers) {
54-
return org.springframework.boot.r2dbc.ConnectionFactoryBuilder
55-
.withOptions(new ConnectionFactoryOptionsInitializer().initialize(properties,
56-
() -> EmbeddedDatabaseConnection.get(classLoader)))
57-
.configure((options) -> {
58-
for (ConnectionFactoryOptionsBuilderCustomizer optionsCustomizer : optionsCustomizers) {
59-
optionsCustomizer.customize(options);
60-
}
61-
}).build();
60+
try {
61+
return org.springframework.boot.r2dbc.ConnectionFactoryBuilder
62+
.withOptions(new ConnectionFactoryOptionsInitializer().initialize(properties,
63+
() -> EmbeddedDatabaseConnection.get(classLoader)))
64+
.configure((options) -> {
65+
for (ConnectionFactoryOptionsBuilderCustomizer optionsCustomizer : optionsCustomizers) {
66+
optionsCustomizer.customize(options);
67+
}
68+
}).build();
69+
}
70+
catch (IllegalStateException ex) {
71+
String message = ex.getMessage();
72+
if (message != null && message.contains("driver=pool")
73+
&& !ClassUtils.isPresent("io.r2dbc.pool.ConnectionPool", classLoader)) {
74+
throw new MissingR2dbcPoolDependencyException();
75+
}
76+
throw ex;
77+
}
6278
}
6379

6480
@Configuration(proxyBeanMethods = false)
65-
@ConditionalOnClass(ConnectionPool.class)
6681
@Conditional(PooledConnectionFactoryCondition.class)
6782
@ConditionalOnMissingBean(ConnectionFactory.class)
68-
static class Pool {
83+
static class PoolConfiguration {
84+
85+
@Configuration(proxyBeanMethods = false)
86+
@ConditionalOnClass(ConnectionPool.class)
87+
static class PooledConnectionFactoryConfiguration {
88+
89+
@Bean(destroyMethod = "dispose")
90+
ConnectionPool connectionFactory(R2dbcProperties properties, ResourceLoader resourceLoader,
91+
ObjectProvider<ConnectionFactoryOptionsBuilderCustomizer> customizers) {
92+
ConnectionFactory connectionFactory = createConnectionFactory(properties,
93+
resourceLoader.getClassLoader(), customizers.orderedStream().collect(Collectors.toList()));
94+
R2dbcProperties.Pool pool = properties.getPool();
95+
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
96+
ConnectionPoolConfiguration.Builder builder = ConnectionPoolConfiguration.builder(connectionFactory);
97+
map.from(pool.getMaxIdleTime()).to(builder::maxIdleTime);
98+
map.from(pool.getMaxLifeTime()).to(builder::maxLifeTime);
99+
map.from(pool.getMaxAcquireTime()).to(builder::maxAcquireTime);
100+
map.from(pool.getMaxCreateConnectionTime()).to(builder::maxCreateConnectionTime);
101+
map.from(pool.getInitialSize()).to(builder::initialSize);
102+
map.from(pool.getMaxSize()).to(builder::maxSize);
103+
map.from(pool.getValidationQuery()).whenHasText().to(builder::validationQuery);
104+
map.from(pool.getValidationDepth()).to(builder::validationDepth);
105+
return new ConnectionPool(builder.build());
106+
}
69107

70-
@Bean(destroyMethod = "dispose")
71-
ConnectionPool connectionFactory(R2dbcProperties properties, ResourceLoader resourceLoader,
72-
ObjectProvider<ConnectionFactoryOptionsBuilderCustomizer> customizers) {
73-
ConnectionFactory connectionFactory = createConnectionFactory(properties, resourceLoader.getClassLoader(),
74-
customizers.orderedStream().collect(Collectors.toList()));
75-
R2dbcProperties.Pool pool = properties.getPool();
76-
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
77-
ConnectionPoolConfiguration.Builder builder = ConnectionPoolConfiguration.builder(connectionFactory);
78-
map.from(pool.getMaxIdleTime()).to(builder::maxIdleTime);
79-
map.from(pool.getMaxLifeTime()).to(builder::maxLifeTime);
80-
map.from(pool.getMaxAcquireTime()).to(builder::maxAcquireTime);
81-
map.from(pool.getMaxCreateConnectionTime()).to(builder::maxCreateConnectionTime);
82-
map.from(pool.getInitialSize()).to(builder::initialSize);
83-
map.from(pool.getMaxSize()).to(builder::maxSize);
84-
map.from(pool.getValidationQuery()).whenHasText().to(builder::validationQuery);
85-
map.from(pool.getValidationDepth()).to(builder::validationDepth);
86-
return new ConnectionPool(builder.build());
87108
}
88109

89110
}
@@ -92,7 +113,7 @@ ConnectionPool connectionFactory(R2dbcProperties properties, ResourceLoader reso
92113
@ConditionalOnProperty(prefix = "spring.r2dbc.pool", value = "enabled", havingValue = "false",
93114
matchIfMissing = true)
94115
@ConditionalOnMissingBean(ConnectionFactory.class)
95-
static class Generic {
116+
static class GenericConfiguration {
96117

97118
@Bean
98119
ConnectionFactory connectionFactory(R2dbcProperties properties, ResourceLoader resourceLoader,
@@ -105,26 +126,35 @@ ConnectionFactory connectionFactory(R2dbcProperties properties, ResourceLoader r
105126

106127
/**
107128
* {@link Condition} that checks that a {@link ConnectionPool} is requested. The
108-
* condition matches if pooling was opt-in via configuration and the r2dbc url does
109-
* not contain pooling-related options.
129+
* condition matches if pooling was opt-in via configuration. If any of the
130+
* spring.r2dbc.pool.* properties have been configured, an exception is thrown if the
131+
* URL also contains pooling-related options or io.r2dbc.pool.ConnectionPool is not on
132+
* the class path.
110133
*/
111134
static class PooledConnectionFactoryCondition extends SpringBootCondition {
112135

113136
@Override
114137
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
115-
boolean poolEnabled = context.getEnvironment().getProperty("spring.r2dbc.pool.enabled", Boolean.class,
116-
true);
117-
if (poolEnabled) {
118-
// Make sure the URL does not have pool options
119-
String url = context.getEnvironment().getProperty("spring.r2dbc.url");
120-
boolean pooledUrl = StringUtils.hasText(url) && url.contains(":pool:");
121-
if (pooledUrl) {
122-
return ConditionOutcome.noMatch("R2DBC Connection URL contains pooling-related options");
138+
BindResult<Pool> pool = Binder.get(context.getEnvironment()).bind("spring.r2dbc.pool",
139+
Bindable.of(Pool.class));
140+
if (hasPoolUrl(context.getEnvironment())) {
141+
if (pool.isBound()) {
142+
throw new MultipleConnectionPoolConfigurationsException();
123143
}
124-
return ConditionOutcome
125-
.match("Pooling is enabled and R2DBC Connection URL does not contain pooling-related options");
144+
return ConditionOutcome.noMatch("URL-based pooling has been configured");
145+
}
146+
if (pool.isBound() && !ClassUtils.isPresent("io.r2dbc.pool.ConnectionPool", context.getClassLoader())) {
147+
throw new MissingR2dbcPoolDependencyException();
148+
}
149+
if (pool.orElseGet(Pool::new).isEnabled()) {
150+
return ConditionOutcome.match("Property-based pooling is enabled");
126151
}
127-
return ConditionOutcome.noMatch("Pooling is disabled");
152+
return ConditionOutcome.noMatch("Property-based pooling is disabled");
153+
}
154+
155+
private boolean hasPoolUrl(Environment environment) {
156+
String url = environment.getProperty("spring.r2dbc.url");
157+
return StringUtils.hasText(url) && url.contains(":pool:");
128158
}
129159

130160
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.r2dbc;
18+
19+
/**
20+
* Exception thrown when R2DBC connection pooling has been configured but the
21+
* {@code io.r2dbc:r2dbc-pool} dependency is missing.
22+
*
23+
* @author Andy Wilkinson
24+
*/
25+
class MissingR2dbcPoolDependencyException extends RuntimeException {
26+
27+
MissingR2dbcPoolDependencyException() {
28+
super("R2DBC connection pooling has been configured but the io.r2dbc.pool.ConnectionPool class is not "
29+
+ "present.");
30+
}
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.r2dbc;
18+
19+
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
20+
import org.springframework.boot.diagnostics.FailureAnalysis;
21+
import org.springframework.boot.diagnostics.FailureAnalyzer;
22+
23+
/**
24+
* {@link FailureAnalyzer} for {@link MissingR2dbcPoolDependencyException}.
25+
*
26+
* @author Andy Wilkinson
27+
*/
28+
class MissingR2dbcPoolDependencyFailureAnalyzer extends AbstractFailureAnalyzer<MissingR2dbcPoolDependencyException> {
29+
30+
@Override
31+
protected FailureAnalysis analyze(Throwable rootFailure, MissingR2dbcPoolDependencyException cause) {
32+
return new FailureAnalysis(cause.getMessage(),
33+
"Update your application's build to depend on io.r2dbc:r2dbc-pool or your application's configuration "
34+
+ "to disable R2DBC connection pooling.",
35+
cause);
36+
}
37+
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.r2dbc;
18+
19+
/**
20+
* Exception thrown when R2DBC connection pooling has been configured both by the URL
21+
* ({@code spring.r2dbc.url}) and the pool properties ({@code spring.r2dbc.pool.*}.
22+
*
23+
* @author Andy Wilkinson
24+
*/
25+
class MultipleConnectionPoolConfigurationsException extends RuntimeException {
26+
27+
MultipleConnectionPoolConfigurationsException() {
28+
super("R2DBC connection pooling configuration should be provided by either the spring.r2dbc.pool.* "
29+
+ "properties or the spring.r2dbc.url property but both have been used.");
30+
}
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.r2dbc;
18+
19+
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
20+
import org.springframework.boot.diagnostics.FailureAnalysis;
21+
import org.springframework.boot.diagnostics.FailureAnalyzer;
22+
23+
/**
24+
* {@link FailureAnalyzer} for {@link MultipleConnectionPoolConfigurationsException}.
25+
*
26+
* @author Andy Wilkinson
27+
*/
28+
class MultipleConnectionPoolConfigurationsFailureAnalzyer
29+
extends AbstractFailureAnalyzer<MultipleConnectionPoolConfigurationsException> {
30+
31+
@Override
32+
protected FailureAnalysis analyze(Throwable rootFailure, MultipleConnectionPoolConfigurationsException cause) {
33+
return new FailureAnalysis(cause.getMessage(),
34+
"Update your configuration so that R2DBC connection pooling is configured using either the "
35+
+ "spring.r2dbc.url property or the spring.r2dbc.pool.* properties",
36+
cause);
37+
}
38+
39+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
@ConditionalOnResource(resources = "classpath:META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider")
4141
@AutoConfigureBefore({ DataSourceAutoConfiguration.class, SqlInitializationAutoConfiguration.class })
4242
@EnableConfigurationProperties(R2dbcProperties.class)
43-
@Import({ ConnectionFactoryConfigurations.Pool.class, ConnectionFactoryConfigurations.Generic.class,
44-
ConnectionFactoryDependentConfiguration.class })
43+
@Import({ ConnectionFactoryConfigurations.PoolConfiguration.class,
44+
ConnectionFactoryConfigurations.GenericConfiguration.class, ConnectionFactoryDependentConfiguration.class })
4545
public class R2dbcAutoConfiguration {
4646

4747
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProperties.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ public static class Pool {
178178
*/
179179
private ValidationDepth validationDepth = ValidationDepth.LOCAL;
180180

181+
/**
182+
* Whether pooling is enabled. Requires r2dbc-pool.
183+
*/
184+
private boolean enabled = true;
185+
181186
public Duration getMaxIdleTime() {
182187
return this.maxIdleTime;
183188
}
@@ -242,6 +247,14 @@ public void setValidationDepth(ValidationDepth validationDepth) {
242247
this.validationDepth = validationDepth;
243248
}
244249

250+
public boolean isEnabled() {
251+
return this.enabled;
252+
}
253+
254+
public void setEnabled(boolean enabled) {
255+
this.enabled = enabled;
256+
}
257+
245258
}
246259

247260
}

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,11 +1688,6 @@
16881688
"name": "spring.quartz.scheduler-name",
16891689
"defaultValue": "quartzScheduler"
16901690
},
1691-
{
1692-
"name": "spring.r2dbc.pool.enabled",
1693-
"type": "java.lang.Boolean",
1694-
"description": "Whether pooling is enabled. Enabled automatically if \"r2dbc-pool\" is on the classpath."
1695-
},
16961691
{
16971692
"name": "spring.r2dbc.pool.validation-depth",
16981693
"defaultValue": "local"

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyze
165165
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
166166
org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\
167167
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
168+
org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\
169+
org.springframework.boot.autoconfigure.r2dbc.MultipleConnectionPoolConfigurationsFailureAnalzyer,\
168170
org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer,\
169171
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
170172

0 commit comments

Comments
 (0)