java类加载器
-
类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。
-
有三种默认使用的类加载器:Bootstrap类加载器、Extension类加载器和System类加载器(或者叫作Application类加载器)。每种类加载器所负责加载的类也各不相同。
-
Bootstrap ClassLoader : 将存放于<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar 名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用
-
Extension ClassLoader : 将<JAVA_HOME>\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库加载。开发者可以直接使用扩展类加载器。
-
Application ClassLoader : 负责加载用户类路径(ClassPath)上所指定的类库,开发者可直接使用。
-
-
类加载器的代理模式
-
类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。
-
在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个 Java 类 com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器 ClassLoaderA和 ClassLoaderB分别读取了这个 Sample.class文件,并定义出两个 java.lang.Class类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastException。
-
一句话总结就是:查找某个类的时候从下至上,加载某个类的时候从上至下。
-
自定义类加载器
- 因为我们需要加载指定路径下的jar文件,所以我们需要自定义类加载器来扫描指定路径下的jar包,代码如下:
package com.bytebeats.switcher.core; import com.bytebeats.switcher.util.IoUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileFilter; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; /** * * @author Ricky Fung * @create 2016-11-12 14:27 */ public class PluginClassLoader { private final Logger logger = LoggerFactory.getLogger(PluginClassLoader.class); private URLClassLoader classLoader; public PluginClassLoader(String jarfileDir){ this(new File(jarfileDir), null); } public PluginClassLoader(File jarfileDir){ this(jarfileDir, null); } public PluginClassLoader(File jarfileDir, ClassLoader parent) { this.classLoader = createClassLoader(jarfileDir, parent); } public void addToClassLoader(final String baseDir, final FileFilter filter, boolean quiet) { File base = new File(baseDir); if (base != null && base.exists() && base.isDirectory()) { File[] files = base.listFiles(filter); if (files == null || files.length == 0) { if (!quiet) { logger.error("No files added to classloader from lib: " + baseDir + " (resolved as: " + base.getAbsolutePath() + ")."); } } else { this.classLoader = replaceClassLoader(classLoader, base, filter); } } else { if (!quiet) { logger.error("Can't find (or read) directory to add to classloader: " + baseDir + " (resolved as: " + base.getAbsolutePath() + ")."); } } } private URLClassLoader replaceClassLoader(final URLClassLoader oldLoader, final File base, final FileFilter filter) { if (null != base && base.canRead() && base.isDirectory()) { File[] files = base.listFiles(filter); if (null == files || 0 == files.length){ logger.error("replaceClassLoader base dir:{} is empty", base.getAbsolutePath()); return oldLoader; } logger.error("replaceClassLoader base dir: {} ,size: {}", base.getAbsolutePath(), files.length); URL[] oldElements = oldLoader.getURLs(); URL[] elements = new URL[oldElements.length + files.length]; System.arraycopy(oldElements, 0, elements, 0, oldElements.length); for (int j = 0; j < files.length; j++) { try { URL element = files[j].toURI().normalize().toURL(); elements[oldElements.length + j] = element; logger.info("Adding '{}' to classloader", element.toString()); } catch (MalformedURLException e) { logger.error("load jar file error", e); } } ClassLoader oldParent = oldLoader.getParent(); IoUtils.closeQuietly(oldLoader); // best effort return URLClassLoader.newInstance(elements, oldParent); } return oldLoader; } private URLClassLoader createClassLoader(final File libDir, ClassLoader parent) { if (null == parent) { parent = Thread.currentThread().getContextClassLoader(); } return replaceClassLoader( URLClassLoader.newInstance(new URL[0], parent), libDir, null); } public Class<?> loadClass(String className) throws ClassNotFoundException{ return classLoader.loadClass(className); } }
- 为了方便使用, 提供了PluginManager
package com.bytebeats.switcher.core; import com.bytebeats.switcher.util.StringUtils; import java.io.File; /** * ${DESCRIPTION} * * @author Ricky Fung * @create 2016-11-12 14:35 */ public class PluginManager { private volatile static PluginManager mgr; private PluginClassLoader pluginClassLoader; private volatile boolean init; private PluginManager(){ } public static PluginManager getMgr(){ if(mgr==null){ synchronized (PluginManager.class){ if(mgr==null){ mgr = new PluginManager(); } } } return mgr; } public <T> T getPlugin(String className, Class<T> required){ Class<?> cls = null; try { cls = pluginClassLoader.loadClass(className); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("can not find class:"+className, e); } if(required.isAssignableFrom(cls)){ try { return (T) cls.newInstance(); } catch (Exception e) { throw new IllegalArgumentException("can not newInstance class:"+className, e); } } throw new IllegalArgumentException("class:"+className+" not sub class of "+required); } public void addExternalJar(String basePath){ if (StringUtils.isEmpty(basePath)) { throw new IllegalArgumentException("basePath can not be empty!"); } File dir = new File(basePath); if(!dir.exists()){ throw new IllegalArgumentException("basePath not exists:"+basePath); } if(!dir.isDirectory()){ throw new IllegalArgumentException("basePath must be a directory:"+basePath); } if(!init){ init= true; pluginClassLoader = doInit(basePath); }else{ pluginClassLoader.addToClassLoader(basePath, null, true); } } private synchronized PluginClassLoader doInit(String basePath){ PluginClassLoader pluginClassLoader = new PluginClassLoader(basePath); return pluginClassLoader; } }
- 插件式开发示例
example-api存放核心api接口,example-plugin是插件工程 提供example-api api接口实现,example-host是主程序 它在启动的时候会去加载plugin。
- example-api中的api接口如下:
package com.bytebeats.switcher.example.api; /** * ${DESCRIPTION} * * @author Ricky Fung * @create 2016-11-12 15:30 */ public interface IHelloService { String hello(String msg); }
package com.bytebeats.switcher.example.api; import com.bytebeats.switcher.example.domain.User; import java.util.List; /** * ${DESCRIPTION} * * @author Ricky Fung * @create 2016-11-12 16:09 */ public interface IUserService { List<User> getUsers(); int update(User user); }
- client
package com.bytebeats.example.plugin; import com.bytebeats.switcher.example.api.IHelloService; /** * ${DESCRIPTION} * * @author Ricky Fung * @create 2016-11-12 16:13 */ public class HelloServiceImpl implements IHelloService { @Override public String hello(String msg) { System.out.println("hello [" + msg + "]"); return "hello [" + msg + "]"; } }
package com.bytebeats.example.plugin; import com.bytebeats.switcher.example.api.IUserService; import com.bytebeats.switcher.example.domain.User; import java.util.ArrayList; import java.util.List; /** * ${DESCRIPTION} * * @author Ricky Fung * @create 2016-11-12 16:14 */ public class UserServiceImpl implements IUserService { @Override public List<User> getUsers() { List<User> list = new ArrayList<>(); list.add(new User("ricky", "12345")); list.add(new User("kobe", "aaa")); list.add(new User("jordan", "root")); return list; } @Override public int update(User user) { System.out.println("update user = [" + user + "]"); user.setPassword("hello"); return 1; } }
- example-host中的启动类如下:
注意:运行之前需要将example-plugin工程打成jar包,然后拷贝到任意路径下即可,本文是D:\osgi目录下。package com.bytebeats.example.host; import com.bytebeats.switcher.core.PluginManager; import com.bytebeats.switcher.example.api.IHelloService; import com.bytebeats.switcher.example.api.IUserService; import com.bytebeats.switcher.example.domain.User; import java.util.List; /** * Hello world! * */ public class App { public static void main( String[] args ) { PluginManager pluginManager = PluginManager.getMgr(); pluginManager.addExternalJar("D:\\osgi"); IHelloService helloService = pluginManager.getPlugin("com.bytebeats.example.plugin.HelloServiceImpl", IHelloService.class); helloService.hello("ricky"); IUserService userService = pluginManager.getPlugin("com.bytebeats.example.plugin.UserServiceImpl", IUserService.class); List<User> list = userService.getUsers(); System.out.println("list = [" + list + "]"); userService.update(new User("test", "test")); } }
- GitHub: https://github.com/TiFG/cherry