From 6a52f274f64ca2ad386c06cfa1cec47fd08cde0d Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 9 Dec 2021 11:13:50 +0100 Subject: [PATCH] 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 --- .../data/convert/DefaultTypeMapper.java | 16 ++++++++++++++- .../convert/SimpleTypeInformationMapper.java | 20 +++++++++++++++---- .../convert/DefaultTypeMapperUnitTests.java | 14 ++++++++++++- .../SimpleTypeInformationMapperUnitTests.java | 19 +++++++++++++++++- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java b/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java index 929b3346b..1ae04b156 100644 --- a/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java +++ b/src/main/java/org/springframework/data/convert/DefaultTypeMapper.java @@ -23,6 +23,7 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.data.mapping.Alias; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.context.MappingContext; @@ -39,7 +40,7 @@ import org.springframework.util.Assert; * @author Christoph Strobl * @author Mark Paluch */ -public class DefaultTypeMapper implements TypeMapper { +public class DefaultTypeMapper implements TypeMapper, BeanClassLoaderAware { private final TypeAliasAccessor accessor; private final List mappers; @@ -215,6 +216,19 @@ public class DefaultTypeMapper implements TypeMapper { } } + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader) + */ + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + for (TypeInformationMapper mapper : mappers) { + if (mapper instanceof BeanClassLoaderAware) { + ((BeanClassLoaderAware) mapper).setBeanClassLoader(classLoader); + } + } + } + /** * Returns the alias to be used for the given {@link TypeInformation}. * diff --git a/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java b/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java index aa9986d76..af996772c 100644 --- a/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java +++ b/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.data.mapping.Alias; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; @@ -33,10 +34,12 @@ import org.springframework.util.ClassUtils; * @author Oliver Gierke * @author Mark Paluch */ -public class SimpleTypeInformationMapper implements TypeInformationMapper { +public class SimpleTypeInformationMapper implements TypeInformationMapper, BeanClassLoaderAware { private final Map>> cache = new ConcurrentHashMap<>(); + private @Nullable ClassLoader classLoader; + /** * Returns the {@link TypeInformation} that shall be used when the given {@link String} value is found as type hint. * The implementation will simply interpret the given value as fully-qualified class name and try to load the class. @@ -53,7 +56,7 @@ public class SimpleTypeInformationMapper implements TypeInformationMapper { String stringAlias = alias.mapTyped(String.class); if (stringAlias != null) { - return cache.computeIfAbsent(stringAlias, SimpleTypeInformationMapper::loadClass).orElse(null); + return cache.computeIfAbsent(stringAlias, this::loadClass).orElse(null); } return null; @@ -70,10 +73,19 @@ public class SimpleTypeInformationMapper implements TypeInformationMapper { return Alias.of(type.getType().getName()); } - private static Optional> loadClass(String typeName) { + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader) + */ + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + private Optional> loadClass(String typeName) { try { - return Optional.of(ClassTypeInformation.from(ClassUtils.forName(typeName, null))); + return Optional.of(ClassTypeInformation.from(ClassUtils.forName(typeName, this.classLoader))); } catch (ClassNotFoundException e) { return Optional.empty(); } diff --git a/src/test/java/org/springframework/data/convert/DefaultTypeMapperUnitTests.java b/src/test/java/org/springframework/data/convert/DefaultTypeMapperUnitTests.java index 64ac3ddb2..9a9db0bc0 100755 --- a/src/test/java/org/springframework/data/convert/DefaultTypeMapperUnitTests.java +++ b/src/test/java/org/springframework/data/convert/DefaultTypeMapperUnitTests.java @@ -18,6 +18,8 @@ package org.springframework.data.convert; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.net.URL; +import java.net.URLClassLoader; import java.util.Collections; import java.util.Map; @@ -29,6 +31,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.data.mapping.Alias; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; @@ -47,7 +50,7 @@ class DefaultTypeMapperUnitTests { static final Alias ALIAS = Alias.of(String.class.getName()); @Mock TypeAliasAccessor> accessor; - @Mock TypeInformationMapper mapper; + @Mock(extraInterfaces = BeanClassLoaderAware.class) TypeInformationMapper mapper; DefaultTypeMapper> typeMapper; Map source; @@ -102,6 +105,15 @@ class DefaultTypeMapperUnitTests { assertThat(typeInformation.getProperty("field").getType()).isEqualTo(Character.class); } + @Test // GH-2508 + void configuresClassLoaderOnTypeInformationMapper() { + + ClassLoader loader = new URLClassLoader(new URL[0]); + typeMapper.setBeanClassLoader(loader); + + verify((BeanClassLoaderAware) mapper).setBeanClassLoader(loader); + } + static class TypeWithAbstractGenericType { AbstractBar abstractBar; } diff --git a/src/test/java/org/springframework/data/convert/SimpleTypeInformationMapperUnitTests.java b/src/test/java/org/springframework/data/convert/SimpleTypeInformationMapperUnitTests.java index a8ebe06e0..e26bec85c 100755 --- a/src/test/java/org/springframework/data/convert/SimpleTypeInformationMapperUnitTests.java +++ b/src/test/java/org/springframework/data/convert/SimpleTypeInformationMapperUnitTests.java @@ -18,6 +18,8 @@ package org.springframework.data.convert; import static org.assertj.core.api.Assertions.*; import org.junit.jupiter.api.Test; + +import org.springframework.data.classloadersupport.HidingClassLoader; import org.springframework.data.mapping.Alias; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; @@ -26,10 +28,11 @@ import org.springframework.data.util.TypeInformation; * Unit tests for {@link SimpleTypeInformationMapper}. * * @author Oliver Gierke + * @author Mark Paluch */ class SimpleTypeInformationMapperUnitTests { - TypeInformationMapper mapper = new SimpleTypeInformationMapper(); + SimpleTypeInformationMapper mapper = new SimpleTypeInformationMapper(); @Test void resolvesTypeByLoadingClass() { @@ -41,6 +44,16 @@ class SimpleTypeInformationMapperUnitTests { assertThat(type).isEqualTo(expected); } + @Test // GH-2508 + void usesConfiguredClassloader() { + + mapper.setBeanClassLoader(HidingClassLoader.hide(SimpleTypeInformationMapperUnitTests.class)); + TypeInformation type = mapper + .resolveTypeFrom(Alias.of("org.springframework.data.convert.SimpleTypeInformationMapperUnitTests.User")); + + assertThat(type).isNull(); + } + @Test void returnsNullForNonStringKey() { assertThat(mapper.resolveTypeFrom(Alias.of(new Object()))).isNull(); @@ -63,4 +76,8 @@ class SimpleTypeInformationMapperUnitTests { assertThat(mapper.createAliasFor(ClassTypeInformation.from(String.class))) .isEqualTo(Alias.of(String.class.getName())); } + + static class User { + + } }