Skip to content

Proper load-time weaving support for Hibernate 5 [SPR-13886] #18459

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
spring-projects-issues opened this issue Jan 25, 2016 · 9 comments
Closed
Assignees
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Jan 25, 2016

Andrei Ivanov opened SPR-13886 and commented

Trying to upgrade Hibernate from 4.3.x to 5.0 throws this nice exception:

25-Jan-2016 13:34:10.985 SEVERE [localhost-startStop-2] org.apache.catalina.core.StandardContext.listenerStop Exception sending context destroyed event to listener instance of class org.springframework.web.context.ContextLoaderListener
 java.lang.ClassCircularityError: org/apache/log4j/spi/ThrowableInformation
	at org.apache.log4j.spi.LoggingEvent.<init>(LoggingEvent.java:165)
	at org.apache.log4j.Category.forcedLog(Category.java:391)
	at org.apache.log4j.Category.log(Category.java:856)
	at org.slf4j.impl.Log4jLoggerAdapter.log(Log4jLoggerAdapter.java:595)
	at org.apache.commons.logging.impl.SLF4JLocationAwareLog.error(SLF4JLocationAwareLog.java:216)
	at org.springframework.orm.jpa.persistenceunit.ClassFileTransformerAdapter.transform(ClassFileTransformerAdapter.java:66)
	at org.springframework.instrument.classloading.WeavingTransformer.transformIfNecessary(WeavingTransformer.java:95)
	at org.springframework.instrument.classloading.WeavingTransformer.transformIfNecessary(WeavingTransformer.java:78)
	at org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader.findResourceInternal(TomcatInstrumentableClassLoader.java:127)
	at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:2427)
	at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:860)
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1302)
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1167)
	at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:963)
	at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:934)
	at org.springframework.web.context.ContextLoader.closeWebApplicationContext(ContextLoader.java:583)
	at org.springframework.web.context.ContextLoaderListener.contextDestroyed(ContextLoaderListener.java:116)
	at org.apache.catalina.core.StandardContext.listenerStop(StandardContext.java:4859)
	at org.apache.catalina.core.StandardContext.stopInternal(StandardContext.java:5478)
	at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:232)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:160)
	at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:725)
	at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:701)
	at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717)
	at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:945)
	at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1795)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

