Java 自定义类加载器实现插件式开发

http://blog.csdn.net/top_code/article/details/43052731

http://blog.csdn.net/u011037869/article/details/49492137


最近接触Solr比较多,感觉Solr提供的插件式开发方式很酷,Solr对开发者提供了一个核心api jar包,开发者如果想扩展Solr某一项功能 比如 中文分词,只需要继承Solr提供的分词接口添加自己的实现,然后把自己的分词jar包拷贝到Solr指定目录,并在solr配置文件中配置,重启即可生效。


本文会涉及到自定义类加载,所以先介绍一下java类加载器的原理和工作机制,熟悉的同学可以直接跳过。


java类加载器

类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。有三种默认使用的类加载器:Bootstrap类加载器、Extension类加载器和System类加载器(或者叫作Application类加载器)。每种类加载器所负责加载的类也各不相同。
JVM三种预定义类型类加载器

  1. Bootstrap ClassLoader : 将存放于<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar 名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用
  2. Extension ClassLoader : 将<JAVA_HOME>\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库加载。开发者可以直接使用扩展类加载器。
  3. Application ClassLoader : 负责加载用户类路径(ClassPath)上所指定的类库,开发者可直接使用。

类加载器的代理模式

类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个 Java 类 com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器 ClassLoaderA和 ClassLoaderB分别读取了这个 Sample.class文件,并定义出两个 java.lang.Class类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastException。


一句话总结就是:查找某个类的时候从下至上,加载某个类的时候从上至下。


自定义类加载器

因为我们需要加载指定路径下的jar文件,所以我们需要自定义类加载器来扫描指定路径下的jar包,代码如下:

[java]  view plain  copy
  1. package com.bytebeats.switcher.core;  
  2.   
  3. import com.bytebeats.switcher.util.IoUtils;  
  4. import org.slf4j.Logger;  
  5. import org.slf4j.LoggerFactory;  
  6. import java.io.File;  
  7. import java.io.FileFilter;  
  8. import java.net.MalformedURLException;  
  9. import java.net.URL;  
  10. import java.net.URLClassLoader;  
  11.   
  12. /** 
  13.  * 
  14.  * @author Ricky Fung 
  15.  * @create 2016-11-12 14:27 
  16.  */  
  17. public class PluginClassLoader {  
  18.   
  19.     private final Logger logger = LoggerFactory.getLogger(PluginClassLoader.class);  
  20.   
  21.     private URLClassLoader classLoader;  
  22.   
  23.     public PluginClassLoader(String jarfileDir){  
  24.         this(new File(jarfileDir), null);  
  25.     }  
  26.     public PluginClassLoader(File jarfileDir){  
  27.         this(jarfileDir, null);  
  28.     }  
  29.     public PluginClassLoader(File jarfileDir, ClassLoader parent) {  
  30.         this.classLoader = createClassLoader(jarfileDir, parent);  
  31.     }  
  32.   
  33.     public void addToClassLoader(final String baseDir, final FileFilter filter,  
  34.             boolean quiet) {  
  35.           
  36.         File base = new File(baseDir);  
  37.           
  38.         if (base != null && base.exists() && base.isDirectory()) {  
  39.             File[] files = base.listFiles(filter);  
  40.             if (files == null || files.length == 0) {  
  41.                 if (!quiet) {  
  42.                     logger.error("No files added to classloader from lib: "  
  43.                             + baseDir + " (resolved as: "  
  44.                             + base.getAbsolutePath() + ").");  
  45.                 }  
  46.             } else {  
  47.                 this.classLoader = replaceClassLoader(classLoader, base, filter);  
  48.             }  
  49.         } else {  
  50.             if (!quiet) {  
  51.                 logger.error("Can't find (or read) directory to add to classloader: "  
  52.                         + baseDir  
  53.                         + " (resolved as: "  
  54.                         + base.getAbsolutePath()  
  55.                         + ").");  
  56.             }  
  57.         }  
  58.     }  
  59.   
  60.     private URLClassLoader replaceClassLoader(final URLClassLoader oldLoader,  
  61.             final File base, final FileFilter filter) {  
  62.   
  63.         if (null != base && base.canRead() && base.isDirectory()) {  
  64.             File[] files = base.listFiles(filter);  
  65.   
  66.             if (null == files || 0 == files.length){  
  67.                 logger.error("replaceClassLoader base dir:{} is empty", base.getAbsolutePath());  
  68.                 return oldLoader;  
  69.             }  
  70.   
  71.             logger.error("replaceClassLoader base dir: {} ,size: {}", base.getAbsolutePath(), files.length);  
  72.               
  73.             URL[] oldElements = oldLoader.getURLs();  
  74.             URL[] elements = new URL[oldElements.length + files.length];  
  75.             System.arraycopy(oldElements, 0, elements, 0, oldElements.length);  
  76.   
  77.             for (int j = 0; j < files.length; j++) {  
  78.                 try {  
  79.                     URL element = files[j].toURI().normalize().toURL();  
  80.                     elements[oldElements.length + j] = element;  
  81.                       
  82.                     logger.info("Adding '{}' to classloader", element.toString());  
  83.                       
  84.                 } catch (MalformedURLException e) {  
  85.                     logger.error("load jar file error", e);  
  86.                 }  
  87.             }  
  88.             ClassLoader oldParent = oldLoader.getParent();  
  89.             IoUtils.closeQuietly(oldLoader); // best effort  
  90.             return URLClassLoader.newInstance(elements, oldParent);  
  91.         }  
  92.           
  93.         return oldLoader;  
  94.     }  
  95.   
  96.     private URLClassLoader createClassLoader(final File libDir,  
  97.             ClassLoader parent) {  
  98.         if (null == parent) {  
  99.             parent = Thread.currentThread().getContextClassLoader();  
  100.         }  
  101.         return replaceClassLoader(  
  102.                 URLClassLoader.newInstance(new URL[0], parent), libDir, null);  
  103.     }  
  104.       
  105.     public Class<?> loadClass(String className) throws ClassNotFoundException{  
  106.   
  107.         return classLoader.loadClass(className);  
  108.     }  
  109. }  


