ClassLoader案例

n6xb 9年前

接自定义类加载器的理论,讲一个实践。

我们都有使用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;   }  }


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;    }   }  }


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!");    }   }     }


/**   *    * @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;   }  }


测试:随意更改BasicUserService 的实现,然后调用reload。

来自:http://wangxinchun.iteye.com/blog/2199498