ClassLoader-热替换

阅读更多

https://www.ibm.com/developerworks/cn/java/l-multithreading/

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

我们都有使用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,然后生成实例,处理请求。

下面我附上相关核心代码:

Java代码 
  1. public abstract class CachedClassLoader extends ClassLoader {  
  2.   
  3.     private final static Log logger = LogFactory.getLog(CachedClassLoader.class);  
  4.     protected HashMap> cache = null;  
  5.     protected String classname;  
  6.     protected String path;  
  7.   
  8.     public String getPath() {  
  9.         return path;  
  10.     }  
  11.   
  12.     public CachedClassLoader(String path, String classname) {  
  13.         super();  
  14.         this.classname = classname;  
  15.         this.path = path;  
  16.         this.cache = new HashMap>();  
  17.     }  
  18.   
  19.     /** 
  20.      * Loads the class with the specified name. 
  21.      * @param name: classname. 
  22.      */  
  23.     public synchronized Class loadClass(String classname, boolean resolve) {  
  24.         if (this.cache.containsKey(classname)) {  
  25.             logger.debug("load Class:" + classname + " from cache.");  
  26.             Class c =  this.cache.get(classname);  
  27.             if (resolve)  
  28.                 resolveClass(c);  
  29.             return c;  
  30.         } else {  
  31.             try {  
  32.                 Class c = Class.forName(classname);  
  33.                 return c;  
  34.             }  
  35.             catch (ClassNotFoundException e) {  
  36.                 Class c = this.newClass(classname);  
  37.                 if (c == null)  
  38.                     return null;  
  39.                 this.cache.put(classname, c);  
  40.                 if (resolve)  
  41.                     resolveClass(c);  
  42.                 return c;  
  43.             }  
  44.             catch (NoClassDefFoundError e) {  
  45.                 Class c = this.newClass(classname);  
  46.                 if (c == null)  
  47.                     return null;  
  48.                 this.cache.put(classname, c);  
  49.                 if (resolve)  
  50.                     resolveClass(c);  
  51.                 return c;  
  52.             }  
  53.         }  
  54.     }  
  55.       
  56.     public synchronized Class getClass(String classname){  
  57.         return this.cache.get(classname);  
  58.     }  
  59.       
  60.     /** 
  61.      * @return java.lang.Class 
  62.      * @param name 
  63.      * @param resolve 
  64.      */  
  65.     public synchronized Class loadClass(boolean resolve) {  
  66.         return this.loadClass(this.classname, resolve);  
  67.     }  
  68.   
  69.     /** 
  70.      * Abstract method for create new class object. 
  71.      * @param classname 
  72.      * @return 
  73.      */  
  74.     abstract Class newClass(String classname);  
  75.   
  76.     public String getClassname() {  
  77.         return classname;  
  78.     }  
  79.   
  80.     public void setClassname(String classname) {  
  81.         this.classname = classname;  
  82.     }  
  83. }  



