Auto-configure Data Mongo if user provides MongoDbFactory but no client

Previously, if a user defined a MongoDbFactory bean but did not define
a client bean, MongoDataAutoConfiguration would back off leaving the
context without a MongoTemplate, etc.

This commit reworks the auto-configuration so that only the
auto-configuration of a MongoDbFactory is dependent on the existence
of a Mongo client bean. Auto-configuration of the other components
that depend on a MongoDbFactory will now continue in the absence of a
Mongo client bean.

Closes gh-17416
pull/17511/head
Andy Wilkinson 5 years ago
parent 7553b60e68
commit 7f85aba546

@ -16,43 +16,18 @@
package org.springframework.boot.autoconfigure.data.mongo;
import com.mongodb.ClientSessionOptions;
import com.mongodb.DB;
import com.mongodb.MongoClient;
import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoDatabase;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration.AnyMongoClientAvailable;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoDbFactorySupport;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data's mongo support.
@ -74,131 +49,9 @@ import org.springframework.util.StringUtils;
*/
@Configuration
@ConditionalOnClass({ MongoClient.class, com.mongodb.client.MongoClient.class, MongoTemplate.class })
@Conditional(AnyMongoClientAvailable.class)
@EnableConfigurationProperties(MongoProperties.class)
@Import(MongoDataConfiguration.class)
@Import({ MongoDataConfiguration.class, MongoDbFactoryConfiguration.class, MongoDbFactoryDependentConfiguration.class })
@AutoConfigureAfter(MongoAutoConfiguration.class)
public class MongoDataAutoConfiguration {
private final MongoProperties properties;
public MongoDataAutoConfiguration(MongoProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean(MongoDbFactory.class)
public MongoDbFactorySupport<?> mongoDbFactory(ObjectProvider<MongoClient> mongo,
ObjectProvider<com.mongodb.client.MongoClient> mongoClient) {
MongoClient preferredClient = mongo.getIfAvailable();
if (preferredClient != null) {
return new SimpleMongoDbFactory(preferredClient, this.properties.getMongoClientDatabase());
}
com.mongodb.client.MongoClient fallbackClient = mongoClient.getIfAvailable();
if (fallbackClient != null) {
return new SimpleMongoClientDbFactory(fallbackClient, this.properties.getMongoClientDatabase());
}
throw new IllegalStateException("Expected to find at least one MongoDB client.");
}
@Bean
@ConditionalOnMissingBean
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter converter) {
return new MongoTemplate(mongoDbFactory, converter);
}
@Bean
@ConditionalOnMissingBean(MongoConverter.class)
public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory, MongoMappingContext context,
MongoCustomConversions conversions) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);
mappingConverter.setCustomConversions(conversions);
return mappingConverter;
}
@Bean
@ConditionalOnMissingBean
public GridFsTemplate gridFsTemplate(MongoDbFactory mongoDbFactory, MongoTemplate mongoTemplate) {
return new GridFsTemplate(new GridFsMongoDbFactory(mongoDbFactory, this.properties),
mongoTemplate.getConverter());
}
/**
* {@link MongoDbFactory} decorator to respect
* {@link MongoProperties#getGridFsDatabase()} if set.
*/
private static class GridFsMongoDbFactory implements MongoDbFactory {
private final MongoDbFactory mongoDbFactory;
private final MongoProperties properties;
GridFsMongoDbFactory(MongoDbFactory mongoDbFactory, MongoProperties properties) {
Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null");
Assert.notNull(properties, "Properties must not be null");
this.mongoDbFactory = mongoDbFactory;
this.properties = properties;
}
@Override
public MongoDatabase getDb() throws DataAccessException {
String gridFsDatabase = this.properties.getGridFsDatabase();
if (StringUtils.hasText(gridFsDatabase)) {
return this.mongoDbFactory.getDb(gridFsDatabase);
}
return this.mongoDbFactory.getDb();
}
@Override
public MongoDatabase getDb(String dbName) throws DataAccessException {
return this.mongoDbFactory.getDb(dbName);
}
@Override
public PersistenceExceptionTranslator getExceptionTranslator() {
return this.mongoDbFactory.getExceptionTranslator();
}
@Override
@Deprecated
public DB getLegacyDb() {
return this.mongoDbFactory.getLegacyDb();
}
@Override
public ClientSession getSession(ClientSessionOptions options) {
return this.mongoDbFactory.getSession(options);
}
@Override
public MongoDbFactory withSession(ClientSession session) {
return this.mongoDbFactory.withSession(session);
}
}
/**
* Check if either a {@link MongoClient com.mongodb.MongoClient} or
* {@link com.mongodb.client.MongoClient com.mongodb.client.MongoClient} bean is
* available.
*/
static class AnyMongoClientAvailable extends AnyNestedCondition {
AnyMongoClientAvailable() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(MongoClient.class)
static class PreferredClientAvailable {
}
@ConditionalOnBean(com.mongodb.client.MongoClient.class)
static class FallbackClientAvailable {
}
}
}