It hides the actual ClassCircularityError, thrown here:

                                                                                                            EnhancingClassTransformerImpl.transform(ClassLoader, String, Class<?>, ProtectionDomain, byte[]) line: 44	
                                                                                                            ClassFileTransformerAdapter.transform(ClassLoader, String, Class<?>, ProtectionDomain, byte[]) line: 56	
                                                                                            WeavingTransformer.transformIfNecessary(String, String, byte[], ProtectionDomain) line: 95	
                                                                    WeavingTransformer.transformIfNecessary(String, byte[]) line: 78	
                                                                                            TomcatInstrumentableClassLoader.findResourceInternal(String, String, boolean) line: 127	
                                                                                                TomcatInstrumentableClassLoader(WebappClassLoaderBase).findClassInternal(String) line: 2427	
                                                                                    TomcatInstrumentableClassLoader(WebappClassLoaderBase).findClass(String) line: 860	
                                                                                                TomcatInstrumentableClassLoader(WebappClassLoaderBase).loadClass(String, boolean) line: 1302	
                                                                                        TomcatInstrumentableClassLoader(WebappClassLoaderBase).loadClass(String) line: 1167	
                                                                                                            EnhancingClassTransformerImpl.transform(ClassLoader, String, Class<?>, ProtectionDomain, byte[]) line: 44	
                                                                                                            ClassFileTransformerAdapter.transform(ClassLoader, String, Class<?>, ProtectionDomain, byte[]) line: 56	
                                                                                            WeavingTransformer.transformIfNecessary(String, String, byte[], ProtectionDomain) line: 95	
                                                                    WeavingTransformer.transformIfNecessary(String, byte[]) line: 78	
                                                                                            TomcatInstrumentableClassLoader.findResourceInternal(String, String, boolean) line: 127	
                                                                                                TomcatInstrumentableClassLoader(WebappClassLoaderBase).findClassInternal(String) line: 2427	
                                                                                    TomcatInstrumentableClassLoader(WebappClassLoaderBase).findClass(String) line: 860	
                                                                                                TomcatInstrumentableClassLoader(WebappClassLoaderBase).loadClass(String, boolean) line: 1302	
                                                                                        TomcatInstrumentableClassLoader(WebappClassLoaderBase).loadClass(String) line: 1167	
                                                                        MetadataBuildingProcess.handleTypes(MetadataBuildingOptions) line: 327	
                                                                                        MetadataBuildingProcess.complete(ManagedResources, MetadataBuildingOptions) line: 111	
                                                        EntityManagerFactoryBuilderImpl.metadata() line: 847	
                                                    EntityManagerFactoryBuilderImpl.build() line: 874	
                                                                                                                SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(PersistenceUnitInfo, Map) line: 60	
                                                                                        LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory() line: 343	
                                                                                                            LocalContainerEntityManagerFactoryBean(AbstractEntityManagerFactoryBean).afterPropertiesSet() line: 318	
                                                                                                                                    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).invokeInitMethods(String, Object, RootBeanDefinition) line: 1637	
                                                                                                                                DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).initializeBean(String, Object, RootBeanDefinition) line: 1574	
                                                                                                                                DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 545	
                                                                                                                            DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 482	
                                                AbstractBeanFactory$1.getObject() line: 306	
                                                                                                            DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory<?>) line: 230	
                                                                                                            DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class<T>, Object[], boolean) line: 302	
                                                                            DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 197	
                                                                                    XmlWebApplicationContext(AbstractApplicationContext).getBean(String) line: 1054	
                                                                                                                                    XmlWebApplicationContext(AbstractApplicationContext).finishBeanFactoryInitialization(ConfigurableListableBeanFactory) line: 829	
                                                                            XmlWebApplicationContext(AbstractApplicationContext).refresh() line: 538	
                                                                                                                                            ContextLoaderListener(ContextLoader).configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext, ServletContext) line: 446	
                                                                                            ContextLoaderListener(ContextLoader).initWebApplicationContext(ServletContext) line: 328	
                                                                            ContextLoaderListener.contextInitialized(ServletContextEvent) line: 107	
                                            StandardContext.listenerStart() line: 4812	
                                            StandardContext.startInternal() line: 5255	
                                                    StandardContext(LifecycleBase).start() line: 150	
                                                                    StandardHost(ContainerBase).addChildInternal(Container) line: 725	
                                                            StandardHost(ContainerBase).addChild(Container) line: 701	
                                            StandardHost.addChild(Container) line: 717	
                                                    HostConfig.deployWAR(ContextName, File) line: 945	
                                        HostConfig$DeployWar.run() line: 1795	
                                                Executors$RunnableAdapter<T>.call() line: 511	
                                FutureTask<V>.run() line: 266	
                                                                    ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1142	
                                            ThreadPoolExecutor$Worker.run() line: 617	
Thread.run() line: 745

MetadataBuildingProcess.handleTypes(MetadataBuildingOptions) line: 327 -> triggers loading of org.hibernate.type.BasicTypeRegistry
1st. call to EnhancingClassTransformerImpl.transform(ClassLoader, String, Class<?>, ProtectionDomain, byte[]) line: 44 triggers the loading of org.hibernate.bytecode.enhance.spi.Enhancer
2nd. call triggers the ClassCircularityError

Investigating the issue, I see that org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl calls persistenceUnit.getTempClassLoader(),
which ends up in org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver#getThrowawayClassLoader, invoking
org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader#getThrowawayClassLoader.
In theory, this should handle this scenario.
There are 2 problems here:

  1. It returns org.apache.catalina.loader.WebappClassLoader, which is not a DecoratingClassLoader, so the exclude package (org.hibernate) is not set:
    SpringPersistenceUnitInfo#getNewTempClassLoader
public ClassLoader getNewTempClassLoader() {
     ClassLoader tcl = (this.loadTimeWeaver != null ? this.loadTimeWeaver.getThrowawayClassLoader() :
               new SimpleThrowawayClassLoader(this.classLoader));
     String packageToExclude = getPersistenceProviderPackageName();
     if (packageToExclude != null && tcl instanceof DecoratingClassLoader) {
          ((DecoratingClassLoader) tcl).excludePackage(packageToExclude);
     }
     return tcl;
}