Java代码 
  1. public class FileClassLoader extends CachedClassLoader{   
  2.     private static Log logger =LogFactory.getLog(FileClassLoader.class);  
  3.     public String CLASSPATH_ROOT=TClassLoaderFactory.getFactory().getPropertyValue(TClassLoaderFactory.FILEROOT_PATH);  
  4.       
  5.     public FileClassLoader (String path,String classname) {  
  6.         super(path, classname);  
  7.     }  
  8.           
  9.     /** 
  10.      * Implements CachedClassLoader.newClass method. 
  11.      * @param classname 
  12.      */  
  13.    protected  Class newClass(String classname) {  
  14.         String fullpath = CLASSPATH_ROOT+File.separator+this.path+File.separator+classname + ".class";  
  15.         logger.debug("loading remote class " + classname + " from "+ fullpath);  
  16.         byte data[] = loadClassData(fullpath);  
  17.         if (data == null) {  
  18.             logger.debug("Class data is null");  
  19.             return null;  
  20.         }  
  21.         logger.debug("defining class " + classname);  
  22.         try {  
  23.             return super.defineClass(this.path.replaceAll("\\\\", ".")+"."+classname, data, 0, data.length);  
  24.         } catch (Exception e) {  
  25.             logger.error("Init class exception",e);  
  26.         }  
  27.         return null;  
  28.     }  
  29.   
  30.     /** 
  31.      * Read class as a byts array. 
  32.      * @return byte[] 
  33.      * @param name 
  34.      */  
  35.     private byte[] loadClassData(String urlString) {  
  36.         logger.debug("loadClassData by:"+urlString);  
  37.         try {             
  38.             //return byteOutput.toByteArray();  
  39.             FileInputStream in =new FileInputStream(urlString);           
  40.             ByteArrayOutputStream out = new ByteArrayOutputStream();  
  41.             FileChannel channel =in.getChannel();              
  42.             WritableByteChannel outchannel = Channels.newChannel(out);   
  43.             ByteBuffer buffer = ByteBuffer.allocateDirect(1024);   
  44.             while (true) {   
  45.                 int i = channel.read(buffer);   
  46.                 if (i == 0 || i == -1) {   
  47.                     break;   
  48.                 }   
  49.                 buffer.flip();   
  50.                 outchannel.write(buffer);   
  51.                 buffer.clear();   
  52.             }               
  53.             byte[] bytes =out.toByteArray();  
  54.             out.close();  
  55.             in.close();  
  56.             return bytes;  
  57.         } catch (IOException ie) {  
  58.             logger.error("read local file exception "+urlString, ie);  
  59.         }  
  60.         return null;  
  61.     }  
  62.       
  63.     /** 
  64.      * Load spec file from FileClassLoader's  rootpath. 
  65.      * @param name resource's name. 
  66.      */  
  67.     public InputStream getResourceAsStream(String name) {  
  68.         String fullpath = CLASSPATH_ROOT+File.separator+this.path+File.separator+name;  
  69.         logger.debug("load resource from:"+fullpath);  
  70.         try {  
  71.             return new FileInputStream(fullpath);  
  72.         }  
  73.         catch(FileNotFoundException fe) {  
  74.             logger.error("spec:"+fullpath,fe);  
  75.             return null;  
  76.         }  
  77.     }  
  78. }  



Java代码 
  1. public class ClassLoaderFactory {  
  2.       
  3.     private static Log logger =LogFactory.getLog(ClassLoaderFactory.class);  
  4.       
  5.     public static final String  LOADER_NAME = "classloader.name";  
  6.     public static final String  NETROOT_URL = "classloader.NetworkClassLoader";  
  7.     public static final String  FILEROOT_PATH = "classloader.FileClassLoader";  
  8.     public static final String PREVIOUS_ON_FILE="classloader.FileClassLoader.PRELOAD";  
  9.       
  10.     private Map loaderMap = null;  
  11.       
  12.     private static ClassLoaderFactory factory = new ClassLoaderFactory();  
  13.       
  14.     public Properties conf = new Properties();  
  15.       
  16.     private ClassLoaderFactory(){         
  17.         this.loaderMap = new ConcurrentHashMap();  
  18.         InputStream in = FileClassLoader.class.getResourceAsStream("/*****.properties");  
  19.         try {  
  20.             conf.load(in);  
  21.             logger.debug("ClassLoaderFactory init:"+LOADER_NAME +this.conf.getProperty(LOADER_NAME) );  
  22.             logger.debug("ClassLoaderFactory init:"+NETROOT_URL + this.conf.getProperty(NETROOT_URL) );  
  23.             logger.debug("ClassLoaderFactory init:"+FILEROOT_PATH  + this.conf.getProperty(FILEROOT_PATH));  
  24.         }  
  25.         catch(IOException ie) {  
  26.             logger.error("Init classpath exception",ie);  
  27.         }  
  28.     }  
  29.       
  30.     /**  
  31.      * Implements factory pattern.  
  32.      * @return  
  33.      */  
  34.     public static ClassLoaderFactory getFactory() {  
  35.         return factory;  
  36.     }  
  37.       
  38.     protected String getPropertyValue(String propertyName) {  
  39.         return this.conf.getProperty(propertyName);  
  40.     }  
  41.     /** 
  42.      * Create new classloader object for this wrapper. default classloader is FileClassLoader. 
  43.      * @param key 
  44.      * @param classname 
  45.      * @return 
  46.      */  
  47.     public ClassLoader getClassLoader(String key,String classname) {  
  48.         long startTime = System.currentTimeMillis();  
  49.         String loaderKey = key;  
  50.         if(!this.loaderMap.containsKey(loaderKey)){  
  51.             synchronized(this.loaderMap) {  
  52.                 if(this.loaderMap.containsKey(loaderKey)){  
  53.                     return (ClassLoader)this.loaderMap.get(loaderKey);  
  54.                 }  
  55.                 try {  
  56.                     Class cl = Class.forName(this.conf.getProperty(LOADER_NAME));  
  57.                     Class[] params = {String.class,String.class};  
  58.                     Constructor constructor = cl.getConstructor(params);  
  59.                     String[] args = {key,classname};  
  60.                     logger.info("create new ClassLoader for:"+key+" classname:"+classname+" consume:"+(System.currentTimeMillis()-startTime)+" (ms)");  
  61.                     this.loaderMap.put(loaderKey, (ClassLoader)constructor.newInstance(args));  
  62.                 }  
  63.                 catch(ClassNotFoundException cne) {  
  64.                     logger.error("init classloader failed. system occure fetal error.!!!"+key+" codename:"+classname, cne);  
  65.                 }  
  66.                 catch(NoSuchMethodException nme) {  
  67.                     logger.error("key:"+key+" classname:"+classname+ "get classloader failed.",nme);  
  68.                 }  
  69.                 catch(Exception e){  
  70.                     logger.error("key:"+key+" classname:"+classname+ "get classloader failed.",e);  
  71.                 }  
  72.             }  
  73.         }else {  
  74.             //(ClassLoader)this.loaderMap.get(loaderKey);  
  75.             CachedClassLoader loader =(CachedClassLoader)this.loaderMap.get(loaderKey);  
  76.             loader.setClassname(classname);           
  77.             logger.debug("retrieve classloader from cache map, key:"+key+" classname:"+classname);  
  78.         }  
  79.         return (ClassLoader)this.loaderMap.get(loaderKey);  
  80.     }  
  81.       
  82.     public void reload(String key){  
  83.         if(loaderMap.containsKey(key)){  
  84.             synchronized(this.loaderMap) {  
  85.                 loaderMap.remove(key);  
  86.                 logger.info("Wrapper classes for key:"+key+ " were removed!");  
  87.             }  
  88.         }  
  89.     }  
  90.     public void reloadAll() {  
  91.         synchronized (this.loaderMap) {  
  92.             loaderMap.clear();  
  93.             logger.info("Wrapper classes for all key were removed!");  
  94.         }  
  95.     }  
  96.       
  97. }  