@ -0,0 +1,82 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.data.mongo;
import com.mongodb.MongoClient;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.mongo.MongoDbFactoryConfiguration.AnyMongoClientAvailable;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoDbFactorySupport;
import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
/**
* Configuration for a {@link MongoDbFactory}.
*
* @author Andy Wilkinson
*/
@Configuration
@ConditionalOnMissingBean(MongoDbFactory.class)
@Conditional(AnyMongoClientAvailable.class)
class MongoDbFactoryConfiguration {
@Bean
public MongoDbFactorySupport<?> mongoDbFactory(ObjectProvider<MongoClient> mongo,
ObjectProvider<com.mongodb.client.MongoClient> mongoClient, MongoProperties properties) {
MongoClient preferredClient = mongo.getIfAvailable();
if (preferredClient != null) {
return new SimpleMongoDbFactory(preferredClient, properties.getMongoClientDatabase());
}
com.mongodb.client.MongoClient fallbackClient = mongoClient.getIfAvailable();
if (fallbackClient != null) {
return new SimpleMongoClientDbFactory(fallbackClient, properties.getMongoClientDatabase());
}
throw new IllegalStateException("Expected to find at least one MongoDB client.");
}
/**
* Check if either a {@link MongoClient com.mongodb.MongoClient} or
* {@link com.mongodb.client.MongoClient com.mongodb.client.MongoClient} bean is
* available.
*/
static class AnyMongoClientAvailable extends AnyNestedCondition {
AnyMongoClientAvailable() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(MongoClient.class)
static class PreferredClientAvailable {
}
@ConditionalOnBean(com.mongodb.client.MongoClient.class)
static class FallbackClientAvailable {
}
}
}

@ -0,0 +1,135 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.data.mongo;
import com.mongodb.ClientSessionOptions;
import com.mongodb.DB;
import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoDatabase;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Configuration for Mongo-related beans that depend on a {@link MongoDbFactory}.
*
* @author Andy Wilkinson
*/
@Configuration
@ConditionalOnBean(MongoDbFactory.class)
class MongoDbFactoryDependentConfiguration {
private final MongoProperties properties;
MongoDbFactoryDependentConfiguration(MongoProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter converter) {
return new MongoTemplate(mongoDbFactory, converter);
}
@Bean
@ConditionalOnMissingBean(MongoConverter.class)
public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory, MongoMappingContext context,
MongoCustomConversions conversions) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);
mappingConverter.setCustomConversions(conversions);
return mappingConverter;
}
@Bean
@ConditionalOnMissingBean
public GridFsTemplate gridFsTemplate(MongoDbFactory mongoDbFactory, MongoTemplate mongoTemplate) {
return new GridFsTemplate(new GridFsMongoDbFactory(mongoDbFactory, this.properties),
mongoTemplate.getConverter());
}
/**
* {@link MongoDbFactory} decorator to respect
* {@link MongoProperties#getGridFsDatabase()} if set.
*/
static class GridFsMongoDbFactory implements MongoDbFactory {
private final MongoDbFactory mongoDbFactory;
private final MongoProperties properties;
GridFsMongoDbFactory(MongoDbFactory mongoDbFactory, MongoProperties properties) {
Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null");
Assert.notNull(properties, "Properties must not be null");
this.mongoDbFactory = mongoDbFactory;
this.properties = properties;
}
@Override
public MongoDatabase getDb() throws DataAccessException {
String gridFsDatabase = this.properties.getGridFsDatabase();
if (StringUtils.hasText(gridFsDatabase)) {
return this.mongoDbFactory.getDb(gridFsDatabase);
}
return this.mongoDbFactory.getDb();
}
@Override
public MongoDatabase getDb(String dbName) throws DataAccessException {
return this.mongoDbFactory.getDb(dbName);
}
@Override
public PersistenceExceptionTranslator getExceptionTranslator() {
return this.mongoDbFactory.getExceptionTranslator();
}
@Override
@Deprecated
public DB getLegacyDb() {
return this.mongoDbFactory.getLegacyDb();
}
@Override
public ClientSession getSession(ClientSessionOptions options) {
return this.mongoDbFactory.getSession(options);
}
@Override
public MongoDbFactory withSession(ClientSession session) {
return this.mongoDbFactory.withSession(session);
}
}
}

@ -31,7 +31,9 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoCon
import org.springframework.boot.autoconfigure.data.mongo.city.City;
import org.springframework.boot.autoconfigure.data.mongo.country.Country;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@ -63,7 +65,8 @@ public class MongoDataAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(PropertyPlaceholderAutoConfiguration.class,
MongoAutoConfiguration.class, MongoDataAutoConfiguration.class));
MongoAutoConfiguration.class, MongoDataAutoConfiguration.class))
.withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO));
@Test
public void templateExists() {
@ -157,7 +160,7 @@ public class MongoDataAutoConfigurationTests {
public void backsOffIfMongoClientBeanIsNotPresent() {
ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MongoDataAutoConfiguration.class));
runner.run((context) -> assertThat(context).doesNotHaveBean(MongoDataAutoConfiguration.class));
runner.run((context) -> assertThat(context).doesNotHaveBean(MongoTemplate.class));
}
@Test
@ -176,6 +179,12 @@ public class MongoDataAutoConfigurationTests {
});
}
@Test
public void autoConfiguresIfUserProvidesMongoDbFactoryButNoClient() {
this.contextRunner.withUserConfiguration(MongoDbFactoryConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(MongoTemplate.class));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static void assertDomainTypesDiscovered(MongoMappingContext mappingContext, Class<?>... types) {
Set<Class> initialEntitySet = (Set<Class>) ReflectionTestUtils.getField(mappingContext, "initialEntitySet");
@ -208,6 +217,16 @@ public class MongoDataAutoConfigurationTests {
}
@Configuration
static class MongoDbFactoryConfiguration {
@Bean
MongoDbFactory mongoDbFactory() {
return new SimpleMongoClientDbFactory(MongoClients.create(), "test");
}
}
private static class MyConverter implements Converter<MongoClient, Boolean> {
@Override

Loading…
Cancel
Save