Skip to content

Commit dc5b0e1

Browse files
author
zhourenjian@gmail.com
committed
Support dynamic class loading or re-loading
Usage: SimpleClassLoader.reloadSimpleClass(className, classpath);
1 parent 124aad7 commit dc5b0e1

File tree

3 files changed

+225
-49
lines changed

3 files changed

+225
-49
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2013 java2script.org and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Zhou Renjian / zhourenjian@gmail.com - initial API and implementation
10+
*******************************************************************************/
11+
12+
package net.sf.j2s.ajax;
13+
14+
import java.io.DataInputStream;
15+
import java.io.File;
16+
import java.io.FileInputStream;
17+
import java.io.IOException;
18+
import java.util.HashMap;
19+
import java.util.HashSet;
20+
import java.util.Iterator;
21+
import java.util.Map;
22+
import java.util.Set;
23+
24+
/**
25+
* SimpleClassLoader is designed to load or reload class instance.
26+
*
27+
* Developer can use {@link #reloadSimpleClass(String, String)} to reload a
28+
* given class.
29+
*
30+
* @author zhourenjian
31+
*
32+
*/
33+
public class SimpleClassLoader extends ClassLoader {
34+
35+
private String classpath;
36+
37+
private Set<String> targetClasses;
38+
private Map<String, Class<?>> loadedClasses;
39+
private Object loadingMutex;
40+
41+
private static boolean hasClassReloaded = false;
42+
private static ClassLoader defaultLoader = SimpleClassLoader.class.getClassLoader();
43+
private static Map<String, SimpleClassLoader> allLoaders = new HashMap<String, SimpleClassLoader>();
44+
45+
public SimpleClassLoader(ClassLoader parent, String path) {
46+
super(parent);
47+
targetClasses = new HashSet<String>();
48+
loadedClasses = new HashMap<String, Class<?>>();
49+
loadingMutex = new Object();
50+
classpath = path;
51+
}
52+
53+
@Override
54+
public Class<?> loadClass(String clazzName) throws ClassNotFoundException {
55+
if (targetClasses.contains(clazzName)) {
56+
Class<?> clazz = loadedClasses.get(clazzName);
57+
if (clazz != null) {
58+
return clazz;
59+
}
60+
try {
61+
synchronized (loadingMutex) {
62+
clazz = loadedClasses.get(clazzName);
63+
if (clazz == null) {
64+
byte[] bytes = loadClassData(clazzName);
65+
clazz = defineClass(clazzName, bytes, 0, bytes.length);
66+
synchronized (loadedClasses) {
67+
loadedClasses.put(clazzName, clazz);
68+
}
69+
}
70+
}
71+
return clazz;
72+
} catch (Throwable e) {
73+
e.printStackTrace();
74+
}
75+
}
76+
return getParent().loadClass(clazzName);
77+
}
78+
79+
/*
80+
* Read class bytes from file system.
81+
*/
82+
private byte[] loadClassData(String className) throws IOException {
83+
String cp = classpath;
84+
if (cp == null) {
85+
cp = "./";
86+
}
87+
int length = cp.length();
88+
if (length > 0) {
89+
char c = cp.charAt(length - 1);
90+
if (c != '/' && c != '\\') {
91+
cp = cp + "/";
92+
}
93+
}
94+
File f = new File(cp + className.replaceAll("\\.", "/") + ".class");
95+
int size = (int) f.length();
96+
byte buff[] = new byte[size];
97+
FileInputStream fis = new FileInputStream(f);
98+
DataInputStream dis = new DataInputStream(fis);
99+
dis.readFully(buff);
100+
dis.close();
101+
return buff;
102+
}
103+
104+
/**
105+
* Load given class and create instance by default constructor.
106+
*
107+
* This method should not be used for those classes without default constructor.
108+
*
109+
* @param clazzName
110+
* @return
111+
*/
112+
public static Object loadSimpleInstance(String clazzName) {
113+
try {
114+
Class<?> runnableClass = null;
115+
ClassLoader classLoader = hasClassReloaded ? allLoaders.get(clazzName) : null;
116+
if (classLoader != null) {
117+
runnableClass = classLoader.loadClass(clazzName);
118+
} else {
119+
runnableClass = Class.forName(clazzName);
120+
}
121+
if (runnableClass != null) {
122+
return runnableClass.newInstance();
123+
}
124+
} catch (Exception e) {
125+
e.printStackTrace();
126+
}
127+
return null;
128+
}
129+
130+
/**
131+
* Try to reload given class.
132+
*
133+
* Class should contains default constructor.
134+
*
135+
* @param clazzName
136+
* @param classpath for the given class
137+
*/
138+
public static void reloadSimpleClass(String clazzName, String path) {
139+
hasClassReloaded = true;
140+
SimpleClassLoader loader = null;
141+
synchronized (allLoaders) {
142+
for (Iterator<SimpleClassLoader> itr = allLoaders.values().iterator();
143+
itr.hasNext();) {
144+
loader = (SimpleClassLoader) itr.next();
145+
if (!loader.loadedClasses.containsKey(clazzName)
146+
&& ((loader.classpath == null && path == null) || loader.classpath.equals(path))) {
147+
// Class loader does not know class specified by clazzName variable
148+
break;
149+
}
150+
loader = null;
151+
}
152+
}
153+
if (loader == null) {
154+
loader = new SimpleClassLoader(defaultLoader, path);
155+
}
156+
synchronized (loader.targetClasses) {
157+
loader.targetClasses.add(clazzName);
158+
}
159+
synchronized (allLoaders) {
160+
allLoaders.put(clazzName, loader);
161+
}
162+
SimpleSerializable.removeCachedClassFields(clazzName);
163+
}
164+
165+
}

