Mybatis源码学习之资源加载(六)
类加载器简介
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);
}
/**
* 构造类加载器数组,指定了类加载器的使用顺序
* <p>
* classLoader 指定的类加载器
* defaultClassLoader 指定的默认类加载器
* Thread.currentThread().getContextClassLoader() 当前线程绑定的类加载器
* getClass().getClassLoader() 加载当前类所使用的类加载器
* systemClassLoader 系统类加载器
* </p>
*
* @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<T> {
/**
* 为当前类实例化一个日志对象
*/
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<? extends Annotation> annotation;
/**
* 构造函数指定注解类型
*/
public AnnotatedWith(Class<? extends Annotation> 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<Class<? extends T>> matches = new HashSet<Class<? extends T>>();
/**
* 类加载器,默认使用Thread.currentThread().getContextClassLoader() will be used.
*/
private ClassLoader classloader;
/**
* 获取匹配的类集合
*
* @return 匹配的类集合
*/
public Set<Class<? extends T>> 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<T> 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<T> findAnnotated(Class<? extends Annotation> 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<T> find(Test test, String packageName) {
//根据包名获取包的路径
String path = getPackagePath(packageName);
try {
List<String> 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<T>) 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类
ResolverUtil<ActionBean> resolver = new ResolverUtil<ActionBean>();
//在pkg1,pkg2中查找继承了ActionBean的类
resolver.findImplementation(ActionBean.class, pkg1, pkg2);
resolver.find(new CustomTest(), pkg1);//在pkg1中查找符合条件CustomTest的类
resolver.find(new CustomTest(), pkg2);//在pkg2中查找符合条件CustomTest的类
//收集上述三个的结果
Collection<ActionBean> 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<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<Class<? extends VFS>>();
/**
* 单例模式,记录全局唯一VFS
*/
private static class VFSHolder {
static final VFS INSTANCE = createVFS();
@SuppressWarnings("unchecked")
static VFS createVFS() {
// 优先使用用户自定义的VFS实现类,其次使用系统的
List<Class<? extends VFS>> impls = new ArrayList<Class<? extends VFS>>();
impls.addAll(USER_IMPLEMENTATIONS);
impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));
// 遍历实现类,依次实例化VFS对象并检测实例对象是否有效,如果获得有效实例对象则循环结束
VFS vfs = null;
for (int i = 0; vfs == null || !vfs.isValid(); i++) {
Class<? extends VFS> 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<? extends VFS> 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> 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<URL> 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<String> list(URL url, String forPath) throws IOException;
/**
* 递归列出在指定路径中找到的所有的完整资源路径。
*
* @param path 资源路径
* @return 所有的完整资源路径集合
* @throws IOException If I/O errors occur
*/
public List<String> list(String path) throws IOException {
List<String> names = new ArrayList<String>();
for (URL url : getResources(path)) {
names.addAll(list(url, path));
}
return names;
}
}
Mybatis源码学习之资源加载(六)的更多相关文章
- 【MyBatis源码分析】Configuration加载(下篇)
元素设置 继续MyBatis的Configuration加载源码分析: private void parseConfiguration(XNode root) { try { Properties s ...
- mybatis源码解析之Configuration加载(四)
概述 上一篇文章,我们主要讲了datasource的相关内容,那么<environments>标签下的内容就看的差不多了,今天就来看一下在拿到transationManager和datas ...
- 【MyBatis源码分析】Configuration加载(上篇)
config.xml解析为org.w3c.dom.Document 本文首先来简单看一下MyBatis中将config.xml解析为org.w3c.dom.Document的流程,代码为上文的这部分: ...
- mybatis源码解析之Configuration加载(五)
概述 前面几篇文章主要看了mybatis配置文件configuation.xml中<setting>,<environments>标签的加载,接下来看一下mapper标签的解析 ...
- mybatis源码解析之Configuration加载(三)
概述 上一篇我们主要分析了下<environments>标签下面,transactionManager的配置,上问最后还有个遗留问题:就是在设置事物管理器的时候有个autocommit的变 ...
- mybatis源码解析之Configuration加载(二)
概述 上一篇我们讲了configuation.xml中几个标签的解析,例如<properties>,<typeAlises>,<settings>等,今天我们来介绍 ...
- flutter源码学习笔记-图片加载流程
本文基于1.12.13+hotfix.8版本源码分析. 0.大纲 Image ImageProvider 图片数据加载 ImageStream.ImageStreamCompleter 缓存池 Pai ...
- mybatis源码解析之Configuration加载(一)
概要 上一篇,我们主要搭建了一个简单的环境,这边我们主要来分析下mybatis是如何来加载它的配置文件Configuration.xml的. 分析 public class App { public ...
- Mybatis源码学习第六天(核心流程分析)之Executor分析
今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...
随机推荐
- 利用Mathpix Snipping Tool轻松在markdown/LaTeX中输入电子书和论文的数学公式
最近写图形学博客写累了,公式太多了,一个个输入实在太累,所以从数学建模队友那里吃了一个安利. 官网下载 下载安装后,直接新建一个截图,就可以转成LaTeX数学公式了.效果如下: 爽的一批啊!!! 另外 ...
- 进程程序替换(自主实现shell)
进程替换 替换进程所运行的程序 流程:将另一段代码加载到内存中,通过页表将原来进程的映射关系重新建立到新的程序在内存中的地址,相当于替换了进程所运行程序以及所要处理的数据 (替换了代码段,重新初始化数 ...
- .net core 根据数据库生成实体类
微软最近几年在跨平台上不断发力,很多.net程序员也摩拳擦掌,对微软寄以厚望.就在最近,微软还推出了asp .net core2.0预览版. 通过对.net core的简单尝试,我发现以往我们开发MV ...
- Signalr Vue Echarts绘制实时CPU使用率
后端基于Asp.net webapi,前端Vue,前后端分离,该demo仅做演示,实现的细节可以自己优化 Echarts:4.2.1 可参考 官网 Jquery:3.4.1 Signalr:2.4. ...
- vue-cli3 本地数据模拟后台接口
vue-cli3 本地数据模拟后台接口 原理: 将本地的json数据在前端模拟为后台接口,然后调用接口,完成前端操作.在后台接通后可以直接在api配置文件中修改路径,完成前后台对接. 配置: 1.文件 ...
- str.charAt()与str[]的区别
str.charAt():只能显示当前字符,没有则显示空. str[]:当索引超出当前字符长度时,则显示undefined.
- React会自动把虚拟DOM数组展开
React会自动把虚拟DOM数组展开,放在父级虚拟DOM中,这个特性还是我同事帮我解决一个问题的时候,偶然发现的. 如何将一个数据数组转换为一个虚拟DOM的数组,需要使用map,如下: const n ...
- Marketing Cloud里取得系统contact数目的API
Marketing Cloud里的Contact标准tile(下图红色tile)上是没有当前系统contact数字显示的,请对比profile tile(下图黑色tile). 客户有需求希望在Laun ...
- apache thinkphp5 强制https://访问
根目录下,.htaccess文件 <IfModule mod_rewrite.c> Options +FollowSymlinks -Multiviews RewriteEngine On ...
- Appium|Locator Strategy ... is not supported for this session
appim server log InvalidSelectorError: Locator Strategy 'xpath,//android.widget.TextView[@resource-i ...