为了方便使用, 提供了PluginManager

[java]  view plain  copy
  1. package com.bytebeats.switcher.core;  
  2.   
  3. import com.bytebeats.switcher.util.StringUtils;  
  4. import java.io.File;  
  5.   
  6. /** 
  7.  * ${DESCRIPTION} 
  8.  * 
  9.  * @author Ricky Fung 
  10.  * @create 2016-11-12 14:35 
  11.  */  
  12. public class PluginManager {  
  13.     private volatile static PluginManager mgr;  
  14.     private PluginClassLoader pluginClassLoader;  
  15.     private volatile boolean init;  
  16.   
  17.     private PluginManager(){  
  18.   
  19.     }  
  20.   
  21.     public static PluginManager getMgr(){  
  22.         if(mgr==null){  
  23.             synchronized (PluginManager.class){  
  24.                 if(mgr==null){  
  25.                     mgr = new PluginManager();  
  26.                 }  
  27.             }  
  28.         }  
  29.         return mgr;  
  30.     }  
  31.   
  32.     public <T> T getPlugin(String className, Class<T> required){  
  33.         Class<?> cls = null;  
  34.         try {  
  35.             cls = pluginClassLoader.loadClass(className);  
  36.         } catch (ClassNotFoundException e) {  
  37.             throw new IllegalArgumentException("can not find class:"+className, e);  
  38.         }  
  39.         if(required.isAssignableFrom(cls)){  
  40.             try {  
  41.                 return (T) cls.newInstance();  
  42.             } catch (Exception e) {  
  43.                 throw new IllegalArgumentException("can not newInstance class:"+className, e);  
  44.             }  
  45.         }  
  46.         throw new IllegalArgumentException("class:"+className+" not sub class of "+required);  
  47.     }  
  48.   
  49.     public void addExternalJar(String basePath){  
  50.         if (StringUtils.isEmpty(basePath)) {  
  51.             throw new IllegalArgumentException("basePath can not be empty!");  
  52.         }  
  53.         File dir = new File(basePath);  
  54.         if(!dir.exists()){  
  55.             throw new IllegalArgumentException("basePath not exists:"+basePath);  
  56.         }  
  57.         if(!dir.isDirectory()){  
  58.             throw new IllegalArgumentException("basePath must be a directory:"+basePath);  
  59.         }  
  60.   
  61.         if(!init){  
  62.             init= true;  
  63.             pluginClassLoader = doInit(basePath);  
  64.         }else{  
  65.             pluginClassLoader.addToClassLoader(basePath, nulltrue);  
  66.         }  
  67.   
  68.     }  
  69.   
  70.     private synchronized PluginClassLoader doInit(String basePath){  
  71.         PluginClassLoader pluginClassLoader = new PluginClassLoader(basePath);  
  72.         return pluginClassLoader;  
  73.     }  
  74. }  


插件式开发示例

下面通过一个简单例子来演示如何实现插件式开发



