pull/9729/merge
Phillip Webb 7 years ago
parent 03f74e96b0
commit 8e3baf3130

@ -17,7 +17,6 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import org.neo4j.ogm.model.Result; import org.neo4j.ogm.model.Result;
import org.neo4j.ogm.session.Session; import org.neo4j.ogm.session.Session;
@ -51,11 +50,8 @@ public class Neo4jHealthIndicator extends AbstractHealthIndicator {
@Override @Override
protected void doHealthCheck(Health.Builder builder) throws Exception { protected void doHealthCheck(Health.Builder builder) throws Exception {
Session session = this.sessionFactory.openSession(); Session session = this.sessionFactory.openSession();
Result result = session.query(CYPHER, Collections.emptyMap());
Result result = session.query(CYPHER, Collections.EMPTY_MAP); int nodes = (int) result.queryResults().iterator().next().get("nodes");
Iterable<Map<String, Object>> results = result.queryResults();
int nodes = (int) results.iterator().next().get("nodes");
builder.up().withDetail("nodes", nodes); builder.up().withDetail("nodes", nodes);
} }

@ -79,9 +79,8 @@ import static org.mockito.Mockito.mock;
*/ */
public class HealthIndicatorAutoConfigurationTests { public class HealthIndicatorAutoConfigurationTests {
public final ContextLoader contextLoader = ContextLoader.standard() public final ContextLoader contextLoader = ContextLoader.standard().autoConfig(
.autoConfig(HealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, ManagementServerProperties.class);
ManagementServerProperties.class);
@Test @Test
public void defaultHealthIndicator() { public void defaultHealthIndicator() {

@ -57,21 +57,18 @@ public class Neo4jHealthIndicatorTests {
@Test @Test
public void neo4jUp() { public void neo4jUp() {
Result result = mock(Result.class); Result result = mock(Result.class);
given(this.session.query(Neo4jHealthIndicator.CYPHER, Collections.EMPTY_MAP)) given(this.session.query(Neo4jHealthIndicator.CYPHER, Collections.emptyMap()))
.willReturn(result); .willReturn(result);
int nodeCount = 500; int nodeCount = 500;
Map<String, Object> expectedCypherDetails = new HashMap<>(); Map<String, Object> expectedCypherDetails = new HashMap<>();
expectedCypherDetails.put("nodes", nodeCount); expectedCypherDetails.put("nodes", nodeCount);
List<Map<String, Object>> queryResults = new ArrayList<>(); List<Map<String, Object>> queryResults = new ArrayList<>();
queryResults.add(expectedCypherDetails); queryResults.add(expectedCypherDetails);
given(result.queryResults()).willReturn(queryResults); given(result.queryResults()).willReturn(queryResults);
Health health = this.neo4jHealthIndicator.health(); Health health = this.neo4jHealthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getStatus()).isEqualTo(Status.UP);
Map<String, Object> details = health.getDetails(); Map<String, Object> details = health.getDetails();
int nodeCountFromDetails = (int) details.get("nodes"); int nodeCountFromDetails = (int) details.get("nodes");
Assert.assertEquals(nodeCount, nodeCountFromDetails); Assert.assertEquals(nodeCount, nodeCountFromDetails);
} }
@ -80,9 +77,8 @@ public class Neo4jHealthIndicatorTests {
CypherException cypherException = new CypherException("Error executing Cypher", CypherException cypherException = new CypherException("Error executing Cypher",
"Neo.ClientError.Statement.SyntaxError", "Neo.ClientError.Statement.SyntaxError",
"Unable to execute invalid Cypher"); "Unable to execute invalid Cypher");
given(this.session.query(Neo4jHealthIndicator.CYPHER, Collections.EMPTY_MAP)) given(this.session.query(Neo4jHealthIndicator.CYPHER, Collections.emptyMap()))
.willThrow(cypherException); .willThrow(cypherException);
Health health = this.neo4jHealthIndicator.health(); Health health = this.neo4jHealthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN); assertThat(health.getStatus()).isEqualTo(Status.DOWN);
} }

@ -71,16 +71,13 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration {
private JedisConnectionFactory createJedisConnectionFactory() { private JedisConnectionFactory createJedisConnectionFactory() {
JedisClientConfiguration clientConfiguration = getJedisClientConfiguration(); JedisClientConfiguration clientConfiguration = getJedisClientConfiguration();
if (getSentinelConfig() != null) { if (getSentinelConfig() != null) {
return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration); return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration);
} }
if (getClusterConfiguration() != null) { if (getClusterConfiguration() != null) {
return new JedisConnectionFactory(getClusterConfiguration(), return new JedisConnectionFactory(getClusterConfiguration(),
clientConfiguration); clientConfiguration);
} }
return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration); return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
} }

@ -145,16 +145,13 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
private LettuceConnectionFactory createLettuceConnectionFactory( private LettuceConnectionFactory createLettuceConnectionFactory(
LettuceClientConfiguration clientConfiguration) { LettuceClientConfiguration clientConfiguration) {
if (getSentinelConfig() != null) { if (getSentinelConfig() != null) {
return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration); return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
} }
if (getClusterConfiguration() != null) { if (getClusterConfiguration() != null) {
return new LettuceConnectionFactory(getClusterConfiguration(), return new LettuceConnectionFactory(getClusterConfiguration(),
clientConfiguration); clientConfiguration);
} }
return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration); return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
} }

