转载

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

正文到此结束
Loading...