Skip to content

Commit 536dae1

Browse files
committed
Introduce configuration of class loader in SimpleTypeInformationMapper.
We now support configuration of the class loader in SimpleTypeInformationMapper to use a configured class loader instead of falling always back to the default/contextual class loader. In arrangements where the contextual class loader isn't able to provide access to the desired classes (e.g. parallel Streams, general Fork/Join Thread usage) the contextual class loader may not have access to the entity types. By implementing BeanClassLoaderAware, we can now propagate a configured class loader. Closes #2508
1 parent 088ccc4 commit 536dae1

File tree

4 files changed

+62
-7
lines changed

4 files changed

+62
-7
lines changed

src/main/java/org/springframework/data/convert/DefaultTypeMapper.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.concurrent.ConcurrentHashMap;
2424
import java.util.function.Function;
2525

26+
import org.springframework.beans.factory.BeanClassLoaderAware;
2627
import org.springframework.data.mapping.Alias;
2728
import org.springframework.data.mapping.PersistentEntity;
2829
import org.springframework.data.mapping.context.MappingContext;
@@ -39,7 +40,7 @@
3940
* @author Christoph Strobl
4041
* @author Mark Paluch
4142
*/
42-
public class DefaultTypeMapper<S> implements TypeMapper<S> {
43+
public class DefaultTypeMapper<S> implements TypeMapper<S>, BeanClassLoaderAware {
4344

4445
private final TypeAliasAccessor<S> accessor;
4546
private final List<? extends TypeInformationMapper> mappers;
@@ -215,6 +216,19 @@ public void writeType(TypeInformation<?> info, S sink) {
215216
}
216217
}
217218

219+
/*
220+
* (non-Javadoc)
221+
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
222+
*/
223+
@Override
224+
public void setBeanClassLoader(ClassLoader classLoader) {
225+
for (TypeInformationMapper mapper : mappers) {
226+
if (mapper instanceof BeanClassLoaderAware) {
227+
((BeanClassLoaderAware) mapper).setBeanClassLoader(classLoader);
228+
}
229+
}
230+
}
231+
218232
/**
219233
* Returns the alias to be used for the given {@link TypeInformation}.
220234
*

src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Optional;
2020
import java.util.concurrent.ConcurrentHashMap;
2121

22+
import org.springframework.beans.factory.BeanClassLoaderAware;
2223
import org.springframework.data.mapping.Alias;
2324
import org.springframework.data.util.ClassTypeInformation;
2425
import org.springframework.data.util.TypeInformation;
@@ -33,10 +34,12 @@
3334
* @author Oliver Gierke
3435
* @author Mark Paluch
3536
*/
36-
public class SimpleTypeInformationMapper implements TypeInformationMapper {
37+
public class SimpleTypeInformationMapper implements TypeInformationMapper, BeanClassLoaderAware {
3738

3839
private final Map<String, Optional<ClassTypeInformation<?>>> cache = new ConcurrentHashMap<>();
3940

41+
private @Nullable ClassLoader classLoader;
42+
4043
/**
4144
* Returns the {@link TypeInformation} that shall be used when the given {@link String} value is found as type hint.
4245
* The implementation will simply interpret the given value as fully-qualified class name and try to load the class.
@@ -53,7 +56,7 @@ public TypeInformation<?> resolveTypeFrom(Alias alias) {
5356
String stringAlias = alias.mapTyped(String.class);
5457

5558
if (stringAlias != null) {
56-
return cache.computeIfAbsent(stringAlias, SimpleTypeInformationMapper::loadClass).orElse(null);
59+
return cache.computeIfAbsent(stringAlias, this::loadClass).orElse(null);
5760
}
5861

5962
return null;
@@ -70,10 +73,19 @@ public Alias createAliasFor(TypeInformation<?> type) {
7073
return Alias.of(type.getType().getName());
7174
}
7275

73-
private static Optional<ClassTypeInformation<?>> loadClass(String typeName) {
76+
/*
77+
* (non-Javadoc)
78+
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
79+
*/
80+
@Override
81+
public void setBeanClassLoader(ClassLoader classLoader) {
82+
this.classLoader = classLoader;
83+
}
84+
85+
private Optional<ClassTypeInformation<?>> loadClass(String typeName) {
7486

7587
try {
76-
return Optional.of(ClassTypeInformation.from(ClassUtils.forName(typeName, null)));
88+
return Optional.of(ClassTypeInformation.from(ClassUtils.forName(typeName, this.classLoader)));
7789
} catch (ClassNotFoundException e) {
7890
return Optional.empty();
7991
}

src/test/java/org/springframework/data/convert/DefaultTypeMapperUnitTests.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.mockito.Mockito.*;
2020

21+
import java.net.URL;
22+
import java.net.URLClassLoader;
2123
import java.util.Collections;
2224
import java.util.Map;
2325

@@ -29,6 +31,7 @@
2931
import org.mockito.junit.jupiter.MockitoSettings;
3032
import org.mockito.quality.Strictness;
3133

34+
import org.springframework.beans.factory.BeanClassLoaderAware;
3235
import org.springframework.data.mapping.Alias;
3336
import org.springframework.data.util.ClassTypeInformation;
3437
import org.springframework.data.util.TypeInformation;
@@ -47,7 +50,7 @@ class DefaultTypeMapperUnitTests {
4750
static final Alias ALIAS = Alias.of(String.class.getName());
4851

4952
@Mock TypeAliasAccessor<Map<String, String>> accessor;
50-
@Mock TypeInformationMapper mapper;
53+
@Mock(extraInterfaces = BeanClassLoaderAware.class) TypeInformationMapper mapper;
5154

5255
DefaultTypeMapper<Map<String, String>> typeMapper;
5356
Map<String, String> source;
@@ -102,6 +105,15 @@ void specializesRawSourceTypeUsingGenericContext() {
102105
assertThat(typeInformation.getProperty("field").getType()).isEqualTo(Character.class);
103106
}
104107

108+
@Test // GH-2508
109+
void configuresClassLoaderOnTypeInformationMapper() {
110+
111+
ClassLoader loader = new URLClassLoader(new URL[0]);
112+
typeMapper.setBeanClassLoader(loader);
113+
114+
verify((BeanClassLoaderAware) mapper).setBeanClassLoader(loader);
115+
}
116+
105117
static class TypeWithAbstractGenericType<T> {
106118
AbstractBar<T> abstractBar;
107119
}

src/test/java/org/springframework/data/convert/SimpleTypeInformationMapperUnitTests.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import static org.assertj.core.api.Assertions.*;
1919

2020
import org.junit.jupiter.api.Test;
21+
22+
import org.springframework.data.classloadersupport.HidingClassLoader;
2123
import org.springframework.data.mapping.Alias;
2224
import org.springframework.data.util.ClassTypeInformation;
2325
import org.springframework.data.util.TypeInformation;
@@ -26,10 +28,11 @@
2628
* Unit tests for {@link SimpleTypeInformationMapper}.
2729
*
2830
* @author Oliver Gierke
31+
* @author Mark Paluch
2932
*/
3033
class SimpleTypeInformationMapperUnitTests {
3134

32-
TypeInformationMapper mapper = new SimpleTypeInformationMapper();
35+
SimpleTypeInformationMapper mapper = new SimpleTypeInformationMapper();
3336

3437
@Test
3538
void resolvesTypeByLoadingClass() {
@@ -41,6 +44,16 @@ void resolvesTypeByLoadingClass() {
4144
assertThat(type).isEqualTo(expected);
4245
}
4346

47+
@Test // GH-2508
48+
void usesConfiguredClassloader() {
49+
50+
mapper.setBeanClassLoader(HidingClassLoader.hide(SimpleTypeInformationMapperUnitTests.class));
51+
TypeInformation<?> type = mapper
52+
.resolveTypeFrom(Alias.of("org.springframework.data.convert.SimpleTypeInformationMapperUnitTests.User"));
53+
54+
assertThat(type).isNull();
55+
}
56+
4457
@Test
4558
void returnsNullForNonStringKey() {
4659
assertThat(mapper.resolveTypeFrom(Alias.of(new Object()))).isNull();
@@ -63,4 +76,8 @@ void usesFullyQualifiedClassNameAsTypeKey() {
6376
assertThat(mapper.createAliasFor(ClassTypeInformation.from(String.class)))
6477
.isEqualTo(Alias.of(String.class.getName()));
6578
}
79+
80+
static class User {
81+
82+
}
6683
}

0 commit comments

Comments
 (0)