Improve JDBC configuration

This commit improves JDBC configuration by introducing `@SpringSessionDataSource` qualifier for explicitly declaring a `DataSource` to be used by Spring Session. This is in particular useful in scenarios with multiple `DataSource` beans present in the application context.

As a consequence, JDBC configuration is simplified and no longer registers a Spring Session specific `JdbcTemplate` bean.

Closes gh-863
This commit is contained in:
Vedran Pavic
2017-08-31 22:40:10 +02:00
parent 19b8effa41
commit e3b61d25bb
3 changed files with 203 additions and 25 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2014-2016 the original author or authors. * Copyright 2014-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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.session.jdbc.config.annotation.web.http; package org.springframework.session.jdbc.config.annotation.web.http;
import java.util.Map; import java.util.Map;
@@ -20,6 +21,7 @@ import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.context.EmbeddedValueResolverAware;
@@ -33,14 +35,11 @@ import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.serializer.support.DeserializingConverter; import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter; import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.lob.LobHandler; import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository; import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver; import org.springframework.util.StringValueResolver;
@@ -78,17 +77,17 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
private StringValueResolver embeddedValueResolver; private StringValueResolver embeddedValueResolver;
@Bean
public JdbcTemplate springSessionJdbcOperations(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean @Bean
public JdbcOperationsSessionRepository sessionRepository( public JdbcOperationsSessionRepository sessionRepository(
@Qualifier("springSessionJdbcOperations") JdbcOperations jdbcOperations, @SpringSessionDataSource ObjectProvider<DataSource> springSessionDataSource,
ObjectProvider<DataSource> dataSource,
PlatformTransactionManager transactionManager) { PlatformTransactionManager transactionManager) {
JdbcOperationsSessionRepository sessionRepository = DataSource dataSourceToUse = springSessionDataSource.getIfAvailable();
new JdbcOperationsSessionRepository(jdbcOperations, transactionManager); if (dataSourceToUse == null) {
dataSourceToUse = dataSource.getObject();
}
JdbcOperationsSessionRepository sessionRepository = new JdbcOperationsSessionRepository(
dataSourceToUse, transactionManager);
String tableName = getTableName(); String tableName = getTableName();
if (StringUtils.hasText(tableName)) { if (StringUtils.hasText(tableName)) {
sessionRepository.setTableName(tableName); sessionRepository.setTableName(tableName);
@@ -104,7 +103,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
else if (this.conversionService != null) { else if (this.conversionService != null) {
sessionRepository.setConversionService(this.conversionService); sessionRepository.setConversionService(this.conversionService);
} }
else if (deserializingConverterSupportsCustomClassLoader()) { else {
GenericConversionService conversionService = createConversionServiceWithBeanClassLoader(); GenericConversionService conversionService = createConversionServiceWithBeanClassLoader();
sessionRepository.setConversionService(conversionService); sessionRepository.setConversionService(conversionService);
} }
@@ -115,7 +114,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
* This must be a separate method because some ClassLoaders load the entire method * This must be a separate method because some ClassLoaders load the entire method
* definition even if an if statement guards against it loading. This means that older * definition even if an if statement guards against it loading. This means that older
* versions of Spring would cause a NoSuchMethodError if this were defined in * versions of Spring would cause a NoSuchMethodError if this were defined in
* {@link #sessionRepository(JdbcOperations, PlatformTransactionManager)}. * {@link #sessionRepository(ObjectProvider, ObjectProvider, PlatformTransactionManager)}.
* *
* @return the default {@link ConversionService} * @return the default {@link ConversionService}
*/ */
@@ -163,10 +162,6 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
return this.tableName; return this.tableName;
} }
private boolean deserializingConverterSupportsCustomClassLoader() {
return ClassUtils.hasConstructor(DeserializingConverter.class, ClassLoader.class);
}
public void setImportMetadata(AnnotationMetadata importMetadata) { public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> enableAttrMap = importMetadata Map<String, Object> enableAttrMap = importMetadata
.getAnnotationAttributes(EnableJdbcHttpSession.class.getName()); .getAnnotationAttributes(EnableJdbcHttpSession.class.getName());
@@ -192,4 +187,5 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer(); return new PropertySourcesPlaceholderConfigurer();
} }
} }

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2014-2017 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
*
* http://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.session.jdbc.config.annotation.web.http;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
/**
* Qualifier annotation for a {@link DataSource} to be injected in
* {@link JdbcOperationsSessionRepository}.
*
* @author Vedran Pavic
* @since 2.0.0
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
public @interface SpringSessionDataSource {
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2014-2016 the original author or authors. * Copyright 2014-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.
@@ -23,12 +23,14 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.support.lob.LobHandler; import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockEnvironment;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository; import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
@@ -67,10 +69,10 @@ public class JdbcHttpSessionConfigurationTests {
@Test @Test
public void noDataSourceConfiguration() { public void noDataSourceConfiguration() {
this.thrown.expect(UnsatisfiedDependencyException.class); this.thrown.expect(BeanCreationException.class);
this.thrown.expectMessage("springSessionJdbcOperations"); this.thrown.expectMessage("sessionRepository");
registerAndRefresh(EmptyConfiguration.class); registerAndRefresh(NoDataSourceConfiguration.class);
} }
@Test @Test
@@ -145,6 +147,78 @@ public class JdbcHttpSessionConfigurationTests {
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); .isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
} }
@Test
public void qualifiedDataSourceConfiguration() {
registerAndRefresh(QualifiedDataSourceConfiguration.class);
JdbcOperationsSessionRepository repository = this.context
.getBean(JdbcOperationsSessionRepository.class);
DataSource dataSource = this.context.getBean("qualifiedDataSource", DataSource.class);
assertThat(repository).isNotNull();
assertThat(dataSource).isNotNull();
JdbcOperations jdbcOperations = (JdbcOperations) ReflectionTestUtils
.getField(repository, "jdbcOperations");
assertThat(jdbcOperations).isNotNull();
assertThat(ReflectionTestUtils.getField(jdbcOperations, "dataSource"))
.isEqualTo(dataSource);
}
@Test
public void primaryDataSourceConfiguration() {
registerAndRefresh(PrimaryDataSourceConfiguration.class);
JdbcOperationsSessionRepository repository = this.context
.getBean(JdbcOperationsSessionRepository.class);
DataSource dataSource = this.context.getBean("primaryDataSource", DataSource.class);
assertThat(repository).isNotNull();
assertThat(dataSource).isNotNull();
JdbcOperations jdbcOperations = (JdbcOperations) ReflectionTestUtils
.getField(repository, "jdbcOperations");
assertThat(jdbcOperations).isNotNull();
assertThat(ReflectionTestUtils.getField(jdbcOperations, "dataSource"))
.isEqualTo(dataSource);
}
@Test
public void qualifiedAndPrimaryDataSourceConfiguration() {
registerAndRefresh(QualifiedAndPrimaryDataSourceConfiguration.class);
JdbcOperationsSessionRepository repository = this.context
.getBean(JdbcOperationsSessionRepository.class);
DataSource dataSource = this.context.getBean("qualifiedDataSource", DataSource.class);
assertThat(repository).isNotNull();
assertThat(dataSource).isNotNull();
JdbcOperations jdbcOperations = (JdbcOperations) ReflectionTestUtils
.getField(repository, "jdbcOperations");
assertThat(jdbcOperations).isNotNull();
assertThat(ReflectionTestUtils.getField(jdbcOperations, "dataSource"))
.isEqualTo(dataSource);
}
@Test
public void namedDataSourceConfiguration() {
registerAndRefresh(NamedDataSourceConfiguration.class);
JdbcOperationsSessionRepository repository = this.context
.getBean(JdbcOperationsSessionRepository.class);
DataSource dataSource = this.context.getBean("dataSource", DataSource.class);
assertThat(repository).isNotNull();
assertThat(dataSource).isNotNull();
JdbcOperations jdbcOperations = (JdbcOperations) ReflectionTestUtils
.getField(repository, "jdbcOperations");
assertThat(jdbcOperations).isNotNull();
assertThat(ReflectionTestUtils.getField(jdbcOperations, "dataSource"))
.isEqualTo(dataSource);
}
@Test
public void multipleDataSourceConfiguration() {
this.thrown.expect(BeanCreationException.class);
this.thrown.expectMessage("sessionRepository");
registerAndRefresh(MultipleDataSourceConfiguration.class);
}
@Test @Test
public void customLobHandlerConfiguration() { public void customLobHandlerConfiguration() {
registerAndRefresh(CustomLobHandlerConfiguration.class); registerAndRefresh(CustomLobHandlerConfiguration.class);
@@ -188,13 +262,13 @@ public class JdbcHttpSessionConfigurationTests {
@Configuration @Configuration
@EnableJdbcHttpSession @EnableJdbcHttpSession
static class EmptyConfiguration { static class NoDataSourceConfiguration {
} }
static class BaseConfiguration { static class BaseConfiguration {
@Bean @Bean
public DataSource dataSource() { public DataSource defaultDataSource() {
return mock(DataSource.class); return mock(DataSource.class);
} }
@@ -239,6 +313,70 @@ public class JdbcHttpSessionConfigurationTests {
extends BaseConfiguration { extends BaseConfiguration {
} }
@Configuration
@EnableJdbcHttpSession
static class QualifiedDataSourceConfiguration extends BaseConfiguration {
@Bean
@SpringSessionDataSource
public DataSource qualifiedDataSource() {
return mock(DataSource.class);
}
}
@Configuration
@EnableJdbcHttpSession
static class PrimaryDataSourceConfiguration extends BaseConfiguration {
@Bean
@Primary
public DataSource primaryDataSource() {
return mock(DataSource.class);
}
}
@Configuration
@EnableJdbcHttpSession
static class QualifiedAndPrimaryDataSourceConfiguration extends BaseConfiguration {
@Bean
@SpringSessionDataSource
public DataSource qualifiedDataSource() {
return mock(DataSource.class);
}
@Bean
@Primary
public DataSource primaryDataSource() {
return mock(DataSource.class);
}
}
@Configuration
@EnableJdbcHttpSession
static class NamedDataSourceConfiguration extends BaseConfiguration {
@Bean
public DataSource dataSource() {
return mock(DataSource.class);
}
}
@Configuration
@EnableJdbcHttpSession
static class MultipleDataSourceConfiguration extends BaseConfiguration {
@Bean
public DataSource secondaryDataSource() {
return mock(DataSource.class);
}
}
@Configuration @Configuration
@EnableJdbcHttpSession @EnableJdbcHttpSession
static class CustomLobHandlerConfiguration extends BaseConfiguration { static class CustomLobHandlerConfiguration extends BaseConfiguration {