DATAJPA-1250 - Inspect repository interfaces in isolated classloader.
We now load repository interfaces for configuration inspection (i.e. for strict mode verification) within an isolated, throw-away classloader if Spring Instrumentation or EclipseLink are on the class path. Loading interfaces and hence domain classes in an isolated classloader does not prevent load-time weaving during EntityManagerFactoryBean initialization since the domain class wasn't loaded by the application classloader.
This commit is contained in:
committed by
Oliver Gierke
parent
87e9bbc230
commit
c1f61b1118
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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.data.jpa.repository.config;
|
||||||
|
|
||||||
|
import org.springframework.instrument.classloading.ShadowingClassLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposable {@link ClassLoader} used to inspect user-code classes within an isolated class loader without preventing
|
||||||
|
* class transformation at a later time.
|
||||||
|
*
|
||||||
|
* @author Mark Paluch
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
class InspectionClassLoader extends ShadowingClassLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link InspectionClassLoader} instance.
|
||||||
|
*
|
||||||
|
* @param parent the parent classloader.
|
||||||
|
*/
|
||||||
|
InspectionClassLoader(ClassLoader parent) {
|
||||||
|
|
||||||
|
super(parent, true);
|
||||||
|
|
||||||
|
excludePackage("org.springframework.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,12 +17,16 @@ package org.springframework.data.jpa.repository.config;
|
|||||||
|
|
||||||
import static org.springframework.data.jpa.repository.config.BeanDefinitionNames.*;
|
import static org.springframework.data.jpa.repository.config.BeanDefinitionNames.*;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.MappedSuperclass;
|
import javax.persistence.MappedSuperclass;
|
||||||
@@ -35,6 +39,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
|||||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||||
import org.springframework.core.annotation.AnnotationAttributes;
|
import org.springframework.core.annotation.AnnotationAttributes;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
|
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
@@ -47,6 +52,7 @@ import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
|||||||
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
|
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -183,6 +189,19 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
|
|||||||
registerIfNotAlreadyRegistered(contextDefinition, registry, JPA_CONTEXT_BEAN_NAME, source);
|
registerIfNotAlreadyRegistered(contextDefinition, registry, JPA_CONTEXT_BEAN_NAME, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getConfigurationInspectionClassLoader(org.springframework.core.io.ResourceLoader)
|
||||||
|
*/
|
||||||
|
protected ClassLoader getConfigurationInspectionClassLoader(ResourceLoader loader) {
|
||||||
|
|
||||||
|
ClassLoader classLoader = loader.getClassLoader();
|
||||||
|
|
||||||
|
return classLoader != null && LazyJvmAgent.isActive(loader.getClassLoader())
|
||||||
|
? new InspectionClassLoader(loader.getClassLoader())
|
||||||
|
: loader.getClassLoader();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an anonymous factory to extract the actual {@link javax.persistence.EntityManager} from the
|
* Creates an anonymous factory to extract the actual {@link javax.persistence.EntityManager} from the
|
||||||
* {@link javax.persistence.EntityManagerFactory} bean name reference.
|
* {@link javax.persistence.EntityManagerFactory} bean name reference.
|
||||||
@@ -210,4 +229,37 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
|
|||||||
Optional<String> entityManagerFactoryRef = config.getAttribute("entityManagerFactoryRef");
|
Optional<String> entityManagerFactoryRef = config.getAttribute("entityManagerFactoryRef");
|
||||||
return entityManagerFactoryRef.orElse("entityManagerFactory");
|
return entityManagerFactoryRef.orElse("entityManagerFactory");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to determine if a lazy Java agent is being used that might transform classes at a later time.
|
||||||
|
*
|
||||||
|
* @author Mark Paluch
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
static class LazyJvmAgent {
|
||||||
|
|
||||||
|
private static final Set<String> AGENT_CLASSES;
|
||||||
|
|
||||||
|
static {
|
||||||
|
|
||||||
|
Set<String> agentClasses = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
agentClasses.add("org.springframework.instrument.InstrumentationSavingAgent");
|
||||||
|
agentClasses.add("org.eclipse.persistence.internal.jpa.deployment.JavaSECMPInitializerAgent");
|
||||||
|
|
||||||
|
AGENT_CLASSES = Collections.unmodifiableSet(agentClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if any agent is active.
|
||||||
|
*
|
||||||
|
* @return {@literal true} if an agent is active.
|
||||||
|
*/
|
||||||
|
static boolean isActive(@Nullable ClassLoader classLoader) {
|
||||||
|
|
||||||
|
return AGENT_CLASSES.stream() //
|
||||||
|
.anyMatch(agentClass -> ClassUtils.isPresent(agentClass, classLoader));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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.data.jpa.repository.config;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link InspectionClassLoader}.
|
||||||
|
*
|
||||||
|
* @author Mark Paluch
|
||||||
|
*/
|
||||||
|
public class InspectionClassLoaderUnitTests {
|
||||||
|
|
||||||
|
@Test // DATAJPA-1250
|
||||||
|
public void shouldLoadExternalClass() throws ClassNotFoundException {
|
||||||
|
|
||||||
|
InspectionClassLoader classLoader = new InspectionClassLoader(getClass().getClassLoader());
|
||||||
|
|
||||||
|
Class<?> isolated = classLoader.loadClass("org.hsqldb.Database");
|
||||||
|
Class<?> included = getClass().getClassLoader().loadClass("org.hsqldb.Database");
|
||||||
|
|
||||||
|
assertThat(isolated.getClassLoader()).isSameAs(classLoader).isNotSameAs(getClass().getClassLoader());
|
||||||
|
assertThat(isolated).isNotEqualTo(included);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,14 +39,17 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
|||||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||||
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
||||||
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||||
|
import org.springframework.instrument.classloading.ShadowingClassLoader;
|
||||||
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link JpaRepositoryConfigExtension}.
|
* Unit tests for {@link JpaRepositoryConfigExtension}.
|
||||||
*
|
*
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
|
* @author Mark Paluch
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class JpaRepositoryConfigExtensionUnitTests {
|
public class JpaRepositoryConfigExtensionUnitTests {
|
||||||
@@ -110,6 +113,40 @@ public class JpaRepositoryConfigExtensionUnitTests {
|
|||||||
factoryBean.createInstance().afterPropertiesSet();
|
factoryBean.createInstance().afterPropertiesSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // DATAJPA-1250
|
||||||
|
public void shouldUseInspectionClassLoader() {
|
||||||
|
|
||||||
|
JpaRepositoryConfigExtension extension = new JpaRepositoryConfigExtension();
|
||||||
|
ClassLoader classLoader = extension.getConfigurationInspectionClassLoader(new GenericApplicationContext());
|
||||||
|
|
||||||
|
assertThat(classLoader, is(instanceOf(InspectionClassLoader.class)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // DATAJPA-1250
|
||||||
|
public void shouldNotUseInspectionClassLoaderWithoutEclipseLink() {
|
||||||
|
|
||||||
|
ShadowingClassLoader shadowingClassLoader = new ShadowingClassLoader(getClass().getClassLoader(), false) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||||
|
|
||||||
|
if (name.startsWith("org.springframework.instrument.") || name.startsWith("org.eclipse.")) {
|
||||||
|
throw new ClassNotFoundException("Excluded: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getClass().getClassLoader().loadClass(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
GenericApplicationContext context = new GenericApplicationContext();
|
||||||
|
context.setClassLoader(shadowingClassLoader);
|
||||||
|
|
||||||
|
JpaRepositoryConfigExtension extension = new JpaRepositoryConfigExtension();
|
||||||
|
ClassLoader classLoader = extension.getConfigurationInspectionClassLoader(context);
|
||||||
|
|
||||||
|
assertThat(classLoader, is(not(instanceOf(InspectionClassLoader.class))));
|
||||||
|
}
|
||||||
|
|
||||||
private void assertOnlyOnePersistenceAnnotationBeanPostProcessorRegistered(DefaultListableBeanFactory factory,
|
private void assertOnlyOnePersistenceAnnotationBeanPostProcessorRegistered(DefaultListableBeanFactory factory,
|
||||||
String expectedBeanName) {
|
String expectedBeanName) {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user