@ -54,7 +54,6 @@ abstract class RedisConnectionConfiguration {
protected final RedisStandaloneConfiguration getStandaloneConfig() { protected final RedisStandaloneConfiguration getStandaloneConfig() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
if (StringUtils.hasText(this.properties.getUrl())) { if (StringUtils.hasText(this.properties.getUrl())) {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl()); ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
config.setHostName(connectionInfo.getHostName()); config.setHostName(connectionInfo.getHostName());
@ -149,7 +148,9 @@ abstract class RedisConnectionConfiguration {
protected static class ConnectionInfo { protected static class ConnectionInfo {
private final URI uri; private final URI uri;
private final boolean useSsl; private final boolean useSsl;
private final String password; private final String password;
public ConnectionInfo(URI uri, boolean useSsl, String password) { public ConnectionInfo(URI uri, boolean useSsl, String password) {

@ -32,15 +32,20 @@ import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.util.MimeType; import org.springframework.util.MimeType;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} * {@link EnableAutoConfiguration Auto-configuration} for
* for {@link org.springframework.core.codec.Encoder}s and {@link org.springframework.core.codec.Decoder}s. * {@link org.springframework.core.codec.Encoder Encoders} and
* {@link org.springframework.core.codec.Decoder Decoders}.
*
* @author Brian Clozel * @author Brian Clozel
* @since 2.0.0
*/ */
@Configuration @Configuration
@ConditionalOnClass(CodecConfigurer.class) @ConditionalOnClass(CodecConfigurer.class)
@AutoConfigureAfter(JacksonAutoConfiguration.class) @AutoConfigureAfter(JacksonAutoConfiguration.class)
public class CodecsAutoConfiguration { public class CodecsAutoConfiguration {
private static final MimeType[] EMPTY_MIME_TYPES = {};
@Configuration @Configuration
@ConditionalOnClass(ObjectMapper.class) @ConditionalOnClass(ObjectMapper.class)
static class JacksonCodecConfiguration { static class JacksonCodecConfiguration {
@ -48,10 +53,12 @@ public class CodecsAutoConfiguration {
@Bean @Bean
@ConditionalOnBean(ObjectMapper.class) @ConditionalOnBean(ObjectMapper.class)
public CodecCustomizer jacksonCodecCustomizer(ObjectMapper objectMapper) { public CodecCustomizer jacksonCodecCustomizer(ObjectMapper objectMapper) {
return configurer -> { return (configurer) -> {
CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs(); CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs();
defaults.jackson2Decoder(new Jackson2JsonDecoder(objectMapper, new MimeType[0])); defaults.jackson2Decoder(
defaults.jackson2Encoder(new Jackson2JsonEncoder(objectMapper, new MimeType[0])); new Jackson2JsonDecoder(objectMapper, EMPTY_MIME_TYPES));
defaults.jackson2Encoder(
new Jackson2JsonEncoder(objectMapper, EMPTY_MIME_TYPES));
}; };
} }

@ -55,8 +55,7 @@ class DataSourceJmxConfiguration {
private final ObjectProvider<MBeanExporter> mBeanExporter; private final ObjectProvider<MBeanExporter> mBeanExporter;
Hikari(HikariDataSource dataSource, Hikari(HikariDataSource dataSource, ObjectProvider<MBeanExporter> mBeanExporter) {
ObjectProvider<MBeanExporter> mBeanExporter) {
this.dataSource = dataSource; this.dataSource = dataSource;
this.mBeanExporter = mBeanExporter; this.mBeanExporter = mBeanExporter;
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2016 the original author or authors. * Copyright 2012-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2016 the original author or authors. * Copyright 2012-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2016 the original author or authors. * Copyright 2012-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

@ -82,7 +82,8 @@ import org.springframework.web.reactive.result.view.ViewResolver;
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class) @ConditionalOnClass(WebFluxConfigurer.class)
@ConditionalOnMissingBean({ WebFluxConfigurationSupport.class }) @ConditionalOnMissingBean({ WebFluxConfigurationSupport.class })
@AutoConfigureAfter({ ReactiveWebServerAutoConfiguration.class, CodecsAutoConfiguration.class }) @AutoConfigureAfter({ ReactiveWebServerAutoConfiguration.class,
CodecsAutoConfiguration.class })
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxAutoConfiguration { public class WebFluxAutoConfiguration {
@ -133,7 +134,8 @@ public class WebFluxAutoConfiguration {
@Override @Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
if (this.codecCustomizers != null) { if (this.codecCustomizers != null) {
this.codecCustomizers.forEach(codecCustomizer -> codecCustomizer.customize(configurer)); this.codecCustomizers
.forEach((customizer) -> customizer.customize(configurer));
} }
} }

@ -38,8 +38,11 @@ import org.springframework.web.reactive.function.client.WebClient;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for {@link WebClient}. * {@link EnableAutoConfiguration Auto-configuration} for {@link WebClient}.
* <p>This will produce a {@link WebClient.Builder} bean with the {@code prototype} scope, * <p>
* meaning each injection point will receive a newly cloned instance of the builder. * This will produce a
* {@link org.springframework.web.reactive.function.client.WebClient.Builder
* WebClient.Builder} bean with the {@code prototype} scope, meaning each injection point
* will receive a newly cloned instance of the builder.
* *
* @author Brian Clozel * @author Brian Clozel
* @since 2.0.0 * @since 2.0.0
@ -51,14 +54,15 @@ public class WebClientAutoConfiguration {
private final WebClient.Builder webClientBuilder; private final WebClient.Builder webClientBuilder;
public WebClientAutoConfiguration(
public WebClientAutoConfiguration(ObjectProvider<List<WebClientCustomizer>> customizerProvider) { ObjectProvider<List<WebClientCustomizer>> customizerProvider) {
this.webClientBuilder = WebClient.builder(); this.webClientBuilder = WebClient.builder();
List<WebClientCustomizer> customizers = customizerProvider.getIfAvailable(); List<WebClientCustomizer> customizers = customizerProvider.getIfAvailable();
if (!CollectionUtils.isEmpty(customizers)) { if (!CollectionUtils.isEmpty(customizers)) {
customizers = new ArrayList<>(customizers); customizers = new ArrayList<>(customizers);
AnnotationAwareOrderComparator.sort(customizers); AnnotationAwareOrderComparator.sort(customizers);
customizers.forEach(customizer -> customizer.customize(this.webClientBuilder)); customizers
.forEach(customizer -> customizer.customize(this.webClientBuilder));
} }
} }

@ -25,6 +25,7 @@ import org.springframework.web.reactive.function.client.WebClient;
/** /**
* {@link WebClientCustomizer} that configures codecs for the HTTP client. * {@link WebClientCustomizer} that configures codecs for the HTTP client.
*
* @author Brian Clozel * @author Brian Clozel
* @since 2.0.0 * @since 2.0.0
*/ */
@ -39,9 +40,10 @@ public class WebClientCodecCustomizer implements WebClientCustomizer {
@Override @Override
public void customize(WebClient.Builder webClientBuilder) { public void customize(WebClient.Builder webClientBuilder) {
webClientBuilder webClientBuilder
.exchangeStrategies(ExchangeStrategies.builder() .exchangeStrategies(ExchangeStrategies.builder().codecs(codecs -> {
.codecs(codecs -> { this.codecCustomizers
this.codecCustomizers.forEach(codecCustomizer -> codecCustomizer.customize(codecs)); .forEach((customizer) -> customizer.customize(codecs));
}).build()); }).build());
} }
} }

@ -1076,54 +1076,63 @@ public class CacheAutoConfigurationTests {
@Bean @Bean
public CacheManagerCustomizer<CacheManager> allCacheManagerCustomizer() { public CacheManagerCustomizer<CacheManager> allCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<CacheManager>() { return new CacheManagerTestCustomizer<CacheManager>() {
}; };
} }
@Bean @Bean
public CacheManagerCustomizer<ConcurrentMapCacheManager> simpleCacheManagerCustomizer() { public CacheManagerCustomizer<ConcurrentMapCacheManager> simpleCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<ConcurrentMapCacheManager>() { return new CacheManagerTestCustomizer<ConcurrentMapCacheManager>() {
}; };
} }
@Bean @Bean
public CacheManagerCustomizer<SimpleCacheManager> genericCacheManagerCustomizer() { public CacheManagerCustomizer<SimpleCacheManager> genericCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<SimpleCacheManager>() { return new CacheManagerTestCustomizer<SimpleCacheManager>() {
}; };
} }
@Bean @Bean
public CacheManagerCustomizer<CouchbaseCacheManager> couchbaseCacheManagerCustomizer() { public CacheManagerCustomizer<CouchbaseCacheManager> couchbaseCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<CouchbaseCacheManager>() { return new CacheManagerTestCustomizer<CouchbaseCacheManager>() {
}; };
} }
@Bean @Bean
public CacheManagerCustomizer<RedisCacheManager> redisCacheManagerCustomizer() { public CacheManagerCustomizer<RedisCacheManager> redisCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<RedisCacheManager>() { return new CacheManagerTestCustomizer<RedisCacheManager>() {
}; };
} }
@Bean @Bean
public CacheManagerCustomizer<EhCacheCacheManager> ehcacheCacheManagerCustomizer() { public CacheManagerCustomizer<EhCacheCacheManager> ehcacheCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<EhCacheCacheManager>() { return new CacheManagerTestCustomizer<EhCacheCacheManager>() {
}; };
} }
@Bean @Bean
public CacheManagerCustomizer<HazelcastCacheManager> hazelcastCacheManagerCustomizer() { public CacheManagerCustomizer<HazelcastCacheManager> hazelcastCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<HazelcastCacheManager>() { return new CacheManagerTestCustomizer<HazelcastCacheManager>() {
}; };
} }
@Bean @Bean
public CacheManagerCustomizer<SpringEmbeddedCacheManager> infinispanCacheManagerCustomizer() { public CacheManagerCustomizer<SpringEmbeddedCacheManager> infinispanCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<SpringEmbeddedCacheManager>() { return new CacheManagerTestCustomizer<SpringEmbeddedCacheManager>() {
}; };
} }
@Bean @Bean
public CacheManagerCustomizer<CaffeineCacheManager> caffeineCacheManagerCustomizer() { public CacheManagerCustomizer<CaffeineCacheManager> caffeineCacheManagerCustomizer() {
return new CacheManagerTestCustomizer<CaffeineCacheManager>() { return new CacheManagerTestCustomizer<CaffeineCacheManager>() {
}; };
} }

@ -146,8 +146,8 @@ public class CassandraDataAutoConfigurationTests {
@Bean @Bean
public CassandraCustomConversions myCassandraCustomConversions() { public CassandraCustomConversions myCassandraCustomConversions() {
return new CassandraCustomConversions(Collections.singletonList( return new CassandraCustomConversions(
new MyConverter())); Collections.singletonList(new MyConverter()));
} }
} }

@ -92,16 +92,16 @@ public class MixedNeo4jRepositoriesAutoConfigurationTests {
} }
private void load(Class<?> config, String... environment) { private void load(Class<?> config, String... environment) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of(environment).and("spring.datasource.initialize", "false") TestPropertyValues.of(environment).and("spring.datasource.initialize", "false")
.and("spring.data.neo4j.uri", "http://localhost:8989").applyTo(ctx); .and("spring.data.neo4j.uri", "http://localhost:8989").applyTo(context);
ctx.register(config); context.register(config);
ctx.register(DataSourceAutoConfiguration.class, context.register(DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class, Neo4jDataAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class, Neo4jDataAutoConfiguration.class,
Neo4jRepositoriesAutoConfiguration.class); Neo4jRepositoriesAutoConfiguration.class);
ctx.refresh(); context.refresh();
this.context = ctx; this.context = context;
} }
@Configuration @Configuration

@ -121,18 +121,19 @@ public class RedisAutoConfigurationJedisTests {
List<String> sentinels = Arrays.asList("127.0.0.1:26379", "127.0.0.1:26380"); List<String> sentinels = Arrays.asList("127.0.0.1:26379", "127.0.0.1:26380");
load("spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.nodes:" load("spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.nodes:"
+ StringUtils.collectionToCommaDelimitedString(sentinels)); + StringUtils.collectionToCommaDelimitedString(sentinels));
assertThat(this.context.getBean(JedisConnectionFactory.class) assertThat(
.isRedisSentinelAware()).isTrue(); this.context.getBean(JedisConnectionFactory.class).isRedisSentinelAware())
.isTrue();
} }
@Test @Test
public void testRedisConfigurationWithSentinelAndPassword() { public void testRedisConfigurationWithSentinelAndPassword() {
List<String> sentinels = Arrays.asList("127.0.0.1:26379", "127.0.0.1:26380"); List<String> sentinels = Arrays.asList("127.0.0.1:26379", "127.0.0.1:26380");
load("spring.redis.password=password", "spring.redis.sentinel.master:mymaster", load("spring.redis.password=password", "spring.redis.sentinel.master:mymaster",
"spring.redis.sentinel.nodes:" + StringUtils "spring.redis.sentinel.nodes:"
.collectionToCommaDelimitedString(sentinels)); + StringUtils.collectionToCommaDelimitedString(sentinels));
assertThat(this.context.getBean(JedisConnectionFactory.class) assertThat(this.context.getBean(JedisConnectionFactory.class).getPassword())
.getPassword()).isEqualTo("password"); .isEqualTo("password");
} }
@Test @Test
@ -140,8 +141,9 @@ public class RedisAutoConfigurationJedisTests {
List<String> clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380"); List<String> clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380");
load("spring.redis.cluster.nodes[0]:" + clusterNodes.get(0), load("spring.redis.cluster.nodes[0]:" + clusterNodes.get(0),
"spring.redis.cluster.nodes[1]:" + clusterNodes.get(1)); "spring.redis.cluster.nodes[1]:" + clusterNodes.get(1));
assertThat(this.context.getBean(JedisConnectionFactory.class) assertThat(
.getClusterConnection()).isNotNull(); this.context.getBean(JedisConnectionFactory.class).getClusterConnection())
.isNotNull();
} }
private void load(String... environment) { private void load(String... environment) {
@ -149,14 +151,14 @@ public class RedisAutoConfigurationJedisTests {
} }
private void load(Class<?> config, String... environment) { private void load(Class<?> config, String... environment) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of(environment).applyTo(ctx); TestPropertyValues.of(environment).applyTo(context);
if (config != null) { if (config != null) {
ctx.register(config); context.register(config);
} }
ctx.register(RedisAutoConfiguration.class); context.register(RedisAutoConfiguration.class);
ctx.refresh(); context.refresh();
this.context = ctx; this.context = context;
} }
@Configuration @Configuration

@ -154,8 +154,8 @@ public class RedisAutoConfigurationTests {
load("spring.redis.password=password", "spring.redis.sentinel.master:mymaster", load("spring.redis.password=password", "spring.redis.sentinel.master:mymaster",
"spring.redis.sentinel.nodes:" "spring.redis.sentinel.nodes:"
+ StringUtils.collectionToCommaDelimitedString(sentinels)); + StringUtils.collectionToCommaDelimitedString(sentinels));
assertThat(this.context.getBean(LettuceConnectionFactory.class) assertThat(this.context.getBean(LettuceConnectionFactory.class).getPassword())
.getPassword()).isEqualTo("password"); .isEqualTo("password");
} }
@Test @Test
@ -173,8 +173,8 @@ public class RedisAutoConfigurationTests {
load("spring.redis.password=password", load("spring.redis.password=password",
"spring.redis.cluster.nodes[0]:" + clusterNodes.get(0), "spring.redis.cluster.nodes[0]:" + clusterNodes.get(0),
"spring.redis.cluster.nodes[1]:" + clusterNodes.get(1)); "spring.redis.cluster.nodes[1]:" + clusterNodes.get(1));
assertThat(this.context.getBean(LettuceConnectionFactory.class) assertThat(this.context.getBean(LettuceConnectionFactory.class).getPassword())
.getPassword()).isEqualTo("password"); .isEqualTo("password");
} }
private DefaultLettucePool getDefaultLettucePool(LettuceConnectionFactory factory) { private DefaultLettucePool getDefaultLettucePool(LettuceConnectionFactory factory) {

@ -88,8 +88,7 @@ public class DataSourceJmxConfigurationTests {
public void hikariAutoConfiguredUsesJmsFlag() throws MalformedObjectNameException { public void hikariAutoConfiguredUsesJmsFlag() throws MalformedObjectNameException {
String poolName = UUID.randomUUID().toString(); String poolName = UUID.randomUUID().toString();
load("spring.datasource.type=" + HikariDataSource.class.getName(), load("spring.datasource.type=" + HikariDataSource.class.getName(),
"spring.jmx.enabled=false", "spring.jmx.enabled=false", "spring.datasource.name=" + poolName,
"spring.datasource.name=" + poolName,
"spring.datasource.hikari.register-mbeans=true"); "spring.datasource.hikari.register-mbeans=true");
assertThat(this.context.getBeansOfType(HikariDataSource.class)).hasSize(1); assertThat(this.context.getBeansOfType(HikariDataSource.class)).hasSize(1);
assertThat(this.context.getBean(HikariDataSource.class).isRegisterMbeans()) assertThat(this.context.getBean(HikariDataSource.class).isRegisterMbeans())
@ -101,10 +100,12 @@ public class DataSourceJmxConfigurationTests {
private void validateHikariMBeansRegistration(MBeanServer mBeanServer, private void validateHikariMBeansRegistration(MBeanServer mBeanServer,
String poolName, boolean expected) throws MalformedObjectNameException { String poolName, boolean expected) throws MalformedObjectNameException {
assertThat(mBeanServer.isRegistered(new ObjectName( assertThat(mBeanServer.isRegistered(
"com.zaxxer.hikari:type=Pool (" + poolName + ")"))).isEqualTo(expected); new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")")))
assertThat(mBeanServer.isRegistered(new ObjectName( .isEqualTo(expected);
"com.zaxxer.hikari:type=PoolConfig (" + poolName + ")"))).isEqualTo(expected); assertThat(mBeanServer.isRegistered(
new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")")))
.isEqualTo(expected);
} }
@Test @Test
@ -127,16 +128,16 @@ public class DataSourceJmxConfigurationTests {
} }
private void load(Class<?> config, String... environment) { private void load(Class<?> config, String... environment) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
String jdbcUrl = "jdbc:hsqldb:mem:test-" + UUID.randomUUID().toString(); String jdbcUrl = "jdbc:hsqldb:mem:test-" + UUID.randomUUID().toString();
TestPropertyValues.of(environment).and("spring.datasource.url", jdbcUrl) TestPropertyValues.of(environment).and("spring.datasource.url", jdbcUrl)
.applyTo(ctx); .applyTo(context);
if (config != null) { if (config != null) {
ctx.register(config); context.register(config);
} }
ctx.register(JmxAutoConfiguration.class, DataSourceAutoConfiguration.class); context.register(JmxAutoConfiguration.class, DataSourceAutoConfiguration.class);
ctx.refresh(); context.refresh();
this.context = ctx; this.context = context;
} }
@Configuration @Configuration

@ -31,7 +31,7 @@ import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link HikariDataSourceConfiguration}. * Tests for {@link DataSourceAutoConfiguration} with Hikari.
* *
* @author Dave Syer * @author Dave Syer
* @author Stephane Nicoll * @author Stephane Nicoll
@ -104,8 +104,7 @@ public class HikariDataSourceConfigurationTests {
private void load(String... environment) { private void load(String... environment) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
TestPropertyValues.of(environment) TestPropertyValues.of(environment).and("spring.datasource.initialize", "false")
.and("spring.datasource.initialize", "false")
.and("spring.datasource.type", HikariDataSource.class.getName()) .and("spring.datasource.type", HikariDataSource.class.getName())
.applyTo(ctx); .applyTo(ctx);
ctx.register(DataSourceAutoConfiguration.class); ctx.register(DataSourceAutoConfiguration.class);

@ -81,13 +81,14 @@ public abstract class AbstractJpaAutoConfigurationTests {
@Test @Test
public void testNoDataSource() throws Exception { public void testNoDataSource() throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
ctx.register(PropertyPlaceholderAutoConfiguration.class, context.register(PropertyPlaceholderAutoConfiguration.class,
getAutoConfigureClass()); getAutoConfigureClass());
this.expected.expect(BeanCreationException.class); this.expected.expect(BeanCreationException.class);
this.expected.expectMessage("No qualifying bean"); this.expected.expectMessage("No qualifying bean");
this.expected.expectMessage("DataSource"); this.expected.expectMessage("DataSource");
ctx.refresh(); context.refresh();
context.close();
} }
@Test @Test
@ -200,7 +201,8 @@ public abstract class AbstractJpaAutoConfigurationTests {
load(configs, new Class<?>[0], environment); load(configs, new Class<?>[0], environment);
} }
protected void load(Class<?>[] configs, Class<?>[] autoConfigs, String... environment) { protected void load(Class<?>[] configs, Class<?>[] autoConfigs,
String... environment) {
load(configs, autoConfigs, null, environment); load(configs, autoConfigs, null, environment);
} }
@ -211,14 +213,13 @@ public abstract class AbstractJpaAutoConfigurationTests {
ctx.setClassLoader(classLoader); ctx.setClassLoader(classLoader);
} }
TestPropertyValues.of(environment) TestPropertyValues.of(environment)
.and("spring.datasource.generate-unique-name", "true") .and("spring.datasource.generate-unique-name", "true").applyTo(ctx);
.applyTo(ctx);
ctx.register(TestConfiguration.class); ctx.register(TestConfiguration.class);
if (!ObjectUtils.isEmpty(configs)) { if (!ObjectUtils.isEmpty(configs)) {
ctx.register(configs); ctx.register(configs);
} }
ctx.register( ctx.register(DataSourceAutoConfiguration.class,
DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class, TransactionAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, getAutoConfigureClass()); PropertyPlaceholderAutoConfiguration.class, getAutoConfigureClass());
if (!ObjectUtils.isEmpty(autoConfigs)) { if (!ObjectUtils.isEmpty(autoConfigs)) {
ctx.register(autoConfigs); ctx.register(autoConfigs);

@ -84,7 +84,8 @@ public class HibernateJpaAutoConfigurationTests
@Test @Test
public void testDataScript() throws Exception { public void testDataScript() throws Exception {
// This can't succeed because the data SQL is executed immediately after the schema // This can't succeed because the data SQL is executed immediately after the
// schema
// and Hibernate hasn't initialized yet at that point // and Hibernate hasn't initialized yet at that point
this.thrown.expect(BeanCreationException.class); this.thrown.expect(BeanCreationException.class);
load("spring.datasource.data:classpath:/city.sql"); load("spring.datasource.data:classpath:/city.sql");
@ -93,12 +94,11 @@ public class HibernateJpaAutoConfigurationTests
@Test @Test
public void testDataScriptRunsEarly() { public void testDataScriptRunsEarly() {
load(new Class<?>[] { TestInitializedJpaConfiguration.class }, null, load(new Class<?>[] { TestInitializedJpaConfiguration.class }, null,
new HideDataScriptClassLoader(), new HideDataScriptClassLoader(), "spring.jpa.show-sql=true",
"spring.jpa.show-sql=true",
"spring.jpa.hibernate.ddl-auto:create-drop", "spring.jpa.hibernate.ddl-auto:create-drop",
"spring.datasource.data:classpath:/city.sql"); "spring.datasource.data:classpath:/city.sql");
assertThat(this.context.getBean( assertThat(this.context.getBean(TestInitializedJpaConfiguration.class).called)
TestInitializedJpaConfiguration.class).called).isTrue(); .isTrue();
} }
@Test @Test
@ -158,7 +158,8 @@ public class HibernateJpaAutoConfigurationTests
@Autowired @Autowired
public void validateDataSourceIsInitialized( public void validateDataSourceIsInitialized(
EntityManagerFactory entityManagerFactory) { EntityManagerFactory entityManagerFactory) {
// Inject the entity manager to validate it is initialized at the injection point // Inject the entity manager to validate it is initialized at the injection
// point
EntityManager entityManager = entityManagerFactory.createEntityManager(); EntityManager entityManager = entityManagerFactory.createEntityManager();
City city = entityManager.find(City.class, 2000L); City city = entityManager.find(City.class, 2000L);
assertThat(city).isNotNull(); assertThat(city).isNotNull();
@ -204,21 +205,21 @@ public class HibernateJpaAutoConfigurationTests
private static class HideDataScriptClassLoader extends URLClassLoader { private static class HideDataScriptClassLoader extends URLClassLoader {
private static final List<String> HIDDEN_RESOURCES = private static final List<String> HIDDEN_RESOURCES = Arrays
Arrays.asList("schema-all.sql", "schema.sql"); .asList("schema-all.sql", "schema.sql");
HideDataScriptClassLoader() { HideDataScriptClassLoader() {
super(new URL[0], HideDataScriptClassLoader.class.getClassLoader()); super(new URL[0], HideDataScriptClassLoader.class.getClassLoader());
} }
@Override @Override
public Enumeration<URL> getResources(String name) throws IOException { public Enumeration<URL> getResources(String name) throws IOException {
if (HIDDEN_RESOURCES.contains(name)) { if (HIDDEN_RESOURCES.contains(name)) {
return new Vector().elements(); return new Vector<URL>().elements();
} }
return super.getResources(name); return super.getResources(name);
} }
} }
} }

@ -21,8 +21,9 @@ import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
@ -45,11 +46,11 @@ public class OAuth2RestOperationsConfigurationTests {
@Test @Test
public void clientIdConditionMatches() throws Exception { public void clientIdConditionMatches() throws Exception {
EnvironmentTestUtils.addEnvironment(this.environment, TestPropertyValues.of("security.oauth2.client.client-id=acme")
"security.oauth2.client.client-id=acme"); .applyTo(this.environment);
this.context = new SpringApplicationBuilder( this.context = new SpringApplicationBuilder(
OAuth2RestOperationsConfiguration.class).environment(this.environment) OAuth2RestOperationsConfiguration.class).environment(this.environment)
.web(false).run(); .web(WebApplicationType.NONE).run();
assertThat(this.context.getBean(OAuth2RestOperationsConfiguration.class)) assertThat(this.context.getBean(OAuth2RestOperationsConfiguration.class))
.isNotNull(); .isNotNull();
} }
@ -58,7 +59,7 @@ public class OAuth2RestOperationsConfigurationTests {
public void clientIdConditionDoesNotMatch() throws Exception { public void clientIdConditionDoesNotMatch() throws Exception {
this.context = new SpringApplicationBuilder( this.context = new SpringApplicationBuilder(
OAuth2RestOperationsConfiguration.class).environment(this.environment) OAuth2RestOperationsConfiguration.class).environment(this.environment)
.web(false).run(); .web(WebApplicationType.NONE).run();
this.thrown.expect(NoSuchBeanDefinitionException.class); this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean(OAuth2RestOperationsConfiguration.class); this.context.getBean(OAuth2RestOperationsConfiguration.class);
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2016 the original author or authors. * Copyright 2012-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

@ -107,20 +107,20 @@ public class WebFluxAutoConfigurationTests {
load(CustomArgumentResolvers.class); load(CustomArgumentResolvers.class);
RequestMappingHandlerAdapter adapter = this.context RequestMappingHandlerAdapter adapter = this.context
.getBean(RequestMappingHandlerAdapter.class); .getBean(RequestMappingHandlerAdapter.class);
assertThat((List<HandlerMethodArgumentResolver>) ReflectionTestUtils List<HandlerMethodArgumentResolver> customResolvers = (List<HandlerMethodArgumentResolver>) ReflectionTestUtils
.getField(adapter.getArgumentResolverConfigurer(), "customResolvers")) .getField(adapter.getArgumentResolverConfigurer(), "customResolvers");
.contains( assertThat(customResolvers).contains(
this.context.getBean("firstResolver", this.context.getBean("firstResolver",
HandlerMethodArgumentResolver.class), HandlerMethodArgumentResolver.class),
this.context.getBean("secondResolver", this.context.getBean("secondResolver",
HandlerMethodArgumentResolver.class)); HandlerMethodArgumentResolver.class));
} }
@Test @Test
public void shouldCustomizeCodecs() throws Exception { public void shouldCustomizeCodecs() throws Exception {
load(CustomCodecCustomizers.class); load(CustomCodecCustomizers.class);
CodecCustomizer codecCustomizer = CodecCustomizer codecCustomizer = this.context.getBean("firstCodecCustomizer",
this.context.getBean("firstCodecCustomizer", CodecCustomizer.class); CodecCustomizer.class);
assertThat(codecCustomizer).isNotNull(); assertThat(codecCustomizer).isNotNull();
verify(codecCustomizer).customize(any(ServerCodecConfigurer.class)); verify(codecCustomizer).customize(any(ServerCodecConfigurer.class));
} }
@ -133,7 +133,6 @@ public class WebFluxAutoConfigurationTests {
assertThat(hm.getUrlMap().get("/**")).isInstanceOf(ResourceWebHandler.class); assertThat(hm.getUrlMap().get("/**")).isInstanceOf(ResourceWebHandler.class);
ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/**"); ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/**");
assertThat(staticHandler.getLocations()).hasSize(5); assertThat(staticHandler.getLocations()).hasSize(5);
assertThat(hm.getUrlMap().get("/webjars/**")) assertThat(hm.getUrlMap().get("/webjars/**"))
.isInstanceOf(ResourceWebHandler.class); .isInstanceOf(ResourceWebHandler.class);
ResourceWebHandler webjarsHandler = (ResourceWebHandler) hm.getUrlMap() ResourceWebHandler webjarsHandler = (ResourceWebHandler) hm.getUrlMap()

@ -61,7 +61,8 @@ public class WebClientAutoConfigurationTests {
load(CodecConfiguration.class); load(CodecConfiguration.class);
WebClient.Builder builder = this.context.getBean(WebClient.Builder.class); WebClient.Builder builder = this.context.getBean(WebClient.Builder.class);
CodecCustomizer codecCustomizer = this.context.getBean(CodecCustomizer.class); CodecCustomizer codecCustomizer = this.context.getBean(CodecCustomizer.class);
WebClientCodecCustomizer clientCustomizer = this.context.getBean(WebClientCodecCustomizer.class); WebClientCodecCustomizer clientCustomizer = this.context
.getBean(WebClientCodecCustomizer.class);
builder.build(); builder.build();
assertThat(clientCustomizer).isNotNull(); assertThat(clientCustomizer).isNotNull();
verify(codecCustomizer).customize(any(CodecConfigurer.class)); verify(codecCustomizer).customize(any(CodecConfigurer.class));
@ -79,24 +80,22 @@ public class WebClientAutoConfigurationTests {
@Test @Test
public void shouldGetPrototypeScopedBean() throws Exception { public void shouldGetPrototypeScopedBean() throws Exception {
load(WebClientCustomizerConfig.class); load(WebClientCustomizerConfig.class);
ClientHttpConnector firstConnector = mock(ClientHttpConnector.class); ClientHttpConnector firstConnector = mock(ClientHttpConnector.class);
given(firstConnector.connect(any(), any(), any())).willReturn(Mono.empty()); given(firstConnector.connect(any(), any(), any())).willReturn(Mono.empty());
WebClient.Builder firstBuilder = this.context.getBean(WebClient.Builder.class); WebClient.Builder firstBuilder = this.context.getBean(WebClient.Builder.class);
firstBuilder.clientConnector(firstConnector).baseUrl("http://first.example.org"); firstBuilder.clientConnector(firstConnector).baseUrl("http://first.example.org");
ClientHttpConnector secondConnector = mock(ClientHttpConnector.class); ClientHttpConnector secondConnector = mock(ClientHttpConnector.class);
given(secondConnector.connect(any(), any(), any())).willReturn(Mono.empty()); given(secondConnector.connect(any(), any(), any())).willReturn(Mono.empty());
WebClient.Builder secondBuilder = this.context.getBean(WebClient.Builder.class); WebClient.Builder secondBuilder = this.context.getBean(WebClient.Builder.class);
secondBuilder.clientConnector(secondConnector).baseUrl("http://second.example.org"); secondBuilder.clientConnector(secondConnector)
.baseUrl("http://second.example.org");
assertThat(firstBuilder).isNotEqualTo(secondBuilder); assertThat(firstBuilder).isNotEqualTo(secondBuilder);
firstBuilder.build().get().uri("/foo").exchange().block(); firstBuilder.build().get().uri("/foo").exchange().block();
secondBuilder.build().get().uri("/foo").exchange().block(); secondBuilder.build().get().uri("/foo").exchange().block();
verify(firstConnector).connect(eq(HttpMethod.GET),
verify(firstConnector).connect(eq(HttpMethod.GET), eq(URI.create("http://first.example.org/foo")), any()); eq(URI.create("http://first.example.org/foo")), any());
verify(secondConnector).connect(eq(HttpMethod.GET), eq(URI.create("http://second.example.org/foo")), any()); verify(secondConnector).connect(eq(HttpMethod.GET),
eq(URI.create("http://second.example.org/foo")), any());
WebClientCustomizer customizer = this.context.getBean(WebClientCustomizer.class); WebClientCustomizer customizer = this.context.getBean(WebClientCustomizer.class);
verify(customizer, times(1)).customize(any(WebClient.Builder.class)); verify(customizer, times(1)).customize(any(WebClient.Builder.class));
} }
@ -108,7 +107,6 @@ public class WebClientAutoConfigurationTests {
assertThat(builder).isInstanceOf(MyWebClientBuilder.class); assertThat(builder).isInstanceOf(MyWebClientBuilder.class);
} }
private void load(Class<?>... config) { private void load(Class<?>... config) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(config); ctx.register(config);
@ -124,6 +122,7 @@ public class WebClientAutoConfigurationTests {
public CodecCustomizer myCodecCustomizer() { public CodecCustomizer myCodecCustomizer() {
return mock(CodecCustomizer.class); return mock(CodecCustomizer.class);
} }
} }
@Configuration @Configuration

@ -498,9 +498,9 @@ The Spring Boot starters bring a default embedded container for you:
* `spring-boot-starter-web` brings Tomcat with `spring-boot-starter-tomcat`, * `spring-boot-starter-web` brings Tomcat with `spring-boot-starter-tomcat`,
but `spring-boot-starter-jetty` and `spring-boot-starter-undertow` can be used instead. but `spring-boot-starter-jetty` and `spring-boot-starter-undertow` can be used instead.
* `spring-boot-starter-webflux` brings Reactor Netty with `spring-boot-starter-reactor-netty`, * `spring-boot-starter-webflux` brings Reactor Netty with
but `spring-boot-starter-tomcat`, `spring-boot-starter-jetty` and `spring-boot-starter-reactor-netty`, but `spring-boot-starter-tomcat`,
`spring-boot-starter-undertow` can be used instead. `spring-boot-starter-jetty` and `spring-boot-starter-undertow` can be used instead.
NOTE: Many starters only support Spring MVC, so they transitively bring NOTE: Many starters only support Spring MVC, so they transitively bring
`spring-boot-starter-web` into your application classpath `spring-boot-starter-web` into your application classpath
@ -548,8 +548,10 @@ Example in Gradle, for Spring WebFlux:
} }
---- ----
NOTE: `spring-boot-starter-reactor-netty` is required to use the `WebClient`, NOTE: `spring-boot-starter-reactor-netty` is required to use the `WebClient`, so excluding
so excluding it is not required if you wish to use a different HTTP server. it is not required if you wish to use a different HTTP server.
[[howto-configure-jetty]] [[howto-configure-jetty]]
=== Configure Jetty === Configure Jetty

@ -2187,6 +2187,8 @@ method:
} }
---- ----
[[boot-features-spring-webflux]] [[boot-features-spring-webflux]]
=== The '`Spring WebFlux framework`' === The '`Spring WebFlux framework`'
@ -2195,8 +2197,8 @@ Unlike Spring MVC, it does not require the Servlet API, is fully asynchronous an
non-blocking, and implements the http://www.reactive-streams.org/[Reactive Streams] non-blocking, and implements the http://www.reactive-streams.org/[Reactive Streams]
specification through http://projectreactor.io/[the Reactor project]. specification through http://projectreactor.io/[the Reactor project].
Spring WebFlux comes in two flavours — the annotation-based one is quite close to Spring WebFlux comes in two flavors — the annotation-based one is quite close to the
the Spring MVC model we know: Spring MVC model we know:
[source,java,indent=0] [source,java,indent=0]
---- ----
@ -2222,8 +2224,8 @@ the Spring MVC model we know:
} }
---- ----
'`WebFlux.fn`', the functional variant, separates the routing configuration from the actual handling '`WebFlux.fn`', the functional variant, separates the routing configuration from the
of the requests: actual handling of the requests:
[source,java,indent=0] [source,java,indent=0]
---- ----
@ -2236,17 +2238,17 @@ of the requests:
.andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers) .andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers)
.andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser); .andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser);
} }
} }
@Component @Component
public class UserHandler { public class UserHandler {
public Mono<ServerResponse> getUser(ServerRequest request) { public Mono<ServerResponse> getUser(ServerRequest request) {
// ... // ...
} }
public Mono<ServerResponse> getUserCustomers(ServerRequest request) { public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
// ... // ...
} }
@ -2256,19 +2258,20 @@ of the requests:
} }
---- ----
WebFlux is part of the Spring Framework and detailed information is available in WebFlux is part of the Spring Framework and detailed information is available in the
the {spring-reference}web.html#web-reactive[reference documentation]. {spring-reference}web.html#web-reactive[reference documentation].
To get started, add the `spring-boot-starter-webflux` module to your application. To get started, add the `spring-boot-starter-webflux` module to your application.
NOTE: Adding both `spring-boot-starter-web` and `spring-boot-starter-webflux` NOTE: Adding both `spring-boot-starter-web` and `spring-boot-starter-webflux` modules in
modules in your application will result in Spring Boot auto-configuring Spring your application will result in Spring Boot auto-configuring Spring MVC, not WebFlux. This
MVC, not WebFlux. This behavior has been chosen because many Spring developers behavior has been chosen because many Spring developers will add
will add `spring-boot-starter-webflux` to their Spring MVC application to use `spring-boot-starter-webflux` to their Spring MVC application to use the reactive
the reactive `WebCLient`. You can still enforce your choice by setting the `WebCLient`. You can still enforce your choice by setting the chosen application type like
chosen application type like
`SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)`. `SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)`.
[[boot-features-spring-webflux-auto-configuration]] [[boot-features-spring-webflux-auto-configuration]]
==== Spring WebFlux auto-configuration ==== Spring WebFlux auto-configuration
Spring Boot provides auto-configuration for Spring WebFlux that works well with most Spring Boot provides auto-configuration for Spring WebFlux that works well with most
@ -2276,29 +2279,30 @@ applications.
The auto-configuration adds the following features on top of Spring's defaults: The auto-configuration adds the following features on top of Spring's defaults:
* Configuring codecs for `HttpMessageReader` and `HttpMessageWriter` instances (see below). * Configuring codecs for `HttpMessageReader` and `HttpMessageWriter` instances (see
below).
* Support for serving static resources, including support for WebJars (see below). * Support for serving static resources, including support for WebJars (see below).
If you want to keep Spring Boot WebFlux features, and you just want to add additional
{spring-reference}web.html#web-reactive[WebFlux configuration] you can add your own
`@Configuration` class of type `WebFluxConfigurer`, but *without* `@EnableWebFlux`.
If you want to keep Spring Boot WebFlux features, and If you want to take complete control of Spring WebFlux, you can add your own
you just want to add additional {spring-reference}web.html#web-reactive[WebFlux configuration] `@Configuration` annotated with `@EnableWebFlux`.
you can add your own `@Configuration` class of type `WebFluxConfigurer`,
but *without* `@EnableWebFlux`.
If you want to take complete control of Spring WebFlux, you can add your own `@Configuration`
annotated with `@EnableWebFlux`.
[[boot-features-spring-webflux-httpcodecs]] [[boot-features-spring-webflux-httpcodecs]]
==== HTTP codecs with HttpMessageReaders and HttpMessageWriters ==== HTTP codecs with HttpMessageReaders and HttpMessageWriters
Spring WebFlux uses the `HttpMessageReader` and `HttpMessageWriter` interface to convert Spring WebFlux uses the `HttpMessageReader` and `HttpMessageWriter` interface to convert
HTTP requests and responses. They are configured with `CodecConfigurer` with sensible defaults, HTTP requests and responses. They are configured with `CodecConfigurer` with sensible
by looking at the libraries available in your classpath. defaults, by looking at the libraries available in your classpath.
Spring Boot will apply further customization using `CodecCustomizer` instances. Spring Boot will apply further customization using `CodecCustomizer` instances.
For example, `spring.jackson.*` configuration keys will be applied to the Jackson codec. For example, `spring.jackson.*` configuration keys will be applied to the Jackson codec.
If you need to add or customize codecs you can create a custom `CodecCustomizer` component: If you need to add or customize codecs you can create a custom `CodecCustomizer`
component:
[source,java,indent=0] [source,java,indent=0]
---- ----
@ -2319,6 +2323,8 @@ If you need to add or customize codecs you can create a custom `CodecCustomizer`
You can also leverage <<boot-features-json-components,Boot's custom JSON serializers and deserializers>>. You can also leverage <<boot-features-json-components,Boot's custom JSON serializers and deserializers>>.
[[boot-features-spring-webflux-static-content]] [[boot-features-spring-webflux-static-content]]
==== Static Content ==== Static Content
By default Spring Boot will serve static content from a directory called `/static` (or By default Spring Boot will serve static content from a directory called `/static` (or
@ -2353,9 +2359,9 @@ be deployed as war and have no use of the `src/main/webapp` directory.
[[boot-features-spring-webflux-template-engines]] [[boot-features-spring-webflux-template-engines]]
==== Template engines ==== Template engines
As well as REST web services, you can also use Spring WebFlux to serve dynamic HTML content. As well as REST web services, you can also use Spring WebFlux to serve dynamic HTML
Spring WebFlux supports a variety of templating technologies including Thymeleaf, FreeMarker content. Spring WebFlux supports a variety of templating technologies including Thymeleaf,
and Mustache. FreeMarker and Mustache.
Spring Boot includes auto-configuration support for the following templating engines: Spring Boot includes auto-configuration support for the following templating engines:
@ -2367,6 +2373,7 @@ When you're using one of these templating engines with the default configuration
templates will be picked up automatically from `src/main/resources/templates`. templates will be picked up automatically from `src/main/resources/templates`.
[[boot-features-jersey]] [[boot-features-jersey]]
=== JAX-RS and Jersey === JAX-RS and Jersey
If you prefer the JAX-RS programming model for REST endpoints you can use one of the If you prefer the JAX-RS programming model for REST endpoints you can use one of the
@ -5094,6 +5101,8 @@ TIP: `RestTemplateBuilder` includes a number of useful methods that can be used
configure a `RestTemplate`. For example, to add BASIC auth support you can use configure a `RestTemplate`. For example, to add BASIC auth support you can use
`builder.basicAuthorization("user", "password").build()`. `builder.basicAuthorization("user", "password").build()`.
[[boot-features-resttemplate-customization]] [[boot-features-resttemplate-customization]]
=== RestTemplate customization === RestTemplate customization
There are three main approaches to `RestTemplate` customization, depending on how broadly There are three main approaches to `RestTemplate` customization, depending on how broadly
@ -5121,9 +5130,9 @@ Lastly, the most extreme (and rarely used) option is to create your own
`RestTemplateBuilder` and will prevent any `RestTemplateCustomizer` beans from being used. `RestTemplateBuilder` and will prevent any `RestTemplateCustomizer` beans from being used.
[[boot-features-webclient]] [[boot-features-webclient]]
== Calling REST services with '`WebClient`' == Calling REST services with '`WebClient`'
If you have Spring WebFlux on your classpath, you can also choose to use `WebClient` If you have Spring WebFlux on your classpath, you can also choose to use `WebClient`
to call remote REST services; compared to `RestTemplate`, this client has more a to call remote REST services; compared to `RestTemplate`, this client has more a
functional feel to it and is fully reactive. You can create your own client functional feel to it and is fully reactive. You can create your own client
@ -5176,6 +5185,8 @@ would do locally at the point of injection.
Lastly, you can fall back to the original API and just use `WebClient.create()`. In that Lastly, you can fall back to the original API and just use `WebClient.create()`. In that
case, no auto-configuration nor `WebClientCustomizer` will be applied. case, no auto-configuration nor `WebClientCustomizer` will be applied.
[[boot-features-validation]] [[boot-features-validation]]
== Validation == Validation
The method validation feature supported by Bean Validation 1.1 is automatically enabled The method validation feature supported by Bean Validation 1.1 is automatically enabled
@ -5992,12 +6003,15 @@ definition.
A list of the auto-configuration that is enabled by `@WebMvcTest` can be A list of the auto-configuration that is enabled by `@WebMvcTest` can be
<<appendix-test-auto-configuration#test-auto-configuration,found in the appendix>>. <<appendix-test-auto-configuration#test-auto-configuration,found in the appendix>>.
[[boot-features-testing-spring-boot-applications-testing-autoconfigured-webflux-tests]] [[boot-features-testing-spring-boot-applications-testing-autoconfigured-webflux-tests]]
==== Auto-configured Spring WebFlux tests ==== Auto-configured Spring WebFlux tests
To test Spring WebFlux controllers are working as expected you can use the `@WebFluxTest` To test Spring WebFlux controllers are working as expected you can use the `@WebFluxTest`
annotation. `@WebFluxTest` will auto-configure the Spring WebFlux infrastructure and limit annotation. `@WebFluxTest` will auto-configure the Spring WebFlux infrastructure and limit
scanned beans to `@Controller`, `@ControllerAdvice`, `@JsonComponent`,and `WebFluxConfigurer`. scanned beans to `@Controller`, `@ControllerAdvice`, `@JsonComponent`,and
Regular `@Component` beans will not be scanned when using this annotation. `WebFluxConfigurer`. Regular `@Component` beans will not be scanned when using this
annotation.
Often `@WebFluxTest` will be limited to a single controller and used in combination with Often `@WebFluxTest` will be limited to a single controller and used in combination with
`@MockBean` to provide mock implementations for required collaborators. `@MockBean` to provide mock implementations for required collaborators.
@ -6010,14 +6024,14 @@ TIP: You can also auto-configure `WebTestClient` in a non-`@WebFluxTest`
[source,java,indent=0] [source,java,indent=0]
---- ----
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@WebFluxTest(UserVehicleController.class) @WebFluxTest(UserVehicleController.class)
@ -6046,6 +6060,7 @@ A list of the auto-configuration that is enabled by `@WebFluxTest` can be
<<appendix-test-auto-configuration#test-auto-configuration,found in the appendix>>. <<appendix-test-auto-configuration#test-auto-configuration,found in the appendix>>.
[[boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test]] [[boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test]]
==== Auto-configured Data JPA tests ==== Auto-configured Data JPA tests
`@DataJpaTest` can be used if you want to test JPA applications. By default it will `@DataJpaTest` can be used if you want to test JPA applications. By default it will

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2016 the original author or authors. * Copyright 2012-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

@ -40,7 +40,6 @@ import org.springframework.test.context.BootstrapWith;
* <p> * <p>
* Using this annotation will disable full auto-configuration and instead apply only * Using this annotation will disable full auto-configuration and instead apply only
* configuration relevant to Redis tests. * configuration relevant to Redis tests.
* <p>
* *
* @author Jayaram Pradhan * @author Jayaram Pradhan
* @since 2.0.0 * @since 2.0.0

@ -38,31 +38,28 @@ import org.springframework.web.reactive.function.client.WebClient;
* @since 2.0.0 * @since 2.0.0
*/ */
@Configuration @Configuration
@ConditionalOnClass({WebClient.class, WebTestClient.class}) @ConditionalOnClass({ WebClient.class, WebTestClient.class })
@AutoConfigureAfter(CodecsAutoConfiguration.class) @AutoConfigureAfter(CodecsAutoConfiguration.class)
public class WebTestClientAutoConfiguration { public class WebTestClientAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public WebTestClient webTestClient(ApplicationContext applicationContext) { public WebTestClient webTestClient(ApplicationContext applicationContext) {
WebTestClient.Builder builder = WebTestClient
WebTestClient.Builder clientBuilder = WebTestClient
.bindToApplicationContext(applicationContext).configureClient(); .bindToApplicationContext(applicationContext).configureClient();
customizeWebTestClientCodecs(clientBuilder, applicationContext); customizeWebTestClientCodecs(builder, applicationContext);
return clientBuilder.build(); return builder.build();
} }
private void customizeWebTestClientCodecs(WebTestClient.Builder clientBuilder, private void customizeWebTestClientCodecs(WebTestClient.Builder builder,
ApplicationContext applicationContext) { ApplicationContext applicationContext) {
Collection<CodecCustomizer> codecCustomizers = applicationContext Collection<CodecCustomizer> codecCustomizers = applicationContext
.getBeansOfType(CodecCustomizer.class).values(); .getBeansOfType(CodecCustomizer.class).values();
if (!CollectionUtils.isEmpty(codecCustomizers)) { if (!CollectionUtils.isEmpty(codecCustomizers)) {
clientBuilder.exchangeStrategies(ExchangeStrategies.builder() builder.exchangeStrategies(ExchangeStrategies.builder().codecs((codecs) -> {
.codecs(codecs -> { codecCustomizers
codecCustomizers.forEach(codecCustomizer -> codecCustomizer.customize(codecs)); .forEach((codecCustomizer) -> codecCustomizer.customize(codecs));
}) }).build());
.build());
} }
} }

@ -24,4 +24,5 @@ import org.springframework.data.repository.CrudRepository;
* @author Jayaram Pradhan * @author Jayaram Pradhan
*/ */
public interface ExampleRepository extends CrudRepository<PersonHash, String> { public interface ExampleRepository extends CrudRepository<PersonHash, String> {
} }

@ -58,7 +58,6 @@ public class WebTestClientAutoConfigurationTests {
verify(codecCustomizer).customize(any(CodecConfigurer.class)); verify(codecCustomizer).customize(any(CodecConfigurer.class));
} }
private void load(Class<?>... config) { private void load(Class<?>... config) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(config); ctx.register(config);

@ -20,6 +20,7 @@ import java.io.Closeable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -49,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class AbstractContextLoader<T extends ConfigurableApplicationContext, L extends AbstractContextLoader<T, ?>> class AbstractContextLoader<T extends ConfigurableApplicationContext, L extends AbstractContextLoader<T, ?>>
implements ContextLoader { implements ContextLoader {
private final Map<String, String> systemProperties = new HashMap<>(); private final Map<String, String> systemProperties = new LinkedHashMap<>();
private final List<String> env = new ArrayList<>(); private final List<String> env = new ArrayList<>();
@ -155,11 +156,11 @@ class AbstractContextLoader<T extends ConfigurableApplicationContext, L extends
return self(); return self();
} }
@SuppressWarnings("unchecked")
protected final L self() { protected final L self() {
return (L) this; return (L) this;
} }
/** /**
* Create and refresh a new {@link ApplicationContext} based on the current state of * Create and refresh a new {@link ApplicationContext} based on the current state of
* this loader. The context is consumed by the specified {@link ContextConsumer} and * this loader. The context is consumed by the specified {@link ContextConsumer} and
@ -226,13 +227,8 @@ class AbstractContextLoader<T extends ConfigurableApplicationContext, L extends
private T configureApplicationContext() { private T configureApplicationContext() {
T context = AbstractContextLoader.this.contextSupplier.get(); T context = AbstractContextLoader.this.contextSupplier.get();
if (this.classLoader != null) { if (this.classLoader != null) {
if (context instanceof DefaultResourceLoader) { Assert.isInstanceOf(DefaultResourceLoader.class, context);
((DefaultResourceLoader) context).setClassLoader(this.classLoader); ((DefaultResourceLoader) context).setClassLoader(this.classLoader);
}
else {
throw new IllegalStateException("Cannot configure ClassLoader: " + context
+ " is not a DefaultResourceLoader sub-class");
}
} }
if (!ObjectUtils.isEmpty(this.env)) { if (!ObjectUtils.isEmpty(this.env)) {
TestPropertyValues.of(this.env.toArray(new String[this.env.size()])) TestPropertyValues.of(this.env.toArray(new String[this.env.size()]))

@ -80,40 +80,6 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
*/ */
public interface ContextLoader { public interface ContextLoader {
/**
* Creates a {@code ContextLoader} that will load a standard
* {@link AnnotationConfigApplicationContext}.
*
* @return the context loader
*/
static StandardContextLoader standard() {
return new StandardContextLoader(AnnotationConfigApplicationContext::new);
}
/**
* Creates a {@code ContextLoader} that will load a
* {@link AnnotationConfigWebApplicationContext}.
*
* @return the context loader
*/
static ServletWebContextLoader servletWeb() {
return new ServletWebContextLoader(() -> {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
return context;
});
}
/**
* Creates a {@code ContextLoader} that will load a
* {@link GenericReactiveWebApplicationContext}.
*
* @return the context loader
*/
static ReactiveWebContextLoader reactiveWeb() {
return new ReactiveWebContextLoader(GenericReactiveWebApplicationContext::new);
}
/** /**
* Set the specified system property prior to loading the context and restore its * Set the specified system property prior to loading the context and restore its
* previous value once the consumer has been invoked and the context closed. If the * previous value once the consumer has been invoked and the context closed. If the
@ -197,4 +163,35 @@ public interface ContextLoader {
*/ */
<E extends Throwable> void loadAndFail(Class<E> exceptionType, Consumer<E> consumer); <E extends Throwable> void loadAndFail(Class<E> exceptionType, Consumer<E> consumer);
/**
* Creates a {@code ContextLoader} that will load a standard
* {@link AnnotationConfigApplicationContext}.
* @return the context loader
*/
static StandardContextLoader standard() {
return new StandardContextLoader(AnnotationConfigApplicationContext::new);
}
/**
* Creates a {@code ContextLoader} that will load a
* {@link AnnotationConfigWebApplicationContext}.
* @return the context loader
*/
static ServletWebContextLoader servletWeb() {
return new ServletWebContextLoader(() -> {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
return context;
});
}
/**
* Creates a {@code ContextLoader} that will load a
* {@link GenericReactiveWebApplicationContext}.
* @return the context loader
*/
static ReactiveWebContextLoader reactiveWeb() {
return new ReactiveWebContextLoader(GenericReactiveWebApplicationContext::new);
}
} }

@ -28,8 +28,8 @@ import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicati
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
*/ */
public final class ReactiveWebContextLoader public final class ReactiveWebContextLoader extends
extends AbstractContextLoader<GenericReactiveWebApplicationContext, ReactiveWebContextLoader> { AbstractContextLoader<GenericReactiveWebApplicationContext, ReactiveWebContextLoader> {
ReactiveWebContextLoader( ReactiveWebContextLoader(
Supplier<GenericReactiveWebApplicationContext> contextSupplier) { Supplier<GenericReactiveWebApplicationContext> contextSupplier) {

@ -29,8 +29,8 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
*/ */
public final class ServletWebContextLoader public final class ServletWebContextLoader extends
extends AbstractContextLoader<AnnotationConfigWebApplicationContext, ServletWebContextLoader> { AbstractContextLoader<AnnotationConfigWebApplicationContext, ServletWebContextLoader> {
ServletWebContextLoader( ServletWebContextLoader(
Supplier<AnnotationConfigWebApplicationContext> contextSupplier) { Supplier<AnnotationConfigWebApplicationContext> contextSupplier) {
@ -41,7 +41,8 @@ public final class ServletWebContextLoader
* Create and refresh a new {@link ConfigurableWebApplicationContext} based on the * Create and refresh a new {@link ConfigurableWebApplicationContext} based on the
* current state of this loader. The context is consumed by the specified * current state of this loader. The context is consumed by the specified
* {@link WebMvcContextConsumer consumer} and closed upon completion. * {@link WebMvcContextConsumer consumer} and closed upon completion.
* @param consumer the consumer of the created {@link ConfigurableWebApplicationContext} * @param consumer the consumer of the created
* {@link ConfigurableWebApplicationContext}
*/ */
public void loadWeb(WebMvcContextConsumer consumer) { public void loadWeb(WebMvcContextConsumer consumer) {
doLoad(consumer::accept); doLoad(consumer::accept);

@ -27,10 +27,11 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 2.0.0 * @since 2.0.0
*/ */
public class StandardContextLoader public class StandardContextLoader extends
extends AbstractContextLoader<AnnotationConfigApplicationContext, StandardContextLoader> { AbstractContextLoader<AnnotationConfigApplicationContext, StandardContextLoader> {
public StandardContextLoader(Supplier<AnnotationConfigApplicationContext> contextSupplier) { public StandardContextLoader(
Supplier<AnnotationConfigApplicationContext> contextSupplier) {
super(contextSupplier); super(contextSupplier);
} }

@ -59,7 +59,6 @@ class WebTestClientContextCustomizer implements ContextCustomizer {
if (beanFactory instanceof BeanDefinitionRegistry) { if (beanFactory instanceof BeanDefinitionRegistry) {
registerWebTestClient(context, (BeanDefinitionRegistry) context); registerWebTestClient(context, (BeanDefinitionRegistry) context);
} }
} }
private void registerWebTestClient(ConfigurableApplicationContext context, private void registerWebTestClient(ConfigurableApplicationContext context,
@ -75,10 +74,7 @@ class WebTestClientContextCustomizer implements ContextCustomizer {
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null || obj.getClass() != getClass()) { return (obj != null && obj.getClass().equals(getClass()));
return false;
}
return true;
} }
/** /**
@ -139,13 +135,14 @@ class WebTestClientContextCustomizer implements ContextCustomizer {
private void customizeWebTestClientCodecs(WebTestClient.Builder clientBuilder, private void customizeWebTestClientCodecs(WebTestClient.Builder clientBuilder,
ApplicationContext context) { ApplicationContext context) {
Collection<CodecCustomizer> codecCustomizers = context.getBeansOfType(CodecCustomizer.class).values(); Collection<CodecCustomizer> codecCustomizers = context
.getBeansOfType(CodecCustomizer.class).values();
if (!CollectionUtils.isEmpty(codecCustomizers)) { if (!CollectionUtils.isEmpty(codecCustomizers)) {
clientBuilder.exchangeStrategies(ExchangeStrategies.builder() clientBuilder.exchangeStrategies(
.codecs(codecs -> { ExchangeStrategies.builder().codecs(codecs -> {
codecCustomizers.forEach(codecCustomizer -> codecCustomizer.customize(codecs)); codecCustomizers.forEach(
}) codecCustomizer -> codecCustomizer.customize(codecs));
.build()); }).build());
} }
} }

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

@ -151,7 +151,8 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
/** /**
* Make a fully executable jar for *nix machines by prepending a launch script to the * Make a fully executable jar for *nix machines by prepending a launch script to the
* jar. <br/> * jar.
* <p>
* Currently, some tools do not accept this format so you may not always be able to * Currently, some tools do not accept this format so you may not always be able to
* use this technique. For example, <code>jar -xf</code> may silently fail to extract * use this technique. For example, <code>jar -xf</code> may silently fail to extract
* a jar or war that has been made fully-executable. It is recommended that you only * a jar or war that has been made fully-executable. It is recommended that you only

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2015 the original author or authors. * Copyright 2012-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -62,6 +62,7 @@ public enum CloudPlatform {
public boolean isActive(Environment environment) { public boolean isActive(Environment environment) {
return environment.containsProperty("HC_LANDSCAPE"); return environment.containsProperty("HC_LANDSCAPE");
} }
}; };
/** /**

@ -185,10 +185,8 @@ public final class ConfigurationPropertyName
start == 0, start == 0,
() -> "Element value '" + elementValue + "' must be a single item")); () -> "Element value '" + elementValue + "' must be a single item"));
if (!isIndexed(elementValue)) { if (!isIndexed(elementValue)) {
List<Character> invalidChars = ElementValidator.getInvalidChars(elementValue); InvalidConfigurationPropertyNameException.throwIfHasInvalidChars(elementValue,
if (!invalidChars.isEmpty()) { ElementValidator.getInvalidChars(elementValue));
throw new InvalidConfigurationPropertyNameException(invalidChars, elementValue);
}
} }
int length = this.elements.length; int length = this.elements.length;
CharSequence[] elements = new CharSequence[length + 1]; CharSequence[] elements = new CharSequence[length + 1];
@ -447,7 +445,8 @@ public final class ConfigurationPropertyName
Assert.notNull(name, "Name must not be null"); Assert.notNull(name, "Name must not be null");
if (name.length() >= 1 if (name.length() >= 1
&& (name.charAt(0) == '.' || name.charAt(name.length() - 1) == '.')) { && (name.charAt(0) == '.' || name.charAt(name.length() - 1) == '.')) {
throw new InvalidConfigurationPropertyNameException(Collections.singletonList('.'), name); throw new InvalidConfigurationPropertyNameException(name,
Collections.singletonList('.'));
} }
if (name.length() == 0) { if (name.length() == 0) {
return EMPTY; return EMPTY;
@ -456,10 +455,8 @@ public final class ConfigurationPropertyName
process(name, '.', (elementValue, start, end, indexed) -> { process(name, '.', (elementValue, start, end, indexed) -> {
if (elementValue.length() > 0) { if (elementValue.length() > 0) {
if (!indexed) { if (!indexed) {
List<Character> invalidChars = ElementValidator.getInvalidChars(elementValue); InvalidConfigurationPropertyNameException.throwIfHasInvalidChars(name,
if (!invalidChars.isEmpty()) { ElementValidator.getInvalidChars(elementValue));
throw new InvalidConfigurationPropertyNameException(invalidChars, name);
}
} }
elements.add(elementValue); elements.add(elementValue);
} }
@ -662,15 +659,6 @@ public final class ConfigurationPropertyName
return getInvalidChars(elementValue).isEmpty(); return getInvalidChars(elementValue).isEmpty();
} }
public static boolean isValidChar(char ch, int index) {
boolean isAlpha = ch >= 'a' && ch <= 'z';
boolean isNumeric = ch >= '0' && ch <= '9';
if (index == 0) {
return isAlpha;
}
return isAlpha || isNumeric || ch == '-';
}
private static List<Character> getInvalidChars(CharSequence elementValue) { private static List<Character> getInvalidChars(CharSequence elementValue) {
List<Character> chars = new ArrayList<>(); List<Character> chars = new ArrayList<>();
for (int i = 0; i < elementValue.length(); i++) { for (int i = 0; i < elementValue.length(); i++) {
@ -682,6 +670,15 @@ public final class ConfigurationPropertyName
return chars; return chars;
} }
public static boolean isValidChar(char ch, int index) {
boolean isAlpha = ch >= 'a' && ch <= 'z';
boolean isNumeric = ch >= '0' && ch <= '9';
if (index == 0) {
return isAlpha;
}
return isAlpha || isNumeric || ch == '-';
}
} }
} }

@ -19,22 +19,22 @@ package org.springframework.boot.context.properties.source;
import java.util.List; import java.util.List;
/** /**
* Exception thrown when {@link ConfigurationPropertyName} has invalid * Exception thrown when {@link ConfigurationPropertyName} has invalid characters.
* characters.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @since 2.0.0 * @since 2.0.0
*/ */
public class InvalidConfigurationPropertyNameException extends RuntimeException { public class InvalidConfigurationPropertyNameException extends RuntimeException {
private final List<Character> invalidCharacters;
private final CharSequence name; private final CharSequence name;
public InvalidConfigurationPropertyNameException(List<Character> invalidCharacters, CharSequence name) { private final List<Character> invalidCharacters;
public InvalidConfigurationPropertyNameException(CharSequence name,
List<Character> invalidCharacters) {
super("Configuration property name '" + name + "' is not valid"); super("Configuration property name '" + name + "' is not valid");
this.invalidCharacters = invalidCharacters;
this.name = name; this.name = name;
this.invalidCharacters = invalidCharacters;
} }
public List<Character> getInvalidCharacters() { public List<Character> getInvalidCharacters() {
@ -45,5 +45,11 @@ public class InvalidConfigurationPropertyNameException extends RuntimeException
return this.name; return this.name;
} }
} public static void throwIfHasInvalidChars(CharSequence name,
List<Character> invalidCharacters) {
if (!invalidCharacters.isEmpty()) {
throw new InvalidConfigurationPropertyNameException(name, invalidCharacters);
}
}
}

@ -24,34 +24,44 @@ import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.FailureAnalysis;
/** /**
* An {@link AbstractFailureAnalyzer} that performs analysis of failures * An {@link AbstractFailureAnalyzer} that performs analysis of failures caused by
* caused by {@link InvalidConfigurationPropertyNameException}. * {@link InvalidConfigurationPropertyNameException}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @since 2.0.0 * @since 2.0.0
*/ */
public class InvalidConfigurationPropertyNameFailureAnalyzer extends AbstractFailureAnalyzer<InvalidConfigurationPropertyNameException> { public class InvalidConfigurationPropertyNameFailureAnalyzer
extends AbstractFailureAnalyzer<InvalidConfigurationPropertyNameException> {
@Override @Override
protected FailureAnalysis analyze(Throwable rootFailure, InvalidConfigurationPropertyNameException cause) { protected FailureAnalysis analyze(Throwable rootFailure,
BeanCreationException exception = findCause(rootFailure, BeanCreationException.class); InvalidConfigurationPropertyNameException cause) {
String description = buildDescription(cause, exception); BeanCreationException exception = findCause(rootFailure,
String action = String.format("Modify '%s' so that it conforms to the canonical names requirements.", cause.getName()); BeanCreationException.class);
return new FailureAnalysis(description, action, cause); String action = String.format(
"Modify '%s' so that it conforms to the canonical names requirements.",
cause.getName());
return new FailureAnalysis(buildDescription(cause, exception), action, cause);
} }
private String buildDescription(InvalidConfigurationPropertyNameException cause, BeanCreationException exception) { private String buildDescription(InvalidConfigurationPropertyNameException cause,
StringBuilder description = new StringBuilder( BeanCreationException exception) {
String.format("Configuration property name '%s' is not valid:%n", cause.getName())); StringBuilder description = new StringBuilder(String.format(
String invalid = cause.getInvalidCharacters().stream().map(c -> "'" + c.toString() + "'").collect(Collectors.joining(",")); "Configuration property name '%s' is not valid:%n", cause.getName()));
String invalid = cause.getInvalidCharacters().stream().map(this::quote)
.collect(Collectors.joining(", "));
description.append(String.format("%n Invalid characters: %s", invalid)); description.append(String.format("%n Invalid characters: %s", invalid));
if (exception != null) { if (exception != null) {
description.append(String.format("%n Bean: %s", exception.getBeanName())); description.append(String.format("%n Bean: %s", exception.getBeanName()));
} }
description.append(String.format("%n Reason: Canonical names should be kebab-case('-' separated), lowercase alpha-numeric characters" + description.append(String.format("%n Reason: Canonical names should be "
" and must start with a letter")); + "kebab-case ('-' separated), lowercase alpha-numeric characters"
+ " and must start with a letter"));
return description.toString(); return description.toString();
} }
} private String quote(Character c) {
return "'" + c + "'";
}
}

@ -62,11 +62,12 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void replacePropertySource(ConfigurableEnvironment environment, private void replacePropertySource(ConfigurableEnvironment environment,
String sourceName, PropertySource<?> propertySource) { String sourceName, PropertySource<?> propertySource) {
Map<String, Object> originalSource = (Map<String, Object>) propertySource Map<String, Object> originalSource = (Map<String, Object>) propertySource
.getSource(); .getSource();
SystemEnvironmentPropertySource source = new OriginAwareSystemEnvironmentPropertySource(sourceName, originalSource); SystemEnvironmentPropertySource source = new OriginAwareSystemEnvironmentPropertySource(
environment.getPropertySources().replace(sourceName, source); sourceName, originalSource);
} environment.getPropertySources().replace(sourceName, source);
}
@Override @Override
public int getOrder() { public int getOrder() {
@ -80,10 +81,11 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor
/** /**
* {@link SystemEnvironmentPropertySource} that also tracks {@link Origin}. * {@link SystemEnvironmentPropertySource} that also tracks {@link Origin}.
*/ */
protected static class OriginAwareSystemEnvironmentPropertySource extends SystemEnvironmentPropertySource protected static class OriginAwareSystemEnvironmentPropertySource
implements OriginLookup<String> { extends SystemEnvironmentPropertySource implements OriginLookup<String> {
OriginAwareSystemEnvironmentPropertySource(String name, Map<String, Object> source) { OriginAwareSystemEnvironmentPropertySource(String name,
Map<String, Object> source) {
super(name, source); super(name, source);
} }
@ -95,6 +97,7 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor
} }
return null; return null;
} }
} }
} }

@ -19,8 +19,9 @@ package org.springframework.boot.web.codec;
import org.springframework.http.codec.CodecConfigurer; import org.springframework.http.codec.CodecConfigurer;
/** /**
* Callback interface that can be used to customize codecs configuration * Callback interface that can be used to customize codecs configuration for an HTTP
* for an HTTP client and/or server with a {@link CodecConfigurer}. * client and/or server with a {@link CodecConfigurer}.
*
* @author Brian Clozel * @author Brian Clozel
* @since 2.0 * @since 2.0
*/ */

@ -19,7 +19,9 @@ package org.springframework.boot.web.reactive.function.client;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
/** /**
* Callback interface that can be used to customize a {@link WebClient.Builder}. * Callback interface that can be used to customize a
* {@link org.springframework.web.reactive.function.client.WebClient.Builder
* WebClient.Builder}.
* *
* @author Brian Clozel * @author Brian Clozel
* @since 2.0.0 * @since 2.0.0
@ -28,8 +30,11 @@ import org.springframework.web.reactive.function.client.WebClient;
public interface WebClientCustomizer { public interface WebClientCustomizer {
/** /**
* Callback to customize a {@link WebClient.Builder} instance. * Callback to customize a
* {@link org.springframework.web.reactive.function.client.WebClient.Builder
* WebClient.Builder} instance.
* @param webClientBuilder the client builder to customize * @param webClientBuilder the client builder to customize
*/ */
void customize(WebClient.Builder webClientBuilder); void customize(WebClient.Builder webClientBuilder);
} }

@ -311,8 +311,7 @@ public class CollectionBinderTests {
source.put("foo.items", "a,b,c"); source.put("foo.items", "a,b,c");
this.sources.add(source); this.sources.add(source);
ExampleCollectionBean result = this.binder ExampleCollectionBean result = this.binder
.bind("foo", ExampleCollectionBean.class) .bind("foo", ExampleCollectionBean.class).get();
.get();
assertThat(result.getItems()).hasSize(4); assertThat(result.getItems()).hasSize(4);
assertThat(result.getItems()).containsExactly("a", "b", "c", "d"); assertThat(result.getItems()).containsExactly("a", "b", "c", "d");
} }

@ -37,13 +37,15 @@ public class InvalidConfigurationPropertyNameFailureAnalyzerTests {
private InvalidConfigurationPropertyNameFailureAnalyzer analyzer = new InvalidConfigurationPropertyNameFailureAnalyzer(); private InvalidConfigurationPropertyNameFailureAnalyzer analyzer = new InvalidConfigurationPropertyNameFailureAnalyzer();
@Test @Test
public void analysisWhenRootCauseIsBeanCreationFailureShouldContainBeanName() throws Exception { public void analysisWhenRootCauseIsBeanCreationFailureShouldContainBeanName()
throws Exception {
BeanCreationException failure = createFailure(InvalidPrefixConfiguration.class); BeanCreationException failure = createFailure(InvalidPrefixConfiguration.class);
FailureAnalysis analysis = this.analyzer.analyze(failure); FailureAnalysis analysis = this.analyzer.analyze(failure);
assertThat(analysis.getDescription()).contains(String.format( assertThat(analysis.getDescription()).contains(String.format(
"%n Invalid characters: %s%n Bean: %s%n Reason: %s", "%n Invalid characters: %s%n Bean: %s%n Reason: %s", "'F', 'P'",
"'F','P'", "invalidPrefixProperties", "Canonical names should be kebab-case('-' separated), " + "invalidPrefixProperties",
"lowercase alpha-numeric characters and must start with a letter")); "Canonical names should be kebab-case ('-' separated), "
+ "lowercase alpha-numeric characters and must start with a letter"));
} }
private BeanCreationException createFailure(Class<?> configuration) { private BeanCreationException createFailure(Class<?> configuration) {
@ -81,6 +83,7 @@ public class InvalidConfigurationPropertyNameFailureAnalyzerTests {
public void setValue(String value) { public void setValue(String value) {
this.value = value; this.value = value;
} }
} }
} }

@ -52,7 +52,8 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessorTests {
postProcessor.postProcessEnvironment(this.environment, null); postProcessor.postProcessEnvironment(this.environment, null);
PropertySource<?> replaced = this.environment.getPropertySources() PropertySource<?> replaced = this.environment.getPropertySources()
.get("systemEnvironment"); .get("systemEnvironment");
assertThat(replaced).isInstanceOf(OriginAwareSystemEnvironmentPropertySource.class); assertThat(replaced)
.isInstanceOf(OriginAwareSystemEnvironmentPropertySource.class);
} }
@Test @Test
@ -62,37 +63,39 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessorTests {
PropertySource<?> original = this.environment.getPropertySources() PropertySource<?> original = this.environment.getPropertySources()
.get("systemEnvironment"); .get("systemEnvironment");
postProcessor.postProcessEnvironment(this.environment, null); postProcessor.postProcessEnvironment(this.environment, null);
OriginAwareSystemEnvironmentPropertySource replaced = (OriginAwareSystemEnvironmentPropertySource) this.environment.getPropertySources() OriginAwareSystemEnvironmentPropertySource replaced = (OriginAwareSystemEnvironmentPropertySource) this.environment
.get("systemEnvironment"); .getPropertySources().get("systemEnvironment");
Map<String, Object> originalMap = (Map<String, Object>) original.getSource(); Map<String, Object> originalMap = (Map<String, Object>) original.getSource();
Map<String, Object> replacedMap = replaced Map<String, Object> replacedMap = replaced.getSource();
.getSource();
for (Map.Entry<String, Object> entry : originalMap.entrySet()) { for (Map.Entry<String, Object> entry : originalMap.entrySet()) {
Object actual = replacedMap.get(entry.getKey()); Object actual = replacedMap.get(entry.getKey());
assertThat(actual).isEqualTo(entry.getValue()); assertThat(actual).isEqualTo(entry.getValue());
assertThat(replaced.getOrigin(entry.getKey())).isInstanceOf(SystemEnvironmentOrigin.class); assertThat(replaced.getOrigin(entry.getKey()))
.isInstanceOf(SystemEnvironmentOrigin.class);
} }
} }
@Test @Test
public void replacedPropertySourceWhenPropertyAbsentShouldReturnNullOrigin() throws Exception { public void replacedPropertySourceWhenPropertyAbsentShouldReturnNullOrigin()
throws Exception {
SystemEnvironmentPropertySourceEnvironmentPostProcessor postProcessor = new SystemEnvironmentPropertySourceEnvironmentPostProcessor(); SystemEnvironmentPropertySourceEnvironmentPostProcessor postProcessor = new SystemEnvironmentPropertySourceEnvironmentPostProcessor();
postProcessor.postProcessEnvironment(this.environment, null); postProcessor.postProcessEnvironment(this.environment, null);
OriginAwareSystemEnvironmentPropertySource replaced = (OriginAwareSystemEnvironmentPropertySource) this.environment.getPropertySources() OriginAwareSystemEnvironmentPropertySource replaced = (OriginAwareSystemEnvironmentPropertySource) this.environment
.get("systemEnvironment"); .getPropertySources().get("systemEnvironment");
assertThat(replaced.getOrigin("NON_EXISTENT")).isNull(); assertThat(replaced.getOrigin("NON_EXISTENT")).isNull();
} }
@Test @Test
@SuppressWarnings("unchecked")
public void replacedPropertySourceShouldResolveProperty() throws Exception { public void replacedPropertySourceShouldResolveProperty() throws Exception {
SystemEnvironmentPropertySourceEnvironmentPostProcessor postProcessor = new SystemEnvironmentPropertySourceEnvironmentPostProcessor(); SystemEnvironmentPropertySourceEnvironmentPostProcessor postProcessor = new SystemEnvironmentPropertySourceEnvironmentPostProcessor();
Map<String, Object> source = Collections.singletonMap("FOO_BAR_BAZ", "hello"); Map<String, Object> source = Collections.singletonMap("FOO_BAR_BAZ", "hello");
this.environment.getPropertySources().replace("systemEnvironment", new SystemEnvironmentPropertySource("systemEnvironment", source)); this.environment.getPropertySources().replace("systemEnvironment",
new SystemEnvironmentPropertySource("systemEnvironment", source));
postProcessor.postProcessEnvironment(this.environment, null); postProcessor.postProcessEnvironment(this.environment, null);
OriginAwareSystemEnvironmentPropertySource replaced = (OriginAwareSystemEnvironmentPropertySource) this.environment.getPropertySources() OriginAwareSystemEnvironmentPropertySource replaced = (OriginAwareSystemEnvironmentPropertySource) this.environment
.get("systemEnvironment"); .getPropertySources().get("systemEnvironment");
SystemEnvironmentOrigin origin = (SystemEnvironmentOrigin) replaced.getOrigin("foo.bar.baz"); SystemEnvironmentOrigin origin = (SystemEnvironmentOrigin) replaced
.getOrigin("foo.bar.baz");
assertThat(origin.getProperty()).isEqualTo("FOO_BAR_BAZ"); assertThat(origin.getProperty()).isEqualTo("FOO_BAR_BAZ");
assertThat(replaced.getProperty("foo.bar.baz")).isEqualTo("hello"); assertThat(replaced.getProperty("foo.bar.baz")).isEqualTo("hello");
} }

@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link JsonComponentModule}. * Tests for {@link JsonComponentModule}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Vladimir Tsanev
*/ */
public class JsonComponentModuleTests { public class JsonComponentModuleTests {
@ -73,11 +74,11 @@ public class JsonComponentModuleTests {
} }
private void load(Class<?>... configs) { private void load(Class<?>... configs) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
ctx.register(configs); context.register(configs);
ctx.register(JsonComponentModule.class); context.register(JsonComponentModule.class);
ctx.refresh(); context.refresh();
this.context = ctx; this.context = context;
} }
private void assertSerialize(Module module) throws Exception { private void assertSerialize(Module module) throws Exception {
@ -117,5 +118,7 @@ public class JsonComponentModuleTests {
static class ConcreteSerializer extends AbstractSerializer { static class ConcreteSerializer extends AbstractSerializer {
} }
} }
} }

@ -32,4 +32,5 @@ public class NettyWebServerTests {
this.server = new NettyWebServer(null, null); this.server = new NettyWebServer(null, null);
this.server.stop(); this.server.stop();
} }
} }

Loading…
Cancel
Save