Java代码 
  1. /** 
  2.  *  
  3.  * @author xinchun.wang  
  4.    @email: [email protected] 
  5.  * @createTime 2015-4-4 下午9:54:12 
  6.  */  
  7. @Controller  
  8. @RequestMapping("test")  
  9. public class TestController {  
  10.     private final Logger logger = LoggerFactory.getLogger(getClass());  
  11.   
  12.     private ClassLoaderFactory classLoaderFactory = TClassLoaderFactory.getFactory();  
  13.     private static final String path = "com"+File.separator+"gym"+File.separator+"backadmin"+File.separator+"service"+File.separator+"user";  
  14.       
  15.     @SuppressWarnings("unchecked")  
  16.     @RequestMapping("getData")  
  17.     @ResponseBody  
  18.     public Map getData() throws Exception {  
  19.         logger.info("enter getData");  
  20.         CachedClassLoader loader = (CachedClassLoader)classLoaderFactory.getClassLoader(path, "BasicUserService");  
  21.         Class userServiceClass = (Class)loader.getClass("BasicUserService");  
  22.         UserService userService = userServiceClass.newInstance();  
  23.         System.out.println(userService.getClass().getClassLoader());  
  24.         Map model = userService.getUser();  
  25.         logger.info("exit getData");  
  26.         return model;  
  27.     }  
  28.       
  29.       
  30.       
  31.     @RequestMapping("reload")  
  32.     @ResponseBody  
  33.     public Map reload(String classname) throws Exception {  
  34.         Map model = new HashMap();  
  35.         try{  
  36.             classLoaderFactory.reload(path);  
  37.             CachedClassLoader loader = (CachedClassLoader)classLoaderFactory.getClassLoader(path, "BasicUserService");  
  38.             loader.loadClass(classname);  
  39.             model.put("ret""success");  
  40.         }catch(Exception e){  
  41.             logger.error("",e);  
  42.             model.put("ret", e);  
  43.         }  
  44.         return model;  
  45.     }     
  46. }  
  47.   
  48. /** 
  49.  *  
  50.  * @author xinchun.wang  
  51.    @email: [email protected] 
  52.  */  
  53. public class BasicUserService implements UserService {  
  54.     public Map getUser() {  
  55.         Map model = new HashMap();  
  56.         model.put("username""ooooooooooooo");  
  57.         return model;  
  58.     }  
  59. }  



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

你可能感兴趣的:(CassLoader,热部署)