sources/net.sf.j2s.ajax/ajaxrpc/net/sf/j2s/ajax/SimpleRPCUtils.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public class SimpleRPCUtils {
2323
public static Set<String> compareDiffs(SimpleRPCRunnable runnable1, SimpleRPCRunnable runnable2) {
2424
Set<String> diffSet = new HashSet<String>();
2525
String[] ignoredFields = runnable1.fieldDiffIgnored();
26-
Map<String, Field> allFields = runnable1.getSerializableFields(runnable1.getClass().getName());
26+
Class<?> clazzType = runnable1.getClass();
27+
Map<String, Field> allFields = SimpleSerializable.getSerializableFields(clazzType.getName(), clazzType, false);
2728
for (Iterator<String> itr = allFields.keySet().iterator(); itr.hasNext();) {
2829
String name = (String) itr.next();
2930
Field field = allFields.get(name);

sources/net.sf.j2s.ajax/ajaxrpc/net/sf/j2s/ajax/SimpleSerializable.java

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
import java.io.UnsupportedEncodingException;
1515
import java.lang.reflect.Array;
16-
import java.lang.reflect.Constructor;
1716
import java.lang.reflect.Field;
1817
import java.lang.reflect.Modifier;
1918
import java.lang.reflect.ParameterizedType;
@@ -47,12 +46,13 @@ public class SimpleSerializable implements Cloneable {
4746

4847
public static boolean JSON_EXPAND_MODE = true;
4948

49+
@J2SIgnore
50+
private static Object mutex = new Object();
5051
@J2SIgnore
5152
private static Map<String, Map<String, Field>> quickFields = new HashMap<String, Map<String, Field>>();
52-
5353
@J2SIgnore
54-
private static Map<String, Constructor<?>> quickConstructors = new HashMap<String, Constructor<?>>();
55-
54+
private static Set<String> expiredClasses = new HashSet<String>();
55+
5656
@J2SIgnore
5757
private static class DeserializeObject {
5858
Object object;
@@ -69,11 +69,20 @@ public DeserializeObject(Object object, int index, boolean error) {
6969
};
7070

7171
@J2SIgnore
72-
Map<String, Field> getSerializableFields(String clazzName) {
73-
Map<String, Field> fields = quickFields.get(clazzName);
72+
static Map<String, Field> getSerializableFields(String clazzName, Class<?> clazz, boolean forceUpdate) {
73+
Map<String, Field> fields = forceUpdate ? null : quickFields.get(clazzName);
7474
if (fields == null) {
75+
if (!forceUpdate) {
76+
if (expiredClasses.contains(clazzName)) {
77+
// Load class from bytes. Variable clazz may be out of date
78+
Object inst = SimpleClassLoader.loadSimpleInstance(clazzName);
79+
if (inst != null) {
80+
// Force updating fields in cache
81+
return getSerializableFields(clazzName, inst.getClass(), true);
82+
}
83+
}
84+
}
7585
fields = new HashMap<String, Field>();
76-
Class<?> clazz = this.getClass();
7786
while(clazz != null && !"net.sf.j2s.ajax.SimpleSerializable".equals(clazz.getName())) {
7887
Field[] clazzFields = clazz.getDeclaredFields();
7988
for (int i = 0; i < clazzFields.length; i++) {
@@ -86,8 +95,20 @@ Map<String, Field> getSerializableFields(String clazzName) {
8695
}
8796
clazz = clazz.getSuperclass();
8897
}
89-
synchronized (quickFields) {
90-
quickFields.put(clazzName, fields);
98+
synchronized (mutex) {
99+
if (!forceUpdate) {
100+
if (expiredClasses.contains(clazzName)) {
101+
// Class already be reloaded. Do not put fields into cache.
102+
return fields;
103+
}
104+
}
105+
if (forceUpdate || quickFields.get(clazzName) == null) {
106+
quickFields.put(clazzName, fields);
107+
if (forceUpdate) {
108+
// Class and fields are already updated, mark it as updated
109+
expiredClasses.remove(clazzName);
110+
}
111+
}
91112
}
92113
}
93114
return fields;
@@ -371,7 +392,7 @@ private String serialize(SimpleFilter filter, List<SimpleSerializable> ssObjs) {
371392
buffer.append("#00000000$"); // later the number of size will be updated!
372393
int headSize = buffer.length();
373394

374-
Map<String, Field> fields = getSerializableFields(clazzName);
395+
Map<String, Field> fields = getSerializableFields(clazzName, this.getClass(), false);
375396
boolean ignoring = (filter == null || filter.ignoreDefaultFields());
376397
String[] fMap = fieldMapping();
377398
try {
@@ -1187,7 +1208,8 @@ private String jsonSerialize(SimpleFilter filter, List<SimpleSerializable> ssObj
11871208
}
11881209
boolean commasAppended = false;
11891210
boolean ignoring = (filter == null || filter.ignoreDefaultFields());
1190-
Map<String, Field> fieldMap = getSerializableFields(this.getClass().getName());
1211+
Class<?> clazzType = this.getClass();
1212+
Map<String, Field> fieldMap = getSerializableFields(clazzType.getName(), clazzType, false);
11911213
String[] fMap = fieldMapping();
11921214
for (Iterator<String> itr = fieldMap.keySet().iterator(); itr.hasNext();) {
11931215
String fieldName = (String) itr.next();
@@ -1945,7 +1967,8 @@ private boolean deserialize(final String str, int start, List<SimpleSerializable
19451967
if (index + size > end) return false;
19461968
}
19471969

1948-
Map<String, Field> fieldMap = getSerializableFields(this.getClass().getName());
1970+
Class<?> clazzType = this.getClass();
1971+
Map<String, Field> fieldMap = getSerializableFields(clazzType.getName(), clazzType, false);
19491972
int objectEnd = index + size;
19501973
String[] fMap = fieldMapping();
19511974
while (index < end && index < objectEnd) {
@@ -2616,28 +2639,21 @@ private DeserializeObject deserializeArrayItem(String str, int index, int end, L
26162639

26172640
@J2SIgnore
26182641
public static SimpleSerializable parseInstance(Map<String, Object> properties) {
2619-
Class<?> runnableClass = null;
26202642
String clazzName = (String)properties.get("class");
2621-
try {
2622-
runnableClass = Class.forName(clazzName); // !!! JavaScript loading!
2623-
if (runnableClass != null) {
2624-
// SimpleRPCRunnale should always has default constructor
2625-
Constructor<?> constructor = runnableClass.getConstructor(new Class[0]);
2626-
Object obj = constructor.newInstance(new Object[0]);
2627-
if (obj != null && obj instanceof SimpleSerializable) {
2628-
return (SimpleSerializable) obj;
2629-
}
2630-
}
2631-
} catch (Exception e) {
2632-
e.printStackTrace();
2643+
if (clazzName == null) {
2644+
return null;
26332645
}
2634-
return null;
2646+
Object inst = SimpleClassLoader.loadSimpleInstance(clazzName);
2647+
if (inst != null && inst instanceof SimpleSerializable) {
2648+
return (SimpleSerializable) inst;
2649+
}
2650+
return UNKNOWN;
26352651
}
26362652

26372653
@J2SIgnore
26382654
public void deserialize(Map<String, Object> properties) {
26392655
String clazzName = (String) properties.get("class");
2640-
Map<String, Field> fieldMap = getSerializableFields(clazzName);
2656+
Map<String, Field> fieldMap = getSerializableFields(clazzName, this.getClass(), false);
26412657
String[] fMap = fieldMapping();
26422658
for (Iterator<String> itr = properties.keySet().iterator(); itr.hasNext();) {
26432659
String fieldName = (String) itr.next();
@@ -2853,7 +2869,8 @@ protected boolean bytesCompactMode() {
28532869
public Object clone() throws CloneNotSupportedException {
28542870
Object clone = super.clone();
28552871

2856-
Map<String, Field> fields = this.getSerializableFields(this.getClass().getName());
2872+
Class<? extends SimpleSerializable> clazz = this.getClass();
2873+
Map<String, Field> fields = getSerializableFields(clazz.getName(), clazz, false);
28572874
for (Iterator<Field> itr = fields.values().iterator(); itr.hasNext();) {
28582875
Field field = (Field) itr.next();
28592876
Class<?> type = field.getType();
@@ -2980,27 +2997,20 @@ public static SimpleSerializable parseInstance(String str, int start, SimpleFilt
29802997
if (filter != null) {
29812998
if (!filter.accept(clazzName)) return null;
29822999
}
2983-
try {
2984-
Constructor<?> constructor = quickConstructors.get(clazzName);
2985-
if (constructor == null) {
2986-
Class<?> runnableClass = Class.forName(clazzName);
2987-
if (runnableClass != null) {
2988-
// SimpleRPCRunnale should always has a default constructor
2989-
constructor = runnableClass.getConstructor(new Class[0]);
2990-
synchronized (quickConstructors) {
2991-
quickConstructors.put(clazzName, constructor);
2992-
}
2993-
}
2994-
}
2995-
if (constructor != null) {
2996-
Object obj = constructor.newInstance(new Object[0]);
2997-
if (obj != null && obj instanceof SimpleSerializable) {
2998-
return (SimpleSerializable) obj;
2999-
}
3000-
}
3001-
} catch (Exception e) {
3002-
e.printStackTrace();
3000+
Object inst = SimpleClassLoader.loadSimpleInstance(clazzName);
3001+
if (inst != null && inst instanceof SimpleSerializable) {
3002+
return (SimpleSerializable) inst;
30033003
}
30043004
return UNKNOWN;
30053005
}
3006+
3007+
@J2SIgnore
3008+
static void removeCachedClassFields(String clazzName) {
3009+
synchronized (mutex) {
3010+
// Will force update cached fields in next time retrieving fields
3011+
quickFields.remove(clazzName);
3012+
expiredClasses.add(clazzName);
3013+
}
3014+
}
3015+
30063016
}

0 commit comments

Comments
 (0)