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
6cbf0347e8
commit
fc82f60347
@@ -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 lombok.experimental.UtilityClass;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
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.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
|
||||
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.lang.Nullable;
|
||||
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -183,6 +189,19 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
|
||||
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
|
||||
* {@link javax.persistence.EntityManagerFactory} bean name reference.
|
||||
@@ -210,4 +229,37 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
|
||||
Optional<String> entityManagerFactoryRef = config.getAttribute("entityManagerFactoryRef");
|
||||
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.context.ApplicationContext;
|
||||
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.RepositoryConfigurationSource;
|
||||
import org.springframework.instrument.classloading.ShadowingClassLoader;
|
||||
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link JpaRepositoryConfigExtension}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class JpaRepositoryConfigExtensionUnitTests {
|
||||
@@ -110,6 +113,40 @@ public class JpaRepositoryConfigExtensionUnitTests {
|
||||
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,
|
||||
String expectedBeanName) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user