example-api存放核心api接口,example-plugin是插件工程 提供example-api api接口实现,example-host是主程序 它在启动的时候会去加载plugin。


example-api中的api接口如下:

[java]  view plain  copy
  1. package com.bytebeats.switcher.example.api;  
  2.   
  3. /** 
  4.  * ${DESCRIPTION} 
  5.  * 
  6.  * @author Ricky Fung 
  7.  * @create 2016-11-12 15:30 
  8.  */  
  9. public interface IHelloService {  
  10.   
  11.     String hello(String msg);  
  12. }  



[java]  view plain  copy
  1. package com.bytebeats.switcher.example.api;  
  2.   
  3. import com.bytebeats.switcher.example.domain.User;  
  4.   
  5. import java.util.List;  
  6.   
  7. /** 
  8.  * ${DESCRIPTION} 
  9.  * 
  10.  * @author Ricky Fung 
  11.  * @create 2016-11-12 16:09 
  12.  */  
  13. public interface IUserService {  
  14.   
  15.     List<User> getUsers();  
  16.   
  17.     int update(User user);  
  18.   
  19. }  


example-plugin中的api接口实现如下:

[java]  view plain  copy
  1. package com.bytebeats.example.plugin;  
  2.   
  3. import com.bytebeats.switcher.example.api.IHelloService;  
  4.   
  5. /** 
  6.  * ${DESCRIPTION} 
  7.  * 
  8.  * @author Ricky Fung 
  9.  * @create 2016-11-12 16:13 
  10.  */  
  11. public class HelloServiceImpl implements IHelloService {  
  12.   
  13.     @Override  
  14.     public String hello(String msg) {  
  15.         System.out.println("hello [" + msg + "]");  
  16.         return "hello [" + msg + "]";  
  17.     }  
  18. }  

[java]  view plain  copy
  1. package com.bytebeats.example.plugin;  
  2.   
  3. import com.bytebeats.switcher.example.api.IUserService;  
  4. import com.bytebeats.switcher.example.domain.User;  
  5.   
  6. import java.util.ArrayList;  
  7. import java.util.List;  
  8.   
  9. /** 
  10.  * ${DESCRIPTION} 
  11.  * 
  12.  * @author Ricky Fung 
  13.  * @create 2016-11-12 16:14 
  14.  */  
  15. public class UserServiceImpl implements IUserService {  
  16.   
  17.     @Override  
  18.     public List<User> getUsers() {  
  19.         List<User> list = new ArrayList<>();  
  20.         list.add(new User("ricky""12345"));  
  21.         list.add(new User("kobe""aaa"));  
  22.         list.add(new User("jordan""root"));  
  23.         return list;  
  24.     }  
  25.   
  26.     @Override  
  27.     public int update(User user) {  
  28.         System.out.println("update user = [" + user + "]");  
  29.         user.setPassword("hello");  
  30.         return 1;  
  31.     }  
  32. }  



example-host中的启动类如下:

[java]  view plain  copy
  1. package com.bytebeats.example.host;  
  2.   
  3. import com.bytebeats.switcher.core.PluginManager;  
  4. import com.bytebeats.switcher.example.api.IHelloService;  
  5. import com.bytebeats.switcher.example.api.IUserService;  
  6. import com.bytebeats.switcher.example.domain.User;  
  7.   
  8. import java.util.List;  
  9.   
  10. /** 
  11.  * Hello world! 
  12.  * 
  13.  */  
  14. public class App {  
  15.   
  16.     public static void main( String[] args ) {  
  17.   
  18.         PluginManager pluginManager = PluginManager.getMgr();  
  19.         pluginManager.addExternalJar("D:\\osgi");  
  20.   
  21.         IHelloService helloService = pluginManager.getPlugin("com.bytebeats.example.plugin.HelloServiceImpl", IHelloService.class);  
  22.         helloService.hello("ricky");  
  23.   
  24.         IUserService userService = pluginManager.getPlugin("com.bytebeats.example.plugin.UserServiceImpl", IUserService.class);  
  25.         List<User> list = userService.getUsers();  
  26.         System.out.println("list = [" + list + "]");  
  27.   
  28.         userService.update(new User("test""test"));  
  29.     }  
  30. }  


注意:运行之前需要将example-plugin工程打成jar包,然后拷贝到任意路径下即可,本文是D:\osgi目录下。


代码下载

所有代码均已上传至Github:https://github.com/TiFG/cherry

猜你喜欢

转载自blog.csdn.net/u012506661/article/details/78576425