ClassLoader案例

n6xb 10年前發布 | 14K 次閱讀 Java開發 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() {
     Map<String, Object> model = new HashMap<String, Object>();
     model.put("username", "ooooooooooooo");
     return model;
    
    } }</pre>

    測試:隨意更改BasicUserService 的實現,然后調用reload。

    來自:http://wangxinchun.iteye.com/blog/2199498
 本文由用戶 n6xb 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!