New setting "hibernate.classloader.tccl_lookup" to allow the configuration of the thread context classloader lookup.

The bootstrap classloader context is not stored anymore in the ClassLoaderService because on Glassfish 4.1.1, the former
will be closed after bootstrap, causing huge warning and stacktraces occurs in the log each time a HQL query has to
be compiled.

See ticket HHH-11245 for details.
This commit is contained in:
Cédric Tabin 2016-11-19 20:18:54 +01:00 committed by Andrea Boriero
parent b0fad884f0
commit 726305f33e
6 changed files with 269 additions and 27 deletions

View File

@ -14,6 +14,7 @@ import java.util.Set;
import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService.TCCLLookupBehavior;
import org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl; import org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl;
import org.hibernate.boot.registry.selector.StrategyRegistration; import org.hibernate.boot.registry.selector.StrategyRegistration;
import org.hibernate.boot.registry.selector.StrategyRegistrationProvider; import org.hibernate.boot.registry.selector.StrategyRegistrationProvider;
@ -39,6 +40,7 @@ public class BootstrapServiceRegistryBuilder {
private List<ClassLoader> providedClassLoaders; private List<ClassLoader> providedClassLoaders;
private ClassLoaderService providedClassLoaderService; private ClassLoaderService providedClassLoaderService;
private StrategySelectorBuilder strategySelectorBuilder = new StrategySelectorBuilder(); private StrategySelectorBuilder strategySelectorBuilder = new StrategySelectorBuilder();
private TCCLLookupBehavior tcclLookupBehaviour = TCCLLookupBehavior.AFTER;
private boolean autoCloseRegistry = true; private boolean autoCloseRegistry = true;
@ -85,6 +87,15 @@ public class BootstrapServiceRegistryBuilder {
return this; return this;
} }
/**
* Defines when the lookup in the thread context {@code ClassLoader} is done.
*
* @param behavior The behavior.
*/
public void applyTCCLBehavior(TCCLLookupBehavior behavior) {
tcclLookupBehaviour = behavior;
}
/** /**
* @deprecated Use {@link #applyClassLoaderService} instead * @deprecated Use {@link #applyClassLoaderService} instead
*/ */
@ -205,6 +216,7 @@ public class BootstrapServiceRegistryBuilder {
} }
classLoaderService = new ClassLoaderServiceImpl( classLoaders ); classLoaderService = new ClassLoaderServiceImpl( classLoaders );
classLoaderService.setTCCLLookupBehavior( tcclLookupBehaviour );
} }
else { else {
classLoaderService = providedClassLoaderService; classLoaderService = providedClassLoaderService;
@ -215,7 +227,6 @@ public class BootstrapServiceRegistryBuilder {
classLoaderService classLoaderService
); );
return new BootstrapServiceRegistryImpl( return new BootstrapServiceRegistryImpl(
autoCloseRegistry, autoCloseRegistry,
classLoaderService, classLoaderService,

View File

@ -79,17 +79,6 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
// then the Hibernate class loader // then the Hibernate class loader
orderedClassLoaderSet.add( ClassLoaderServiceImpl.class.getClassLoader() ); orderedClassLoaderSet.add( ClassLoaderServiceImpl.class.getClassLoader() );
// then the TCCL, if one...
final ClassLoader tccl = locateTCCL();
if ( tccl != null ) {
orderedClassLoaderSet.add( tccl );
}
// finally the system classloader
final ClassLoader sysClassLoader = locateSystemClassLoader();
if ( sysClassLoader != null ) {
orderedClassLoaderSet.add( sysClassLoader );
}
// now build the aggregated class loader... // now build the aggregated class loader...
this.aggregatedClassLoader = new AggregatedClassLoader( orderedClassLoaderSet ); this.aggregatedClassLoader = new AggregatedClassLoader( orderedClassLoaderSet );
} }
@ -120,15 +109,6 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
addIfSet( providedClassLoaders, AvailableSettings.HIBERNATE_CLASSLOADER, configValues ); addIfSet( providedClassLoaders, AvailableSettings.HIBERNATE_CLASSLOADER, configValues );
addIfSet( providedClassLoaders, AvailableSettings.ENVIRONMENT_CLASSLOADER, configValues ); addIfSet( providedClassLoaders, AvailableSettings.ENVIRONMENT_CLASSLOADER, configValues );
if ( providedClassLoaders.isEmpty() ) {
log.debugf( "Incoming config yielded no classloaders; adding standard SE ones" );
final ClassLoader tccl = locateTCCL();
if ( tccl != null ) {
providedClassLoaders.add( tccl );
}
providedClassLoaders.add( ClassLoaderServiceImpl.class.getClassLoader() );
}
return new ClassLoaderServiceImpl( providedClassLoaders ); return new ClassLoaderServiceImpl( providedClassLoaders );
} }
@ -157,19 +137,96 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
} }
} }
@Override
public TCCLLookupBehavior getTTCLLookupBehavior() {
return getAggregatedClassLoader().tcclLookupBehavior;
}
@Override
public void setTCCLLookupBehavior(TCCLLookupBehavior behavior) {
getAggregatedClassLoader().tcclLookupBehavior = behavior;
}
private static class AggregatedClassLoader extends ClassLoader { private static class AggregatedClassLoader extends ClassLoader {
private final ClassLoader[] individualClassLoaders; private final ClassLoader[] individualClassLoaders;
private volatile TCCLLookupBehavior tcclLookupBehavior = TCCLLookupBehavior.AFTER;
private AggregatedClassLoader(final LinkedHashSet<ClassLoader> orderedClassLoaderSet) { private AggregatedClassLoader(final LinkedHashSet<ClassLoader> orderedClassLoaderSet) {
super( null ); super( null );
individualClassLoaders = orderedClassLoaderSet.toArray( new ClassLoader[orderedClassLoaderSet.size()] ); individualClassLoaders = orderedClassLoaderSet.toArray( new ClassLoader[orderedClassLoaderSet.size()] );
} }
private Iterator<ClassLoader> newClassLoaderIterator() {
final ClassLoader sysClassLoader = locateSystemClassLoader();
final ClassLoader threadClassLoader = locateTCCL();
final TCCLLookupBehavior behavior;
if ( threadClassLoader == null ) {
behavior = TCCLLookupBehavior.NEVER;
}
else {
behavior = tcclLookupBehavior;
}
return new Iterator<ClassLoader>() {
private boolean tcclReturned = false;
private boolean sysclReturned = false;
private int currentIndex = 0;
private ClassLoader nextClassLoader;
@Override
public boolean hasNext() {
if ( nextClassLoader != null ) {
return true;
}
if ( currentIndex == 0 && behavior == TCCLLookupBehavior.BEFORE && !tcclReturned ) {
tcclReturned = true;
nextClassLoader = threadClassLoader;
return true;
}
if ( currentIndex < individualClassLoaders.length ) {
nextClassLoader = individualClassLoaders[ currentIndex ];
++currentIndex;
return true;
}
if ( behavior == TCCLLookupBehavior.AFTER && !tcclReturned ) {
tcclReturned = true;
nextClassLoader = threadClassLoader;
return true;
}
if ( !sysclReturned ) {
sysclReturned = true;
nextClassLoader = sysClassLoader;
return true;
}
return false;
}
@Override
public ClassLoader next() {
if ( nextClassLoader == null ) {
throw new IllegalStateException( "No more ClassLoader to return" );
}
ClassLoader result = nextClassLoader;
nextClassLoader = null;
return result;
}
};
}
@Override @Override
public Enumeration<URL> getResources(String name) throws IOException { public Enumeration<URL> getResources(String name) throws IOException {
final LinkedHashSet<URL> resourceUrls = new LinkedHashSet<URL>(); final LinkedHashSet<URL> resourceUrls = new LinkedHashSet<URL>();
final Iterator<ClassLoader> clIterator = newClassLoaderIterator();
for ( ClassLoader classLoader : individualClassLoaders ) { while ( clIterator.hasNext() ) {
final ClassLoader classLoader = clIterator.next();
final Enumeration<URL> urls = classLoader.getResources( name ); final Enumeration<URL> urls = classLoader.getResources( name );
while ( urls.hasMoreElements() ) { while ( urls.hasMoreElements() ) {
resourceUrls.add( urls.nextElement() ); resourceUrls.add( urls.nextElement() );
@ -193,7 +250,9 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
@Override @Override
protected URL findResource(String name) { protected URL findResource(String name) {
for ( ClassLoader classLoader : individualClassLoaders ) { final Iterator<ClassLoader> clIterator = newClassLoaderIterator();
while ( clIterator.hasNext() ) {
final ClassLoader classLoader = clIterator.next();
final URL resource = classLoader.getResource( name ); final URL resource = classLoader.getResource( name );
if ( resource != null ) { if ( resource != null ) {
return resource; return resource;
@ -204,7 +263,9 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
@Override @Override
protected Class<?> findClass(String name) throws ClassNotFoundException { protected Class<?> findClass(String name) throws ClassNotFoundException {
for ( ClassLoader classLoader : individualClassLoaders ) { final Iterator<ClassLoader> clIterator = newClassLoaderIterator();
while ( clIterator.hasNext() ) {
final ClassLoader classLoader = clIterator.next();
try { try {
return classLoader.loadClass( name ); return classLoader.loadClass( name );
} }
@ -358,8 +419,8 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
return work.doWork( getAggregatedClassLoader() ); return work.doWork( getAggregatedClassLoader() );
} }
private ClassLoader getAggregatedClassLoader() { private AggregatedClassLoader getAggregatedClassLoader() {
final ClassLoader aggregated = this.aggregatedClassLoader; final AggregatedClassLoader aggregated = this.aggregatedClassLoader;
if ( aggregated == null ) { if ( aggregated == null ) {
throw log.usingStoppedClassLoaderService(); throw log.usingStoppedClassLoaderService();
} }

View File

@ -80,4 +80,33 @@ public interface ClassLoaderService extends Service, Stoppable {
} }
<T> T workWithClassLoader(Work<T> work); <T> T workWithClassLoader(Work<T> work);
/**
* Defines of the lookup in the current thread context {@link ClassLoader} should be
* used.
*/
enum TCCLLookupBehavior {
/**
* The current thread context {@link ClassLoader} will never be used during
* the class lookup.
*/
NEVER,
/**
* The class lookup will be done in the thread context {@link ClassLoader} prior
* to the other {@code ClassLoader}s.
*/
BEFORE,
/**
* The class lookup will be done in the thread context {@link ClassLoader} if
* the former hasn't been found in the other {@code ClassLoader}s.
* This is the default value.
*/
AFTER
}
TCCLLookupBehavior getTTCLLookupBehavior();
void setTCCLLookupBehavior(TCCLLookupBehavior behavior);
} }

View File

@ -7,6 +7,7 @@
package org.hibernate.cfg; package org.hibernate.cfg;
import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.MetadataBuilder;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.query.internal.ParameterMetadataImpl; import org.hibernate.query.internal.ParameterMetadataImpl;
import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionCoordinator;
import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder;
@ -190,6 +191,14 @@ public interface AvailableSettings {
*/ */
String CLASSLOADERS = "hibernate.classLoaders"; String CLASSLOADERS = "hibernate.classLoaders";
/**
* Used to define how the current thread context {@link ClassLoader} must be used
* for class lookup.
*
* @see ClassLoaderService#TCCLLookupBehavior
*/
String TC_CLASSLOADER = "hibernate.classloader.tccl_lookup";
/** /**
* Names the {@link ClassLoader} used to load user application classes. * Names the {@link ClassLoader} used to load user application classes.
* @since 4.0 * @since 4.0

View File

@ -13,6 +13,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.AttributeConverter; import javax.persistence.AttributeConverter;
@ -44,6 +45,7 @@ import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService.TCCLLookupBehavior;
import org.hibernate.boot.registry.selector.StrategyRegistrationProvider; import org.hibernate.boot.registry.selector.StrategyRegistrationProvider;
import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.boot.spi.MetadataBuilderImplementor; import org.hibernate.boot.spi.MetadataBuilderImplementor;
@ -390,6 +392,15 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil
bsrBuilder.applyClassLoader( (ClassLoader) classLoadersSetting ); bsrBuilder.applyClassLoader( (ClassLoader) classLoadersSetting );
} }
} }
//configurationValues not assigned yet, using directly the properties of the PU
Properties puProperties = persistenceUnit.getProperties();
if( puProperties != null ) {
final String tcclBehavior = puProperties.getProperty( org.hibernate.cfg.AvailableSettings.TC_CLASSLOADER );
if( tcclBehavior != null ) {
bsrBuilder.applyTCCLBehavior( TCCLLookupBehavior.valueOf( tcclBehavior.toUpperCase() ) );
}
}
} }
return bsrBuilder.build(); return bsrBuilder.build();

View File

@ -0,0 +1,121 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.boot.registry.classloading.internal;
import java.net.URL;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author Cedric Tabin
*/
public class ClassLoaderServiceImplTest {
@Test
public void testNullTCCL() {
ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl();
Thread.currentThread().setContextClassLoader(null);
csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.BEFORE);
Class<ClassLoaderServiceImplTest> clazz1 = csi.classForName(ClassLoaderServiceImplTest.class.getName());
assertEquals(ClassLoaderServiceImplTest.class, clazz1);
csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.AFTER);
Class<ClassLoaderServiceImplTest> clazz2 = csi.classForName(ClassLoaderServiceImplTest.class.getName());
assertEquals(ClassLoaderServiceImplTest.class, clazz2);
csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.NEVER);
Class<ClassLoaderServiceImplTest> clazz3 = csi.classForName(ClassLoaderServiceImplTest.class.getName());
assertEquals(ClassLoaderServiceImplTest.class, clazz3);
csi.stop();
try { csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.BEFORE); assertTrue(false); } catch (Exception e) { }
try { csi.getTTCLLookupBehavior(); assertTrue(false); } catch (Exception e) { }
}
@Test
public void testLookupBefore() {
InternalClassLoader icl = new InternalClassLoader();
Thread.currentThread().setContextClassLoader(icl);
ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl();
csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.BEFORE);
Class<ClassLoaderServiceImplTest> clazz = csi.classForName(ClassLoaderServiceImplTest.class.getName());
assertEquals(ClassLoaderServiceImplTest.class, clazz);
assertEquals(1, icl.accessCount);
}
@Test
public void testLookupAfterAvoided() {
InternalClassLoader icl = new InternalClassLoader();
Thread.currentThread().setContextClassLoader(icl);
ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl();
csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.AFTER);
Class<ClassLoaderServiceImplTest> clazz = csi.classForName(ClassLoaderServiceImplTest.class.getName());
assertEquals(ClassLoaderServiceImplTest.class, clazz);
assertEquals(0, icl.accessCount);
}
@Test
public void testLookupAfter() {
InternalClassLoader icl = new InternalClassLoader();
Thread.currentThread().setContextClassLoader(icl);
ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl();
csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.AFTER);
try { csi.classForName("test.class.name"); assertTrue(false); }
catch (Exception e) {}
assertEquals(1, icl.accessCount);
}
@Test
public void testLookupAfterNotFound() {
InternalClassLoader icl = new InternalClassLoader();
Thread.currentThread().setContextClassLoader(icl);
ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl();
csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.AFTER);
try { csi.classForName("test.class.not.found"); assertTrue(false); }
catch (Exception e) { }
assertEquals(1, icl.accessCount);
}
@Test
public void testLookupNever() {
InternalClassLoader icl = new InternalClassLoader();
Thread.currentThread().setContextClassLoader(icl);
ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl();
csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.NEVER);
try { csi.classForName("test.class.name"); assertTrue(false); }
catch (Exception e) { }
assertEquals(0, icl.accessCount);
csi.stop();
try { csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.BEFORE); assertTrue(false); } catch (Exception e) { }
try { csi.getTTCLLookupBehavior(); assertTrue(false); } catch (Exception e) { }
}
private static class InternalClassLoader extends ClassLoader {
private int accessCount = 0;
public InternalClassLoader() { super(null); }
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
++accessCount;
return super.loadClass(name);
}
@Override
protected URL findResource(String name) {
++accessCount;
return super.findResource(name);
}
}
}