Skip to content

Commit ad37d6f

Browse files
committed
Inject the class loader from the parent context into the child context more aggressively.
`ConditionEvaluator` extracts the class loader from the bean factory at instantiation, and then uses said classloader to load classes when evaluating conditionals. Hence, we need to inject the parent class loader into a bean factory that we inject into the child context. This fix is an extension of the fix made in spring-cloud/spring-cloud-netflix#3101. Fixes spring-cloud/spring-cloud-openfeign#475
1 parent 353d4f3 commit ad37d6f

File tree

2 files changed

+68
-4
lines changed

2 files changed

+68
-4
lines changed

spring-cloud-context/src/main/java/org/springframework/cloud/context/named/NamedContextFactory.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@
2929
import org.springframework.beans.factory.DisposableBean;
3030
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3131
import org.springframework.beans.factory.ObjectProvider;
32+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3233
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
3334
import org.springframework.context.ApplicationContext;
3435
import org.springframework.context.ApplicationContextAware;
36+
import org.springframework.context.ConfigurableApplicationContext;
3537
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3638
import org.springframework.core.ResolvableType;
3739
import org.springframework.core.env.MapPropertySource;
@@ -110,7 +112,25 @@ protected AnnotationConfigApplicationContext getContext(String name) {
110112
}
111113

112114
protected AnnotationConfigApplicationContext createContext(String name) {
113-
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
115+
AnnotationConfigApplicationContext context;
116+
if (this.parent != null) {
117+
// jdk11 issue
118+
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
119+
// https://github.com/spring-cloud/spring-cloud-openfeign/issues/475
120+
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
121+
if (parent instanceof ConfigurableApplicationContext) {
122+
beanFactory.setBeanClassLoader(
123+
((ConfigurableApplicationContext) parent).getBeanFactory().getBeanClassLoader());
124+
}
125+
else {
126+
beanFactory.setBeanClassLoader(parent.getClassLoader());
127+
}
128+
context = new AnnotationConfigApplicationContext(beanFactory);
129+
context.setClassLoader(this.parent.getClassLoader());
130+
}
131+
else {
132+
context = new AnnotationConfigApplicationContext();
133+
}
114134
if (this.configurations.containsKey(name)) {
115135
for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
116136
context.register(configuration);
@@ -129,9 +149,6 @@ protected AnnotationConfigApplicationContext createContext(String name) {
129149
if (this.parent != null) {
130150
// Uses Environment from parent as well as beans
131151
context.setParent(this.parent);
132-
// jdk11 issue
133-
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
134-
context.setClassLoader(this.parent.getClassLoader());
135152
}
136153
context.setDisplayName(generateDisplayName(name));
137154
context.refresh();

spring-cloud-context/src/test/java/org/springframework/cloud/context/named/NamedContextFactoryTests.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,20 @@
1818

1919
import java.util.Arrays;
2020
import java.util.Map;
21+
import java.util.concurrent.ExecutionException;
22+
import java.util.concurrent.ExecutorService;
23+
import java.util.concurrent.Executors;
24+
import java.util.concurrent.TimeUnit;
25+
import java.util.concurrent.TimeoutException;
2126

2227
import org.assertj.core.api.Assertions;
2328
import org.junit.jupiter.api.Test;
2429

30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2531
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2632
import org.springframework.context.annotation.Bean;
33+
import org.springframework.context.support.GenericApplicationContext;
34+
import org.springframework.util.ClassUtils;
2735

2836
import static org.assertj.core.api.BDDAssertions.then;
2937

@@ -37,6 +45,10 @@ public void testChildContexts() {
3745
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
3846
parent.register(BaseConfig.class);
3947
parent.refresh();
48+
testChildContexts(parent);
49+
}
50+
51+
private void testChildContexts(GenericApplicationContext parent) {
4052
TestClientFactory factory = new TestClientFactory();
4153
factory.setApplicationContext(parent);
4254
factory.setConfigurations(Arrays.asList(getSpec("foo", FooConfig.class), getSpec("bar", BarConfig.class)));
@@ -79,6 +91,10 @@ public void testChildContexts() {
7991
then(fooContext.getClassLoader()).as("foo context classloader does not match parent")
8092
.isSameAs(parent.getClassLoader());
8193

94+
then(fooContext.getBeanFactory().getBeanClassLoader())
95+
.as("foo context bean factory classloader does not match parent")
96+
.isSameAs(parent.getBeanFactory().getBeanClassLoader());
97+
8298
Assertions.assertThat(fooContext).hasFieldOrPropertyWithValue("customClassLoader", true);
8399

84100
factory.destroy();
@@ -88,10 +104,40 @@ public void testChildContexts() {
88104
then(barContext.isActive()).as("bar context wasn't closed").isFalse();
89105
}
90106

107+
@Test
108+
public void testBadThreadContextClassLoader() throws InterruptedException, ExecutionException, TimeoutException {
109+
110+
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
111+
parent.setClassLoader(ClassUtils.getDefaultClassLoader());
112+
parent.register(BaseConfig.class);
113+
parent.refresh();
114+
115+
ExecutorService es = Executors.newSingleThreadExecutor(r -> {
116+
Thread t = new Thread(r);
117+
t.setContextClassLoader(new ThrowingClassLoader());
118+
return t;
119+
});
120+
121+
es.submit(() -> this.testChildContexts(parent)).get(5, TimeUnit.SECONDS);
122+
}
123+
91124
private TestSpec getSpec(String name, Class<?> configClass) {
92125
return new TestSpec(name, new Class[] { configClass });
93126
}
94127

128+
static class ThrowingClassLoader extends ClassLoader {
129+
130+
ThrowingClassLoader() {
131+
super("Throwing classloader", null);
132+
}
133+
134+
@Override
135+
public Class<?> loadClass(String name) throws ClassNotFoundException {
136+
throw new ClassNotFoundException(name);
137+
}
138+
139+
}
140+
95141
static class TestClientFactory extends NamedContextFactory<TestSpec> {
96142

97143
TestClientFactory() {
@@ -147,6 +193,7 @@ static class Baz {
147193

148194
}
149195

196+
@ConditionalOnClass(Object.class)
150197
static class FooConfig {
151198

152199
@Bean

0 commit comments

Comments
 (0)