类加载器简介
Java虚拟机中的类加载器(ClassLoader)负责加载来自文件系统、网络或其他来源的类文件。Java虚拟机中的类加载器默认使用的是双亲委派模式,如图所示,其中有三种默认使用的类加载器,分别是Bootstrap ClassLoader、Extension ClassLoader和System ClassLoader(也被称为Application ClassLoader),每种类加载器都已经确定从哪个位置加载类文件。
Bootstrap ClassLoader: 加载JDK自带的rt.jar包中的中的类文件,它是所有类加载器的父加载器
Extension ClassLoader: 负责加载Java的扩展类库,从jre/lib/ext目录下或者java.ext.dirs系统属性指定的目录下加载类。
System ClassLoader: 负责从classpath环境变量中加载类文件,它是Extension ClassLoader的子加载器。
根据双亲委派模式,在加载类文件时,子加载器首先会将加载请求委托给它的父加载器。父加载器会检测自己是否已经加载过该类,如果已加载则加载过程结束;如果未加载则请求继续向上传递,直到Bootstrap ClassLoader。如果在请求向上委托的过程中,始终未检测到该类已加载,则从Bootstrap ClassLoader开始尝试从其对应路径中加载该类文件,如果加载失败则由子加载器继续尝试加载,直至发起加载请求的子加载器位为止。
除了系统提供三种类加载器,也可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求,例如Tomcat、JBoss等都涉及了自定义类加载器的使用。
ClassLoaderWrapper
在Mybatis IO包中提供的ClassLoaderWrapper是一个ClassLoader的包装器,其中包含了多个ClassLoader对象。通过调整多个类加载器的使用顺序,ClassLoaderWrapper可以确保返回给系统使用的是正确的类加载器。使用ClassLoaderWrapper就如同使用一个ClassLoader对象,ClassLoaderWrapper会按照指定的顺序依次检测其中封装的ClassLoader对象,并从中选取第一个可用的ClassLoader完成相关功能。
ClassLoaderWrapper的主要功能可以分为三类,分别是getResourceAsURL()方法、classForName()方法、getResourceAsStream()方法,这三个方法都有多个重载,这三类方法最终都会调用参数为String和ClassLoader[]的重载。
/** * 该类封装了多个类加载器,统一了使用入口 * * @author kaifeng * @author Clinton Begin */public class ClassLoaderWrapper { //默认类加载器 ClassLoader defaultClassLoader; //系统类加载器 ClassLoader systemClassLoader; /** * 无参构造函数 */ ClassLoaderWrapper() { try { //初始化系统类加载器 systemClassLoader = ClassLoader.getSystemClassLoader(); } catch (SecurityException ignored) { // AccessControlException on Google App Engine } } //region getResourceAsURL 方法重载 /** * 使用当前类路径获取资源作为URL * * @param resource 要定位的资源 * @return 返回URL或null */ public URL getResourceAsURL(String resource) { return getResourceAsURL(resource, getClassLoaders(null)); } /** * Get a resource from the classpath, starting with a specific class loader * * @param resource - 要定位的资源 * @param classLoader - 指定的类加载器 * @return 返回URL或null */ public URL getResourceAsURL(String resource, ClassLoader classLoader) { return getResourceAsURL(resource, getClassLoaders(classLoader)); } //endregion //region getResourceAsStream 方法重载 /** * 从指定路径获取资源 * * @param resource - 要定位的资源 * @return 返回流格式资源或null */ public InputStream getResourceAsStream(String resource) { return getResourceAsStream(resource, getClassLoaders(null)); } /** * 根据指定路径和类加载器查找资源 * * @param resource - 要定位的资源 * @param classLoader - 指定的类加载器 * @return 返回流格式资源或null */ public InputStream getResourceAsStream(String resource, ClassLoader classLoader) { return getResourceAsStream(resource, getClassLoaders(classLoader)); } //endregion //region classForName 方法重载 /** * 根据类名查找类对象 * * @param name - 要查找的类对象 * @return - 返回找到的类对象 * @throws ClassNotFoundException 不存在抛出异常 */ public Class classForName(String name) throws ClassNotFoundException { return classForName(name, getClassLoaders(null)); } /** * 根据指定的类名,优先使用指定的类加载器查找类对象 * * @param name - 要查找的类对象 * @param classLoader - 优先使用指定的类加载器 * @return - 返回找到的类对象 * @throws ClassNotFoundException 不存在抛出异常 */ public Class classForName(String name, ClassLoader classLoader) throws ClassNotFoundException { return classForName(name, getClassLoaders(classLoader)); } //endregion /** * 根据指定的资源路径,尝试使用各种类加载器查找资源 * * @param resource - 要查找的资源 * @param classLoader - 类加载器数组 * @return 返回找到的资源或null */ InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) { for (ClassLoader cl : classLoader) { if (null != cl) { // 通过使用不同的类加载器查找资源 InputStream returnValue = cl.getResourceAsStream(resource); // 如果没有找到资源,通过在指定路径前面加上“/”,重新尝试一次 if (null == returnValue) { returnValue = cl.getResourceAsStream("/" + resource); } // 找到资源并返回 if (null != returnValue) { return returnValue; } } } return null; } /** * 根据指定的资源路径,尝试使用多种类加载器查找资源 * * @param resource - 指定的资源路径 * @param classLoader - 多种类加载器组成的数组 * @return 返回找到的资源或null */ URL getResourceAsURL(String resource, ClassLoader[] classLoader) { URL url; for (ClassLoader cl : classLoader) { if (null != cl) { // 通过使用不同的类加载器查找资源 url = cl.getResource(resource); // 如果没有找到资源,通过在指定路径前面加上“/”,重新尝试一次 if (null == url) { url = cl.getResource("/" + resource); } // 找到资源并返回 if (null != url) { return url; } } } // 没找到返回null return null; } /** * 根据指定的类名,尝试使用各种类加载器查找类对象 * * @param name - 要定位的类名 * @param classLoader - 类加载器组成的数组 * @return the class * @throws ClassNotFoundException - 抛出找不到异常 */ Class classForName(String name, ClassLoader[] classLoader) throws ClassNotFoundException { for (ClassLoader cl : classLoader) { if (null != cl) { try { Class c = Class.forName(name, true, cl); if (null != c) { return c; } } catch (ClassNotFoundException e) { // we'll ignore this until all classloaders fail to locate the class } } } throw new ClassNotFoundException("Cannot find class: " + name); } /** * 构造类加载器数组,指定了类加载器的使用顺序 ** classLoader 指定的类加载器 * defaultClassLoader 指定的默认类加载器 * Thread.currentThread().getContextClassLoader() 当前线程绑定的类加载器 * getClass().getClassLoader() 加载当前类所使用的类加载器 * systemClassLoader 系统类加载器 *
* * @param classLoader 类加载器 */ ClassLoader[] getClassLoaders(ClassLoader classLoader) { return new ClassLoader[]{ classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(), getClass().getClassLoader(), systemClassLoader}; }}
Resources是一个提供了多个静态方法的工具类,其中封装了一个ClassLoaderWrapper类型的静态字段,Resources提供的这些静态工具都是都是通过调用该ClassLoaderWrapper对象的相应方法实现的。
ResolverUtil
ResolverUtil可以根据指定的条件查找指定包下的类,其中使用的条件由Test接口表示。ResolverUtil中使用classLoader字段(ClassLoader类型)记录了当前使用的类加载器,默认情况下,使用的是当前线程上下文绑定的ClassLoader,我们可以通过setClassLoader()方法修改使用类加载器。
MyBatis提供了两个常用的Test接口实现,分别是IsA和AnnotatedWith,如图。IsA用于检测类是否继承了指定的类或接口,AnnotatedWith用于检测类是否添加了指定的注解。
ResolverUtil.findImplementations()方法和ResolverUtil.findAnnotated()方法都是依赖ResolverUtil.find()方法实现的,findImplementations()方法会创建IsA对象作为检测条件,findAnnotated()方法会创建AnnotatedWith对象作为检测条件。
/** * ResolverUtil可以根据指定的条件查找指定包下的类,其中使用的条件由Test接口表示。 * * @author kaifeng * @author Tim Fennell */public class ResolverUtil{ /** * 为当前类实例化一个日志对象 */ private static final Log log = LogFactory.getLog(ResolverUtil.class); /** * 一个简单的接口,指定如何检测类,以确定它们是否包含在ResolverUtil生成的结果中。 */ public interface Test { /** * 如果待检测的类符合条件,则返回True,否则返回false。 * * @param type 待检测的类 */ boolean matches(Class type); } /** * 用于检测类是否继承了指定的类或接口, */ public static class IsA implements Test { private Class parent; /** * 构造函数指定父类或实现的接口 */ public IsA(Class parentType) { this.parent = parentType; } /** * 如果继承了构造函数中指定的父类则返回true */ @Override public boolean matches(Class type) { return type != null && parent.isAssignableFrom(type); } @Override public String toString() { return "is assignable to " + parent.getSimpleName(); } } /** * 检测指定类是否添加了指定的注解 */ public static class AnnotatedWith implements Test { private Class annotation; /** * 构造函数指定注解类型 */ public AnnotatedWith(Class annotation) { this.annotation = annotation; } /** * 如果检测的类使用了构造函数中指定的注解,则返回true */ @Override public boolean matches(Class type) { return type != null && type.isAnnotationPresent(annotation); } @Override public String toString() { return "annotated with @" + annotation.getSimpleName(); } } /** * The set of matches being accumulated. */ private Set > matches = new HashSet >(); /** * 类加载器,默认使用Thread.currentThread().getContextClassLoader() will be used. */ private ClassLoader classloader; /** * 获取匹配的类集合 * * @return 匹配的类集合 */ public Set > getClasses() { return matches; } /** * 获取类加载器,默认使用当前线程绑定的类加载器 * * @return 用来加载类的类加载器 */ public ClassLoader getClassLoader() { return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader; } /** * 修改类加载器 * * @param classloader 指定的类加载器 */ public void setClassLoader(ClassLoader classloader) { this.classloader = classloader; } /** * 查找指定包中所有继承了指定父类的类 * * @param parent 父类或接口 * @param packageNames 一个或多个包名 */ public ResolverUtil findImplementations(Class parent, String... packageNames) { if (packageNames == null) { return this; } Test test = new IsA(parent); for (String pkg : packageNames) { find(test, pkg); } return this; } /** * 查找指定包下所有使用指定注解的类 * * @param annotation 指定的注解对象 * @param packageNames 一个或多个包名 */ public ResolverUtil findAnnotated(Class annotation, String... packageNames) { if (packageNames == null) { return this; } Test test = new AnnotatedWith(annotation); for (String pkg : packageNames) { find(test, pkg); } return this; } /** * 扫描指定包及子包下的所有类,检测包下所有的类 * * @param test 检测类的实例对象 * @param packageName 包名 e.g. { @code net.sourceforge.stripes} */ public ResolverUtil find(Test test, String packageName) { //根据包名获取包的路径 String path = getPackagePath(packageName); try { List children = VFS.getInstance().list(path); for (String child : children) { if (child.endsWith(".class")) { //检测类是否符合test条件 addIfMatching(test, child); } } } catch (IOException ioe) { log.error("Could not read package: " + packageName, ioe); } return this; } /** * 根据包名转换成包路径 * * @param packageName 包名 */ protected String getPackagePath(String packageName) { return packageName == null ? null : packageName.replace('.', '/'); } /** * 检测类是否符合指定的test条件 * * @param test 检测条件 * @param fqn 一个类的完整限定名,包括所在包的路径 */ @SuppressWarnings("unchecked") protected void addIfMatching(Test test, String fqn) { try { String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.'); ClassLoader loader = getClassLoader(); if (log.isDebugEnabled()) { log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]"); } Class type = loader.loadClass(externalName); if (test.matches(type)) { matches.add((Class ) type); } } catch (Throwable t) { log.warn("Could not examine class '" + fqn + "'" + " due to a " + t.getClass().getName() + " with message: " + t.getMessage()); } }}
ResolverUtil 的使用方式
//在pkg1,pkg2两个包下查找ActionBean类ResolverUtilresolver = new ResolverUtil ();//在pkg1,pkg2中查找继承了ActionBean的类resolver.findImplementation(ActionBean.class, pkg1, pkg2);resolver.find(new CustomTest(), pkg1);//在pkg1中查找符合条件CustomTest的类resolver.find(new CustomTest(), pkg2);//在pkg2中查找符合条件CustomTest的类//收集上述三个的结果Collection beans = resolver.getClasses();
VFS
VFS表示虚拟文件系统(Virtual File System),它用来查找指定路径下的资源。VFS是一个抽象类,MyBatis中提供了JBoss6VFS 和 DefaultVFS两个VFS的实现,如图所示。用户也可以提供自定义的VFS实现类
VFS中定义了list(URL, String)和isValid()两个抽象方法,
isValid()负责检测当前VFS对象在当前环境下是否有效,list(URL, String)方法负责查找指定的资源名称列表,在ResolverUtil.find()方法查找类文件时会调用list()方法的重载方法,该重载最终会调用list(list(URL,String)这个重载。/** * VFS表示虚拟文件系统(Virtual File System),它用来查找指定路径下的资源。 * VFS是一个抽象类,MyBatis中提供了JBoss6VFS 和 DefaultVFS两个VFS的实现。 * * @author kaifeng * @author Ben Gunter */public abstract class VFS { private static final Log log = LogFactory.getLog(VFS.class); /** * 记录两个VFS实现类 */ public static final Class [] IMPLEMENTATIONS = {JBoss6VFS.class, DefaultVFS.class}; /** * 记录用户自定义的VFS实现类,通过 {@link #addImplClass(Class)}.将自定义实现类添加到USER_IMPLEMENTATIONS集合中 */ public static final List> USER_IMPLEMENTATIONS = new ArrayList >(); /** * 单例模式,记录全局唯一VFS */ private static class VFSHolder { static final VFS INSTANCE = createVFS(); @SuppressWarnings("unchecked") static VFS createVFS() { // 优先使用用户自定义的VFS实现类,其次使用系统的 List > impls = new ArrayList >(); impls.addAll(USER_IMPLEMENTATIONS); impls.addAll(Arrays.asList((Class []) IMPLEMENTATIONS)); // 遍历实现类,依次实例化VFS对象并检测实例对象是否有效,如果获得有效实例对象则循环结束 VFS vfs = null; for (int i = 0; vfs == null || !vfs.isValid(); i++) { Class impl = impls.get(i); try { vfs = impl.newInstance(); if (vfs == null || !vfs.isValid()) { if (log.isDebugEnabled()) { log.debug("VFS implementation " + impl.getName() + " is not valid in this environment."); } } } catch (InstantiationException e) { log.error("Failed to instantiate " + impl, e); return null; } catch (IllegalAccessException e) { log.error("Failed to instantiate " + impl, e); return null; } } if (log.isDebugEnabled()) { log.debug("Using VFS adapter " + vfs.getClass().getName()); } return vfs; } } /** * 获取全局唯一VFS实例化对象 */ public static VFS getInstance() { return VFSHolder.INSTANCE; } /** * 将用户自定义VFS实现类添加到集合USER_IMPLEMENTATIONS * * @param clazz 自定义VFS实现类 */ public static void addImplClass(Class clazz) { if (clazz != null) { USER_IMPLEMENTATIONS.add(clazz); } } /** * 根据类名获取类 */ protected static Class getClass(String className) { try { return Thread.currentThread().getContextClassLoader().loadClass(className);// return ReflectUtil.findClass(className); } catch (ClassNotFoundException e) { if (log.isDebugEnabled()) { log.debug("Class not found: " + className); } return null; } } /** * 根据指定的类,方法名及方法参数获取方法对象 * * @param clazz 方法所属类对象 * @param methodName 方法名 * @param parameterTypes 方法参数 */ protected static Method getMethod(Class clazz, String methodName, Class ... parameterTypes) { if (clazz == null) { return null; } try { return clazz.getMethod(methodName, parameterTypes); } catch (SecurityException e) { log.error("Security exception looking for method " + clazz.getName() + "." + methodName + ". Cause: " + e); return null; } catch (NoSuchMethodException e) { log.error("Method not found " + clazz.getName() + "." + methodName + "." + methodName + ". Cause: " + e); return null; } } /** * 调用指定对象的指定方法并返回内容 * * @param method 指定的方法对象 * @param object 实例化对象 * @param parameters 方法参数 * @return 返回调用方法的返回值 * @throws IOException If I/O errors occur * @throws RuntimeException If anything else goes wrong */ @SuppressWarnings("unchecked") protected static T invoke(Method method, Object object, Object... parameters) throws IOException, RuntimeException { try { return (T) method.invoke(object, parameters); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { if (e.getTargetException() instanceof IOException) { throw (IOException) e.getTargetException(); } else { throw new RuntimeException(e); } } } /** * 根据指定的路径,使用当前线程绑定的类加载器获取该路径下所有资源的URL * * @param path 指定的资源路径 * @return 所有资源的URL集合 {@link ClassLoader#getResources(String)}. * @throws IOException If I/O errors occur */ protected static List getResources(String path) throws IOException { return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path)); } /** * 检测当前VFS对象在当前环境下是否有效 */ public abstract boolean isValid(); /** * 查找指定的资源名称列表, * * @param url 资源url地址 * @param forPath URL标识的资源的路径 * @return 包含资源名称的集合 * @throws IOException If I/O errors occur */ protected abstract List list(URL url, String forPath) throws IOException; /** * 递归列出在指定路径中找到的所有的完整资源路径。 * * @param path 资源路径 * @return 所有的完整资源路径集合 * @throws IOException If I/O errors occur */ public List list(String path) throws IOException { List names = new ArrayList (); for (URL url : getResources(path)) { names.addAll(list(url, path)); } return names; }}