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,然后生成实例,处理请求。
下面我附上相关核心代码:
- public abstract class CachedClassLoader extends ClassLoader {
- private final static Log logger = LogFactory.getLog(CachedClassLoader.class);
- protected HashMap
> 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
>(); - }
- /**
- * 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
loaderMap = null; - private static ClassLoaderFactory factory = new ClassLoaderFactory();
- public Properties conf = new Properties();
- private ClassLoaderFactory(){
- this.loaderMap = new ConcurrentHashMap
(); - 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: [email protected]
- * @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
getData() throws Exception { - logger.info("enter getData");
- CachedClassLoader loader = (CachedClassLoader)classLoaderFactory.getClassLoader(path, "BasicUserService");
- Class
userServiceClass = (Class )loader.getClass("BasicUserService"); - UserService userService = userServiceClass.newInstance();
- System.out.println(userService.getClass().getClassLoader());
- Map
model = userService.getUser(); - logger.info("exit getData");
- return model;
- }
- @RequestMapping("reload")
- @ResponseBody
- public Map
reload(String classname) throws Exception { - Map
model = new HashMap (); - 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: [email protected]
- */
- public class BasicUserService implements UserService {
- public Map
getUser() { - Map
model = new HashMap (); - model.put("username", "ooooooooooooo");
- return model;
- }
- }
测试:随意更改BasicUserService 的实现,然后调用reload。