Even if it did, just org.hibernate wouldn't be enough here.

  1. This temp classloader is no longer used when the error occurs
    EntityManagerFactoryBuilderImpl, on line 227, pushes the class transformer but 3 lines after (230) it sets the temp classloader to null.
    After this, EntityManagerFactoryBuilderImpl#build is called, and that triggers the error.

Now for the workaround.
Inspired by the AspectJClassBypassingClassFileTransformer, I've created my own:

public class HibernateClassBypassingLoadTimeWeaver implements LoadTimeWeaver {

	private final LoadTimeWeaver loadTimeWeaver;

	public HibernateClassBypassingLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
		Assert.notNull(loadTimeWeaver);
		this.loadTimeWeaver = loadTimeWeaver;
	}

	@Override
	public ClassLoader getInstrumentableClassLoader() {
		return loadTimeWeaver.getInstrumentableClassLoader();
	}

	@Override
	public ClassLoader getThrowawayClassLoader() {
		return loadTimeWeaver.getThrowawayClassLoader();
	}

	@Override
	public void addTransformer(ClassFileTransformer transformer) {
		loadTimeWeaver.addTransformer(new HibernateClassBypassingClassFileTransformer(transformer));
	}

	/**
	 * ClassFileTransformer decorator that suppresses processing of Hibernate
	 * classes in order to avoid potential LinkageErrors.
	 * @see org.springframework.context.annotation.LoadTimeWeavingConfiguration
	 */
	private static class HibernateClassBypassingClassFileTransformer implements ClassFileTransformer {
		private static final Logger LOGGER = LoggerFactory.getLogger(HibernateClassBypassingClassFileTransformer.class);

		private final ClassFileTransformer delegate;

		public HibernateClassBypassingClassFileTransformer(ClassFileTransformer delegate) {
			this.delegate = delegate;
		}

		@Override
		public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
				throws IllegalClassFormatException {

			if (className.startsWith("org.hibernate") || className.startsWith("org/hibernate") || className.startsWith("javassist")) {
				LOGGER.trace("Bypassing class: {}", className);
				return classfileBuffer;
			}
			return this.delegate.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
		}
	}

As you can see in the code, ignoring just the Hibernate classes isn't enough, the Javassist classes must also be ignored.

The problem with the workaround so far is that it cannot injected:

	<context:load-time-weaver />

	<bean id="testEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="testDS" />
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
		</property>
		<!-- this is normally initialized from the jpaVendorAdapter in afterPropertiesSet(), but it is needed earlier 
			by the post-processor -->
		<property name="jpaDialect">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
		</property>
		<property name="persistenceUnitName" value="testPU" />
		<property name="jpaPropertyMap">
			<map>
				<entry key="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
				<entry key="hibernate.enable_lazy_load_no_trans" value="true" />
				<!-- this deprecated setting enables all 3 new setting following -->
				<entry key="hibernate.ejb.use_class_enhancer" value="true" />
				<!--
				<entry key="hibernate.enhancer.enableDirtyTracking" value="true" />
				<entry key="hibernate.enhancer.enableLazyInitialization" value="true" />
				<entry key="hibernate.enhancer.enableAssociationManagement" value="true" />
				-->
				<entry key="hibernate.id.new_generator_mappings" value="false" />
			</map>
		</property>
		<property name="packagesToScan">
			<list>
				<value>org.springframework.issues.entity</value>
			</list>
		</property>
		<property name="loadTimeWeaver">
			<bean class="org.springframework.issues.hibernate.HibernateClassBypassingLoadTimeWeaver">
				<constructor-arg ref="loadTimeWeaver" />
			</bean>
		</property>
	</bean>

My LTW initially gets injected, but then the LoadTimeWeaverAwareProcessor comes along and injects back the original LTW (DefaultContextLoadTimeWeaver).
I even tried with a DefaultPersistenceUnitManager, but that also implements LoadTimeWeaverAware.

So, to get my LTW injected, I've created my own processor:

public class HibernateLoadTimeWeaverAwareProcessor implements BeanPostProcessor {
	private static final Logger LOGGER = LoggerFactory.getLogger(HibernateLoadTimeWeaverAwareProcessor.class);

	private final LoadTimeWeaver loadTimeWeaver;

