From 20a286b4d68bec210456751b496c9b174e7ca287 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 24 Nov 2015 17:38:02 +0100 Subject: [PATCH] ASM ClassWriter uses application ClassLoader for its getCommonSuperClass check Issue: SPR-13695 --- .../aop/framework/CglibAopProxy.java | 54 +++++++++++++++++- ...CglibSubclassingInstantiationStrategy.java | 55 ++++++++++++++++++- .../ConfigurationClassEnhancer.java | 51 ++++++++++++++--- .../ConfigurationClassPostProcessor.java | 6 +- .../ReflectionUtilsIntegrationTests.java | 4 +- .../org/springframework/asm/ClassWriter.java | 14 ++++- .../spel/standard/SpelCompiler.java | 27 ++++++--- 7 files changed, 187 insertions(+), 24 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java index 7f6e0c00424..ae35dd2dd64 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java @@ -36,6 +36,7 @@ import org.springframework.aop.PointcutAdvisor; import org.springframework.aop.RawTargetAccess; import org.springframework.aop.TargetSource; import org.springframework.aop.support.AopUtils; +import org.springframework.cglib.core.ClassGenerator; import org.springframework.cglib.core.CodeGenerationException; import org.springframework.cglib.core.SpringNamingPolicy; import org.springframework.cglib.proxy.Callback; @@ -186,7 +187,7 @@ class CglibAopProxy implements AopProxy, Serializable { enhancer.setSuperclass(proxySuperClass); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); - enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class)); + enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader)); Callback[] callbacks = getCallbacks(rootClass); Class[] types = new Class[callbacks.length]; @@ -703,6 +704,7 @@ class CglibAopProxy implements AopProxy, Serializable { public CglibMethodInvocation(Object proxy, Object target, Method method, Object[] arguments, Class targetClass, List interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) { + super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers); this.methodProxy = methodProxy; this.publicMethod = Modifier.isPublic(method.getModifiers()); @@ -822,8 +824,7 @@ class CglibAopProxy implements AopProxy, Serializable { if (logger.isDebugEnabled()) { logger.debug("Method has advice and optimisations are enabled: " + method); } - // We know that we are optimising so we can use the - // FixedStaticChainInterceptors. + // We know that we are optimising so we can use the FixedStaticChainInterceptors. int index = this.fixedInterceptorMap.get(key); return (index + this.fixedInterceptorOffset); } @@ -950,4 +951,51 @@ class CglibAopProxy implements AopProxy, Serializable { } } + + /** + * CGLIB GeneratorStrategy variant which exposes the application ClassLoader + * as thread context ClassLoader for the time of class generation + * (in order for ASM to pick it up when doing common superclass resolution). + */ + private static class ClassLoaderAwareUndeclaredThrowableStrategy extends UndeclaredThrowableStrategy { + + private final ClassLoader classLoader; + + public ClassLoaderAwareUndeclaredThrowableStrategy(ClassLoader classLoader) { + super(UndeclaredThrowableException.class); + this.classLoader = classLoader; + } + + @Override + public byte[] generate(ClassGenerator cg) throws Exception { + if (this.classLoader == null) { + return super.generate(cg); + } + + Thread currentThread = Thread.currentThread(); + ClassLoader threadContextClassLoader; + try { + threadContextClassLoader = currentThread.getContextClassLoader(); + } + catch (Throwable ex) { + // Cannot access thread context ClassLoader - falling back... + return super.generate(cg); + } + + boolean overrideClassLoader = !this.classLoader.equals(threadContextClassLoader); + if (overrideClassLoader) { + currentThread.setContextClassLoader(this.classLoader); + } + try { + return super.generate(cg); + } + finally { + if (overrideClassLoader) { + // Reset original thread context ClassLoader. + currentThread.setContextClassLoader(threadContextClassLoader); + } + } + } + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java index ed8d5a3e83a..4cf1bea9dc1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -25,6 +25,9 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.cglib.core.ClassGenerator; +import org.springframework.cglib.core.DefaultGeneratorStrategy; import org.springframework.cglib.core.SpringNamingPolicy; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.CallbackFilter; @@ -141,6 +144,10 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(beanDefinition.getBeanClass()); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + if (this.owner instanceof ConfigurableBeanFactory) { + ClassLoader cl = ((ConfigurableBeanFactory) this.owner).getBeanClassLoader(); + enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl)); + } enhancer.setCallbackFilter(new MethodOverrideCallbackFilter(beanDefinition)); enhancer.setCallbackTypes(CALLBACK_TYPES); return enhancer.createClass(); @@ -178,6 +185,52 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt } + /** + * CGLIB GeneratorStrategy variant which exposes the application ClassLoader + * as thread context ClassLoader for the time of class generation + * (in order for ASM to pick it up when doing common superclass resolution). + */ + private static class ClassLoaderAwareGeneratorStrategy extends DefaultGeneratorStrategy { + + private final ClassLoader classLoader; + + public ClassLoaderAwareGeneratorStrategy(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public byte[] generate(ClassGenerator cg) throws Exception { + if (this.classLoader == null) { + return super.generate(cg); + } + + Thread currentThread = Thread.currentThread(); + ClassLoader threadContextClassLoader; + try { + threadContextClassLoader = currentThread.getContextClassLoader(); + } + catch (Throwable ex) { + // Cannot access thread context ClassLoader - falling back... + return super.generate(cg); + } + + boolean overrideClassLoader = !this.classLoader.equals(threadContextClassLoader); + if (overrideClassLoader) { + currentThread.setContextClassLoader(this.classLoader); + } + try { + return super.generate(cg); + } + finally { + if (overrideClassLoader) { + // Reset original thread context ClassLoader. + currentThread.setContextClassLoader(threadContextClassLoader); + } + } + } + } + + /** * CGLIB callback for filtering method interception behavior. */ diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index bcc90294c23..52986ab923e 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -79,8 +79,6 @@ class ConfigurationClassEnhancer { private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS); - private static final DefaultGeneratorStrategy GENERATOR_STRATEGY = new BeanFactoryAwareGeneratorStrategy(); - private static final String BEAN_FACTORY_FIELD = "$$beanFactory"; @@ -94,7 +92,7 @@ class ConfigurationClassEnhancer { * container-aware callbacks capable of respecting scoping and other bean semantics. * @return the enhanced subclass */ - public Class enhance(Class configClass) { + public Class enhance(Class configClass, ClassLoader classLoader) { if (EnhancedConfiguration.class.isAssignableFrom(configClass)) { if (logger.isDebugEnabled()) { logger.debug(String.format("Ignoring request to enhance %s as it has " + @@ -106,7 +104,7 @@ class ConfigurationClassEnhancer { } return configClass; } - Class enhancedClass = createClass(newEnhancer(configClass)); + Class enhancedClass = createClass(newEnhancer(configClass, classLoader)); if (logger.isDebugEnabled()) { logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s", configClass.getName(), enhancedClass.getName())); @@ -117,13 +115,13 @@ class ConfigurationClassEnhancer { /** * Creates a new CGLIB {@link Enhancer} instance. */ - private Enhancer newEnhancer(Class superclass) { + private Enhancer newEnhancer(Class superclass, ClassLoader classLoader) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(superclass); enhancer.setInterfaces(new Class[] {EnhancedConfiguration.class}); enhancer.setUseFactory(false); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); - enhancer.setStrategy(GENERATOR_STRATEGY); + enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader)); enhancer.setCallbackFilter(CALLBACK_FILTER); enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes()); return enhancer; @@ -144,7 +142,7 @@ class ConfigurationClassEnhancer { /** * Marker interface to be implemented by all @Configuration CGLIB subclasses. - * Facilitates idempotent behavior for {@link ConfigurationClassEnhancer#enhance(Class)} + * Facilitates idempotent behavior for {@link ConfigurationClassEnhancer#enhance} * through checking to see if candidate classes are already assignable to it, e.g. * have already been enhanced. *

Also extends {@link BeanFactoryAware}, as all enhanced {@code @Configuration} @@ -204,9 +202,17 @@ class ConfigurationClassEnhancer { /** * Custom extension of CGLIB's DefaultGeneratorStrategy, introducing a {@link BeanFactory} field. + * Also exposes the application ClassLoader as thread context ClassLoader for the time of + * class generation (in order for ASM to pick it up when doing common superclass resolution). */ private static class BeanFactoryAwareGeneratorStrategy extends DefaultGeneratorStrategy { + private final ClassLoader classLoader; + + public BeanFactoryAwareGeneratorStrategy(ClassLoader classLoader) { + this.classLoader = classLoader; + } + @Override protected ClassGenerator transform(ClassGenerator cg) throws Exception { ClassEmitterTransformer transformer = new ClassEmitterTransformer() { @@ -218,6 +224,37 @@ class ConfigurationClassEnhancer { }; return new TransformingClassGenerator(cg, transformer); } + + @Override + public byte[] generate(ClassGenerator cg) throws Exception { + if (this.classLoader == null) { + return super.generate(cg); + } + + Thread currentThread = Thread.currentThread(); + ClassLoader threadContextClassLoader; + try { + threadContextClassLoader = currentThread.getContextClassLoader(); + } + catch (Throwable ex) { + // Cannot access thread context ClassLoader - falling back... + return super.generate(cg); + } + + boolean overrideClassLoader = !this.classLoader.equals(threadContextClassLoader); + if (overrideClassLoader) { + currentThread.setContextClassLoader(this.classLoader); + } + try { + return super.generate(cg); + } + finally { + if (overrideClassLoader) { + // Reset original thread context ClassLoader. + currentThread.setContextClassLoader(threadContextClassLoader); + } + } + } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 937fa1ff9e4..78f3b04ed59 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -397,11 +397,11 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo try { // Set enhanced subclass of the user-specified bean class Class configClass = beanDef.resolveBeanClass(this.beanClassLoader); - Class enhancedClass = enhancer.enhance(configClass); + Class enhancedClass = enhancer.enhance(configClass, this.beanClassLoader); if (configClass != enhancedClass) { if (logger.isDebugEnabled()) { - logger.debug(String.format("Replacing bean definition '%s' existing class name '%s' " + - "with enhanced class name '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName())); + logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " + + "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName())); } beanDef.setBeanClass(enhancedClass); } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ReflectionUtilsIntegrationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ReflectionUtilsIntegrationTests.java index 338e0bd5515..e913b0f8bff 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ReflectionUtilsIntegrationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ReflectionUtilsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 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. @@ -37,7 +37,7 @@ public class ReflectionUtilsIntegrationTests { @Test public void getUniqueDeclaredMethods_withCovariantReturnType_andCglibRewrittenMethodNames() throws Exception { - Class cglibLeaf = new ConfigurationClassEnhancer().enhance(Leaf.class); + Class cglibLeaf = new ConfigurationClassEnhancer().enhance(Leaf.class, null); int m1MethodCount = 0; Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(cglibLeaf); for (Method method : methods) { diff --git a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java index a3d3a2debb8..4ec8c53a86e 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java @@ -1676,7 +1676,8 @@ public class ClassWriter extends ClassVisitor { */ protected String getCommonSuperClass(final String type1, final String type2) { Class c, d; - ClassLoader classLoader = getClass().getClassLoader(); + // SPRING PATCH: PREFER APPLICATION CLASSLOADER + ClassLoader classLoader = getClassLoader(); try { c = Class.forName(type1.replace('/', '.'), false, classLoader); d = Class.forName(type2.replace('/', '.'), false, classLoader); @@ -1699,6 +1700,17 @@ public class ClassWriter extends ClassVisitor { } } + // SPRING PATCH: PREFER THREAD CONTEXT CLASSLOADER FOR APPLICATION CLASSES + protected ClassLoader getClassLoader() { + ClassLoader classLoader = null; + try { + classLoader = Thread.currentThread().getContextClassLoader(); + } catch (Throwable ex) { + // Cannot access thread context ClassLoader - falling back... + } + return (classLoader != null ? classLoader : getClass().getClassLoader()); + } + /** * Returns the constant pool's hash table item which is equal to the given * item. diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java index 9ec6c8b83d9..faa14f3d42b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -134,7 +134,7 @@ public class SpelCompiler implements Opcodes { private Class createExpressionClass(SpelNodeImpl expressionToCompile) { // Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression' String clazzName = "spel/Ex" + getNextSuffix(); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); + ClassWriter cw = new ExpressionClassWriter(); cw.visit(V1_5, ACC_PUBLIC, clazzName, null, "org/springframework/expression/spel/CompiledExpression", null); // Create default constructor @@ -256,20 +256,20 @@ public class SpelCompiler implements Opcodes { } catch (IOException ex) { throw new IllegalStateException( - "Unexpected problem dumping class " + nameToUse + " into " + dumpLocation, ex); + "Unexpected problem dumping class '" + nameToUse + "' into " + dumpLocation, ex); } } /** - * A ChildClassLoader will load the generated compiled expression classes + * A ChildClassLoader will load the generated compiled expression classes. */ - public static class ChildClassLoader extends URLClassLoader { + private static class ChildClassLoader extends URLClassLoader { private static final URL[] NO_URLS = new URL[0]; - public ChildClassLoader(ClassLoader classloader) { - super(NO_URLS, classloader); + public ChildClassLoader(ClassLoader classLoader) { + super(NO_URLS, classLoader); } public Class defineClass(String name, byte[] bytes) { @@ -277,4 +277,17 @@ public class SpelCompiler implements Opcodes { } } + + private class ExpressionClassWriter extends ClassWriter { + + public ExpressionClassWriter() { + super(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + } + + @Override + protected ClassLoader getClassLoader() { + return ccl; + } + } + }