Skip to content

Commit 0b531ee

Browse files
authored
Merge pull request #26 from hatgaei/master
fix afterburner serialization issue
2 parents 2d98c18 + a0fe1d0 commit 0b531ee

File tree

3 files changed

+84
-22
lines changed

3 files changed

+84
-22
lines changed

afterburner/src/main/java/com/fasterxml/jackson/module/afterburner/util/MyClassLoader.java

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ public MyClassLoader(ClassLoader parent, boolean tryToUseParent)
2323
_cfgUseParentLoader = tryToUseParent;
2424
}
2525

26+
@Override
27+
public Class<?> loadClass(String name) throws ClassNotFoundException {
28+
try {
29+
return super.loadClass(name);
30+
} catch (ClassNotFoundException e) {
31+
return getClass().getClassLoader().loadClass(name);
32+
}
33+
}
34+
2635
/**
2736
* Helper method called to check whether it is acceptable to create a new
2837
* class in package that given class is part of.
@@ -63,47 +72,44 @@ public Class<?> loadAndResolve(ClassName className, byte[] byteCode)
6372
if (old != null) {
6473
return old;
6574
}
66-
67-
Class<?> impl;
6875

6976
// Important: bytecode is generated with a template name (since bytecode itself
7077
// is used for checksum calculation) -- must be replaced now, however
7178
replaceName(byteCode, className.getSlashedTemplate(), className.getSlashedName());
7279

7380
// First: let's try calling it directly on parent, to be able to access protected/package-access stuff:
74-
if (_cfgUseParentLoader) {
75-
ClassLoader cl = getParent();
76-
// if we have parent, that is
77-
if (cl != null) {
78-
try {
79-
Method method = ClassLoader.class.getDeclaredMethod("defineClass",
80-
new Class[] {String.class, byte[].class, int.class,
81-
int.class});
82-
method.setAccessible(true);
83-
return (Class<?>)method.invoke(getParent(),
84-
className.getDottedName(), byteCode, 0, byteCode.length);
85-
} catch (Exception e) {
86-
// Should we handle this somehow?
87-
}
81+
if (_cfgUseParentLoader && getParent() != null) {
82+
try {
83+
Method method = ClassLoader.class.getDeclaredMethod("defineClass",
84+
new Class[] {String.class, byte[].class, int.class,
85+
int.class});
86+
method.setAccessible(true);
87+
return (Class<?>)method.invoke(getParent(),
88+
className.getDottedName(), byteCode, 0, byteCode.length);
89+
} catch (Exception e) {
90+
// Should we handle this somehow?
8891
}
8992
}
9093

9194
// but if that doesn't fly, try to do it from our own class loader
92-
95+
return resolveFromThisClassLoader(className, byteCode);
96+
}
97+
98+
private Class<?> resolveFromThisClassLoader(ClassName className, byte[] byteCode) {
9399
try {
94-
impl = defineClass(className.getDottedName(), byteCode, 0, byteCode.length);
100+
Class<?> impl = defineClass(className.getDottedName(), byteCode, 0, byteCode.length);
101+
// important: must also resolve the class...
102+
resolveClass(impl);
103+
return impl;
95104
} catch (LinkageError e) {
96105
Throwable t = e;
97106
while (t.getCause() != null) {
98107
t = t.getCause();
99108
}
100109
throw new IllegalArgumentException("Failed to load class '"+className+"': "+t.getMessage(), t);
101110
}
102-
// important: must also resolve the class...
103-
resolveClass(impl);
104-
return impl;
105111
}
106-
112+
107113
public static int replaceName(byte[] byteCode,
108114
String from, String to)
109115
{
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.fasterxml.jackson.module.afterburner.roundtrip;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
5+
import junit.framework.TestCase;
6+
7+
import java.io.IOException;
8+
import java.net.URL;
9+
import java.net.URLClassLoader;
10+
11+
/**
12+
* Made for a bug found when trying to serialize an Object loaded from an
13+
* parent classloader (or, more generally, an isolated classloader).<br/><br/>
14+
*
15+
* What happens is that MyClassLoader defaults the parent cl to the bean's
16+
* classloader, which then extends BeanPropertyAccessor. However, the
17+
* bean's classloader doesn't know what BeanPropertyAccessor is and blows
18+
* up.<br/><br/>
19+
*
20+
* The Bean.class (in the resources dir) is simply defined as:<br/><br/>
21+
*
22+
* <pre>
23+
* public class Bean {
24+
* private String value = "some string";
25+
* public String getValue() { return value; }
26+
* }
27+
* </pre>
28+
*
29+
* It's important that Bean.class doesn't have a setter; otherwise the
30+
* exception doesn't occur.<br/><br/>
31+
*/
32+
public class IsolatedClassLoaderTest extends TestCase {
33+
34+
public void testBeanWithSeparateClassLoader() throws IOException {
35+
36+
AfterburnerModule module = new AfterburnerModule();
37+
ObjectMapper mapper = new ObjectMapper();
38+
mapper.registerModule(module);
39+
40+
Object bean = makeObjectFromIsolatedClassloader();
41+
String result = mapper.writeValueAsString(bean);
42+
assertEquals("{\"value\":\"some string\"}", result);
43+
}
44+
45+
private Object makeObjectFromIsolatedClassloader() {
46+
try {
47+
URL[] resourcesDir = {getClass().getResource("")};
48+
// Parent classloader is null so Afterburner is inaccessible.
49+
ClassLoader isolated = new URLClassLoader(resourcesDir, null);
50+
Class<?> beanClz = isolated.loadClass("Bean");
51+
return beanClz.newInstance();
52+
} catch (Exception e) {
53+
throw new RuntimeException(e);
54+
}
55+
}
56+
}
Binary file not shown.

0 commit comments

Comments
 (0)