	public HibernateLoadTimeWeaverAwareProcessor(LoadTimeWeaver loadTimeWeaver) {
		this.loadTimeWeaver = new HibernateClassBypassingLoadTimeWeaver(loadTimeWeaver);
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (bean instanceof LoadTimeWeaverAware && bean instanceof EntityManagerFactoryInfo) {
			JpaDialect jpaDialect = ((EntityManagerFactoryInfo) bean).getJpaDialect();
			boolean isHibernate = jpaDialect instanceof HibernateJpaDialect;
			LOGGER.info("EMF {} has Hibernate dialect: {}", bean, isHibernate);
			if (isHibernate) {
				LOGGER.info("Injecting custom LTW: {}", loadTimeWeaver);
				((LoadTimeWeaverAware) bean).setLoadTimeWeaver(loadTimeWeaver);
			}
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}

This finally makes it run.

So, did I miss something in the configuration?
Is this supposed to run using standard Spring configuration?
Or maybe Hibernate + bytecode enhancement wasn't tested thoroughly?

The app is running inside a Tomcat 8.0.30 with LTW active.


Affects: 4.2.4

Issue Links:

Referenced from: pull request spring-attic/spring-framework-issues#117, and commits bdb9473, 836a321, b82df14

0 votes, 5 watchers

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I'm afraid our Hibernate 5.0 support in Spring 4.2 is somewhat basic still, with load-time weaving not having been fully tested and not really supported yet in the first place. Native Tomcat weaving support is also pretty recent. We'll make sure to close any such gaps for 4.3.

Juergen

@spring-projects-issues
Copy link
Collaborator Author

Andrei Ivanov commented

Hmm, should I open a separate issue for the inability to inject a specific LTW into a component that is LoadTimeWeaverAware?

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

It looks like we'll have to expose a DecoratingClassLoader for Tomcat 8 (and a few others) as well, wrapping the underlying native ClassLoader copy before returning it from TomcatLoadTimeWeaver.getTempClassLoader(). This requires a bit of redesign in DecoratingClassLoader and/or OverridingClassLoader, allowing it to decorate a target ClassLoader pair. In any case, it is ultimately the safest option, as far as I can see... not requiring every ClassFileTransformer to manually exclude.

Excluding the javassist package by default makes sense in any case, since arguably you'll never want to transform the bytecode of a bytecode transformation library itself...

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I've committed a revision where we expose an OverridingClassLoader from LoadTimeWeaver.getThrowawayClassLoader() implementations if not otherwise decoratable already. This will show up in the upcoming 4.3.0.BUILD-SNAPSHOT; feel free to give it a try...

@spring-projects-issues
Copy link
Collaborator Author

Andrei Ivanov commented

Made a quick test, doesn't work :(

12-Apr-2016 08:38:30.144 INFO [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.addTransformer Added class file transformer [org.sprin
gframework.context.weaving.AspectJWeavingEnabler$AspectJClassBypassingClassFileTransformer] to web application [enss].
12-Apr-2016 08:38:34.264 INFO [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.addTransformer Added class file transformer [Standard
ClassFileTransformer wrapping JPA transformer: org.hibernate.jpa.internal.enhance.EnhancingClassTransformerImpl] to web application [enss].
2016-04-12 08:38:34,269 localhost-startStop-2 ERROR Recursive call to appender file
2016-04-12 08:38:34,270 localhost-startStop-2 ERROR An exception occurred processing Appender file java.lang.IllegalStateException: Could not weave class [or
g/apache/logging/log4j/core/impl/ThrowableProxy]
        at org.springframework.orm.jpa.persistenceunit.ClassFileTransformerAdapter.transform(ClassFileTransformerAdapter.java:68)
        at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:2447)
        at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:831)
        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1274)
        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1139)
        at org.apache.logging.log4j.core.impl.Log4jLogEvent.getThrownProxy(Log4jLogEvent.java:482)
        at org.apache.logging.log4j.core.pattern.ExtendedThrowablePatternConverter.format(ExtendedThrowablePatternConverter.java:64)
        at org.apache.logging.log4j.core.pattern.PatternFormatter.format(PatternFormatter.java:36)
        at org.apache.logging.log4j.core.layout.PatternLayout$PatternSerializer.toSerializable(PatternLayout.java:292)
        at org.apache.logging.log4j.core.layout.PatternLayout.toSerializable(PatternLayout.java:206)
        at org.apache.logging.log4j.core.layout.PatternLayout.toSerializable(PatternLayout.java:56)
        at org.apache.logging.log4j.core.layout.AbstractStringLayout.toByteArray(AbstractStringLayout.java:148)
        at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:112)
        at org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender.append(RollingRandomAccessFileAppender.java:98)
        at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:152)
        at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:125)
        at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:116)
        at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:84)
        at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:390)
        at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:378)
        at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:362)
        at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:352)
        at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:63)
        at org.apache.logging.log4j.core.Logger.logMessage(Logger.java:147)
        at org.apache.logging.slf4j.Log4jLogger.log(Log4jLogger.java:375)
        at org.apache.commons.logging.impl.SLF4JLocationAwareLog.error(SLF4JLocationAwareLog.java:216)
        at org.springframework.orm.jpa.persistenceunit.ClassFileTransformerAdapter.transform(ClassFileTransformerAdapter.java:66)
        at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:2447)
        at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:831)
        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1274)
        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1139)
        at org.hibernate.jpa.internal.enhance.EnhancingClassTransformerImpl.transform(EnhancingClassTransformerImpl.java:44)
        at org.springframework.orm.jpa.persistenceunit.ClassFileTransformerAdapter.transform(ClassFileTransformerAdapter.java:56)
        at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:2447)
        at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:831)
        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1274)
        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1139)
        at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.handleTypes(MetadataBuildingProcess.java:327)
        at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:111)
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:848)
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:876)
        at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider
