ClassLoader案例
接自定義類加載器的理論,講一個實踐。
我們都有使用jsp的經驗,為什么jsp可以修改后直接生效?就是ClassLoader在起作用,一個jsp對應一個ClassLoader,一旦jsp修改,就需要卸載原來加載此jsp(先是被轉換為java文件,然后被編譯為class文件)的ClassLoader ,然后重新生成一個ClassLoader來加載jsp對應的class文件。
最近參與到了一個抓取垂直網站報價數據的設計,由于抓取的網站有上千之多,并且每天都有大量的網站更新,導致原來的java解析代碼必須修改以適應原來的功能。
數據抓取有兩步:
1、抓取數據頁面(html,或者json串)
2、解析數據
這樣我們的系統,對每一要抓取的個網站建一個類,根據條件調用不同的類對象,問題來了:如果修改其中一個網站的類,如何生效? 重新啟動tomcat當然是可以的,不過代價過高,也不可取。
想必大家想到了jsp和tomcat交互的方式:通過對每一個類建立一個ClassLoader對象,如果某個類更新了,上傳class文件到特定目錄下,重新加載一個ClassLoader對象,由新的ClassLoader來加載class,然后生成實例,處理請求。
下面我附上相關核心代碼:
public abstract class CachedClassLoader extends ClassLoader {
private final static Log logger = LogFactory.getLog(CachedClassLoader.class);
protected HashMap<String,Class<?>> cache = null;
protected String classname;
protected String path;
public String getPath() {
return path;
}
public CachedClassLoader(String path, String classname) {
super();
this.classname = classname;
this.path = path;
this.cache = new HashMap<String,Class<?>>();
}
/**
* Loads the class with the specified name.
* @param name: classname.
*/
public synchronized Class<?> loadClass(String classname, boolean resolve) {
if (this.cache.containsKey(classname)) {
logger.debug("load Class:" + classname + " from cache.");
Class<?> c = this.cache.get(classname);
if (resolve)
resolveClass(c);
return c;
} else {
try {
Class<?> c = Class.forName(classname);
return c;
}
catch (ClassNotFoundException e) {
Class<?> c = this.newClass(classname);
if (c == null)
return null;
this.cache.put(classname, c);
if (resolve)
resolveClass(c);
return c;
}
catch (NoClassDefFoundError e) {
Class<?> c = this.newClass(classname);
if (c == null)
return null;
this.cache.put(classname, c);
if (resolve)
resolveClass(c);
return c;
}
}
}
public synchronized Class<?> getClass(String classname){
return this.cache.get(classname);
}
/**
* @return java.lang.Class
* @param name
* @param resolve
*/
public synchronized Class<?> loadClass(boolean resolve) {
return this.loadClass(this.classname, resolve);
}
/**
* Abstract method for create new class object.
* @param classname
* @return
*/
abstract Class<?> newClass(String classname);
public String getClassname() {
return classname;
}
public void setClassname(String classname) {
this.classname = classname;
}
}</pre>
public class FileClassLoader extends CachedClassLoader{
private static Log logger =LogFactory.getLog(FileClassLoader.class);
public String CLASSPATH_ROOT=TClassLoaderFactory.getFactory().getPropertyValue(TClassLoaderFactory.FILEROOT_PATH);
public FileClassLoader (String path,String classname) {
super(path, classname);
}
/**
* Implements CachedClassLoader.newClass method.
* @param classname
*/
protected Class<?> newClass(String classname) {
String fullpath = CLASSPATH_ROOT+File.separator+this.path+File.separator+classname + ".class";
logger.debug("loading remote class " + classname + " from "+ fullpath);
byte data[] = loadClassData(fullpath);
if (data == null) {
logger.debug("Class data is null");
return null;
}
logger.debug("defining class " + classname);
try {
return super.defineClass(this.path.replaceAll("\\", ".")+"."+classname, data, 0, data.length);
} catch (Exception e) {
logger.error("Init class exception",e);
}
return null;
}
/**
* Read class as a byts array.
* @return byte[]
* @param name
*/
private byte[] loadClassData(String urlString) {
logger.debug("loadClassData by:"+urlString);
try {
//return byteOutput.toByteArray();
FileInputStream in =new FileInputStream(urlString);
ByteArrayOutputStream out = new ByteArrayOutputStream();
FileChannel channel =in.getChannel();
WritableByteChannel outchannel = Channels.newChannel(out);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = channel.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outchannel.write(buffer);
buffer.clear();
}
byte[] bytes =out.toByteArray();
out.close();
in.close();
return bytes;
} catch (IOException ie) {
logger.error("read local file exception "+urlString, ie);
}
return null;
}
/**
* Load spec file from FileClassLoader's rootpath.
* @param name resource's name.
*/
public InputStream getResourceAsStream(String name) {
String fullpath = CLASSPATH_ROOT+File.separator+this.path+File.separator+name;
logger.debug("load resource from:"+fullpath);
try {
return new FileInputStream(fullpath);
}
catch(FileNotFoundException fe) {
logger.error("spec:"+fullpath,fe);
return null;
}
}
}</pre>
public class ClassLoaderFactory {
private static Log logger =LogFactory.getLog(ClassLoaderFactory.class);
public static final String LOADER_NAME = "classloader.name";
public static final String NETROOT_URL = "classloader.NetworkClassLoader";
public static final String FILEROOT_PATH = "classloader.FileClassLoader";
public static final String PREVIOUS_ON_FILE="classloader.FileClassLoader.PRELOAD";
private Map<String,ClassLoader> loaderMap = null;
private static ClassLoaderFactory factory = new ClassLoaderFactory();
public Properties conf = new Properties();
private ClassLoaderFactory(){
this.loaderMap = new ConcurrentHashMap<String,ClassLoader>();
InputStream in = FileClassLoader.class.getResourceAsStream("/*****.properties");
try {
conf.load(in);
logger.debug("ClassLoaderFactory init:"+LOADER_NAME +this.conf.getProperty(LOADER_NAME) );
logger.debug("ClassLoaderFactory init:"+NETROOT_URL + this.conf.getProperty(NETROOT_URL) );
logger.debug("ClassLoaderFactory init:"+FILEROOT_PATH + this.conf.getProperty(FILEROOT_PATH));
}
catch(IOException ie) {
logger.error("Init classpath exception",ie);
}
}
/**
* Implements factory pattern.
* @return
*/
public static ClassLoaderFactory getFactory() {
return factory;
}
protected String getPropertyValue(String propertyName) {
return this.conf.getProperty(propertyName);
}
/**
* Create new classloader object for this wrapper. default classloader is FileClassLoader.
* @param key
* @param classname
* @return
*/
public ClassLoader getClassLoader(String key,String classname) {
long startTime = System.currentTimeMillis();
String loaderKey = key;
if(!this.loaderMap.containsKey(loaderKey)){
synchronized(this.loaderMap) {
if(this.loaderMap.containsKey(loaderKey)){
return (ClassLoader)this.loaderMap.get(loaderKey);
}
try {
Class<?> cl = Class.forName(this.conf.getProperty(LOADER_NAME));
Class<?>[] params = {String.class,String.class};
Constructor<?> constructor = cl.getConstructor(params);
String[] args = {key,classname};
logger.info("create new ClassLoader for:"+key+" classname:"+classname+" consume:"+(System.currentTimeMillis()-startTime)+" (ms)");
this.loaderMap.put(loaderKey, (ClassLoader)constructor.newInstance(args));
}
catch(ClassNotFoundException cne) {
logger.error("init classloader failed. system occure fetal error.!!!"+key+" codename:"+classname, cne);
}
catch(NoSuchMethodException nme) {
logger.error("key:"+key+" classname:"+classname+ "get classloader failed.",nme);
}
catch(Exception e){
logger.error("key:"+key+" classname:"+classname+ "get classloader failed.",e);
}
}
}else {
//(ClassLoader)this.loaderMap.get(loaderKey);
CachedClassLoader loader =(CachedClassLoader)this.loaderMap.get(loaderKey);
loader.setClassname(classname);
logger.debug("retrieve classloader from cache map, key:"+key+" classname:"+classname);
}
return (ClassLoader)this.loaderMap.get(loaderKey);
}
public void reload(String key){
if(loaderMap.containsKey(key)){
synchronized(this.loaderMap) {
loaderMap.remove(key);
logger.info("Wrapper classes for key:"+key+ " were removed!");
}
}
}
public void reloadAll() {
synchronized (this.loaderMap) {
loaderMap.clear();
logger.info("Wrapper classes for all key were removed!");
}
}
}</pre>
/**
- @author xinchun.wang @email: 532002108@qq.com
@createTime 2015-4-4 下午9:54:12 */ @Controller @RequestMapping("test") public class TestController { private final Logger logger = LoggerFactory.getLogger(getClass());
private ClassLoaderFactory classLoaderFactory = TClassLoaderFactory.getFactory(); private static final String path = "com"+File.separator+"gym"+File.separator+"backadmin"+File.separator+"service"+File.separator+"user";
@SuppressWarnings("unchecked") @RequestMapping("getData") @ResponseBody public Map<String, Object> getData() throws Exception {
logger.info("enter getData"); CachedClassLoader loader = (CachedClassLoader)classLoaderFactory.getClassLoader(path, "BasicUserService"); Class<UserService> userServiceClass = (Class<UserService>)loader.getClass("BasicUserService"); UserService userService = userServiceClass.newInstance(); System.out.println(userService.getClass().getClassLoader()); Map<String, Object> model = userService.getUser(); logger.info("exit getData"); return model;}
@RequestMapping("reload")
@ResponseBody
public Map<String, Object> reload(String classname) throws Exception {
Map<String, Object> model = new HashMap<String,Object>();
try{
classLoaderFactory.reload(path);
CachedClassLoader loader = (CachedClassLoader)classLoaderFactory.getClassLoader(path, "BasicUserService");
loader.loadClass(classname);
model.put("ret", "success");
}catch(Exception e){
logger.error("",e);
model.put("ret", e);
}
return model;
}
}
/**
- @author xinchun.wang
@email: 532002108@qq.com
*/
public class BasicUserService implements UserService {
public Map<String, Object> getUser() {
} }</pre>Map<String, Object> model = new HashMap<String, Object>(); model.put("username", "ooooooooooooo"); return model;
測試:隨意更改BasicUserService 的實現,然后調用reload。
來自:http://wangxinchun.iteye.com/blog/2199498