.java:60)
        at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:33
8)
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:373)
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:362)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
        at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1076)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:851)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541)
        at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:444)
        at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:326)
        at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:107)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4811)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5251)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:725)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:701)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717)
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:940)
        at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1816)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ClassCircularityError: org/hibernate/bytecode/enhance/spi/Enhancer
        at org.hibernate.jpa.internal.enhance.EnhancingClassTransformerImpl.transform(EnhancingClassTransformerImpl.java:44)
        at org.springframework.orm.jpa.persistenceunit.ClassFileTransformerAdapter.transform(ClassFileTransformerAdapter.java:56)
        ... 71 more

Btw, there's a PR attached to the ticket that should help reproduce the problem.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Indeed, it would probably work if the throwaway class loader was actually used for those purposes! It seems that Hibernate only uses it for identifying candidate classes but then goes on registering its class transformer in a very broad way... without filtering for entity classes first, like it did up until 4.3 and like OpenJPA and EclipseLink still do. The transformer just unconditionally goes right into its enhancer code path: Aside from choking on some classes, it also sets up a lot of stuff for every single transform attempt on the application class loader, even for completely unrelated classes loaded after the persistence bootstrap phase...

Frankly, this looks like misbehavior on Hibernate's side: The first thing a transformer should do is to check whether it actually applies! With JPA persistence metadata, this is quite straightforward... if the persistence provider itself does it. From our perspective, we don't have hard information on this: In particular, we don't know the provider's scanning results. Filtering based on exclusions, like in your workaround above, is not efficient there on the application class loader itself at all. We really need the transformer to do it based on its own metadata, immediately backing out if it encounters a non-entity class that it doesn't apply to.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

It seems that the root of this particular problem is not in getThrowawayClassLoader()'s ability to exclude certain packages... but rather in Hibernate's unconditional class transformer. In that respect, the problem doesn't seem to be specific to TomcatInstrumentableClassLoader or Tomcat 8 in particular, even if it nevertheless makes sense to be defensive there.

As a consequence, I'm afraid this will have to be reported to the Hibernate project instead: EnhancingClassTransformerImpl should really only proceed with enhancement if it encounters an entity class from its current persistence unit...

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

There is a trick applied by OpenJPA's class transformer that we could possibly apply at the level of our ClassFileTransformerAdapter: preventing re-entrant calls through checking a currentlyTransforming flag while we're attempting to transform... So if another class load attempt is being triggered during the execution of the target transformer, we're backing out immediately now. This should hopefully cover your initial case above, so I'll push this for testing against the next 4.3.0.BUILD-SNAPSHOT.

At the same time, it still makes a lot of sense for Hibernate to narrow the applicability of its transformer implementation.

@spring-projects-issues
Copy link
Collaborator Author

Andrei Ivanov commented

I would create a ticket for Hibernate, but I would also have to create a test to demonstrate the issue, and that will probably be much more complicated than for this one :(

Seems to work now without my workaround :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants