Mybaits资源加载模块所在位置:
在java中类加载器(classloader)主要负责加载文件系统、网络或者其他来源的类文件,通过双亲委派模式进行加载,默认使用三种类加载器进行加载相关的类,分别为BootStrapClassloader,Extension Classloader以及System Classloader(Applicaiton Classloader),三种加载器加载不同位置的类,BootStrapClassloader加载JDK自带的rt.jar包中的类文件,而Extension Classlader负责加载java的扩展类库。System Classloader主要负责加载classpath环境变量下的类文件,通常我们自定义的类都由该加载器进行加载。双亲委派模型如下:
当一个类被加载时,一次向上委托,直到到BootStrap Classloader,当BootStrap Classloader已加载该类时,其他加载器将不能加载,当改加载器不能加载该类时,将由Extension Classloader进行加载,若其不能加载,继续向下交给Application ClassLoader进行加载。
1、在Mybatis的IO模块中ClassloaderWrapper对ClassLoader进行了封装
/**
* 默认classloader 对象,通过ClassLoaderWrapper.defaultClassLoader = xxx方式进行设置
*/
ClassLoader defaultClassLoader;
/**
* 系统classloader
*/
ClassLoader systemClassLoader;
ClassLoaderWrapper() {
try {
systemClassLoader = ClassLoader.getSystemClassLoader();
} catch (SecurityException ignored) {
// AccessControlException on Google App Engine
}
}
ClassLoadWrapper主要通过三种方式进行加载相关资源。getResourceAsURL()、getResourceAsStream()和classForName()方法。在该类中对三种方法进行了重载,最终都调用了其带有String,Classloader[]参数的重载方法。
public URL getResourceAsURL(String resource, ClassLoader classLoader) {
return getResourceAsURL(resource, getClassLoaders(classLoader));
}
对于getClassLoaders(classLoader)方法主要获取classloader[]并确定类加载器的顺序。
// 获取classloader数组
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader, // 指定类加载器
defaultClassLoader, // 默认加载器
Thread.currentThread().getContextClassLoader(), // 当前线程绑定加载器
getClass().getClassLoader(), // 加载器当前类所使用的加载器
systemClassLoader}; // 系统类加载器
}
/**
* Get a resource as a URL using the current class path
*
* @param resource - the resource to locate
* @param classLoader - the class loaders to examine
* @return the resource or null
* 获得指定资源的 URL
*/
URL getResourceAsURL(String resource, ClassLoader[] classLoader) {
URL url;
// 遍历classloader数组,当其中有个加载器加载到资源时立即返回
for (ClassLoader cl : classLoader) {
if (null != cl) {
// 获得URL 不带 /
// look for the resource as passed in...
url = cl.getResource(resource);
// ...but some class loaders want this leading "/", so we'll add it
// and try again if we didn't find the resource
if (null == url) {
// 获取URL 带 /
url = cl.getResource("/" + resource);
}
// "It's always in the last place I look for it!"
// ... because only an idiot would keep looking for it after finding it, so stop looking already.
if (null != url) {
// 成功获得返回
return url;
}
}
}
// didn't find it anywhere.
return null;
}
2、Resources类主要对ClassloaderWrapper进行了封装,其中的静态方法都调用了ClassloaderWrapper中的方法,相当于一个工具类。
public static URL getResourceURL(ClassLoader loader, String resource) throws IOException {
URL url = classLoaderWrapper.getResourceAsURL(resource, loader);
if (url == null) {
throw new IOException("Could not find resource " + resource);
}
return url;
}
public static Reader getResourceAsReader(ClassLoader loader, String resource) throws IOException {
Reader reader;
if (charset == null) {
reader = new InputStreamReader(*getResourceAsStream(loader, resource*));
} else {
reader = new InputStreamReader(getResourceAsStream(loader, resource), charset);
}
return reader;
}
其中许多方法只是进行了进一步封装,就不一一说明了。
3、ResolverUtil主要根据指定条件查找指定包下的类,条件通过类的方式体现。在其内部定义了Test接口,以该接口的实现类作为条件。
public interface Test {
boolean matches(Class<?> type);
}
Test主要有两个实现类,IsA,与AnnotatedWith
public static class IsA implements Test {
/**
* 用于检测类是否继承了指定的parent类
*/
private Class<?> parent;
public IsA(Class<?> parentType) {
this.parent = parentType;
}
@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;
}
@Override
public boolean matches(Class<?> type) {
return type != null && type.isAnnotationPresent(annotation);
}
@Override
public String toString() {
return "annotated with @" + annotation.getSimpleName();
}
}
ResolverUtil内部定义的属性:
/**
* 存储匹配的类
*/
private Set<Class<? extends T>> matches = new HashSet<>();
private ClassLoader classloader;
public Set<Class<? extends T>> getClasses() {
return matches;
}
public ClassLoader getClassLoader() {
// 默认使用当前线程绑定的 类加载器
return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader;
}
public void setClassLoader(ClassLoader classloader) {
this.classloader = classloader;
}
findImplementations(Class<?> parent, String… packageNames)方法,创建IsA作为检测对象。
if (packageNames == null) {
return this;
}
Test test = new IsA(parent);
for (String pkg : packageNames) {
find(test, pkg);
}
return this;
findAnnotated()方法,创建AnnotatedWith作为检测对象
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;
}
find(test, pkg):方法如下
public ResolverUtil<T> find(Test test, String packageName) {
// 根据包名获取其对应的路径
String path = getPackagePath(packageName);
try {
// 通过VFS查找该包下的所有资源
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
if (child.endsWith(".class")) {
// 匹配则进行添加
addIfMatching(test, child); // 检测该类是否符合test条件
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
将包名转化为路径的方法:
protected String getPackagePath(String packageName) {
// 获取包路径
return packageName == null ? null : packageName.replace('.', '/');
}
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());
}
}
}
4、VFS虚拟文件系统,用来查找指定路径下的资源
// 记录mybatis提供的VFS的实现类
public static final Class<?>[] IMPLEMENTATIONS = { JBoss6VFS.class, DefaultVFS.class };
// 记录用户自定义的实现类
public static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<>();
对于VFS的构建则是通过内部静态类的方式进行构造的:
/** Singleton instance holder. */
private static class VFSHolder {
static final VFS INSTANCE = createVFS();
@SuppressWarnings("unchecked")
static VFS createVFS() {
// Try the user implementations first, then the built-ins
List<Class<? extends VFS>> impls = new ArrayList<>();
// 先使用用户自定义的vfs实现类,
impls.addAll(USER_IMPLEMENTATIONS);
// 再使用mybatis提供的实现类
impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));
// Try each implementation class until a valid one is found
VFS vfs = null;
// 遍历实现类,找到一个实现类后并且有效将会直接返回
for (int i = 0; vfs == null || !vfs.isValid(); i++) {
Class<? extends VFS> impl = impls.get(i);
try {
vfs = impl.getDeclaredConstructor().newInstance();
if (!vfs.isValid()) {
if (log.isDebugEnabled()) {
log.debug("VFS implementation " + impl.getName() +
" is not valid in this environment.");
}
}
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
log.error("Failed to instantiate " + impl, e);
return null;
}
}
if (log.isDebugEnabled()) {
log.debug("Using VFS adapter " + vfs.getClass().getName());
}
return vfs;
}
}`在这里插入代码片`
/**
* 单例模式,通过内部静态类方式实现
*/
public static VFS getInstance() {
return VFSHolder.INSTANCE;
}
该类中提供了一个极为重要的方法 list(URL url, String forPath),用于查找指定资源的名称列表,在其子类中实现,以DefaultVFS为例:
@Override
public List<String> list(URL url, String path) throws IOException {
InputStream is = null;
try {
List<String> resources = new ArrayList<>();
// First, try to find the URL of a JAR file containing the requested resource. If a JAR
// file is found, then we'll list child resources by reading the JAR.
// 如果url指向的资源在一个jar包中,则获取这个jar包的url,负责返回null
URL jarUrl = findJarForResource(url);
if (jarUrl != null) {
is = jarUrl.openStream();
if (log.isDebugEnabled()) {
log.debug("Listing " + url);
}
// 遍历jar包中的资源并返回以path开头的资源列表
resources = listResources(new JarInputStream(is), path);
}
else {
List<String> children = new ArrayList<>();
try {
// // 判断为 JAR URL
if (isJar(url)) {
// Some versions of JBoss VFS might give a JAR stream even if the resource
// referenced by the URL isn't actually a JAR
is = url.openStream();
try (JarInputStream jarInput = new JarInputStream(is)) {
if (log.isDebugEnabled()) {
log.debug("Listing " + url);
}
for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null; ) {
if (log.isDebugEnabled()) {
log.debug("Jar entry: " + entry.getName());
}
children.add(entry.getName());
}
}
}
else {
// 获取路径下所有资源
is = url.openStream();
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
for (String line; (line = reader.readLine()) != null;) {
if (log.isDebugEnabled()) {
log.debug("Reader entry: " + line);
}
lines.add(line);
if (getResources(path + "/" + line).isEmpty()) {
lines.clear();
break;
}
}
}
if (!lines.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug("Listing " + url);
}
children.addAll(lines);
}
}
} catch (FileNotFoundException e) {
/*
* For file URLs the openStream() call might fail, depending on the servlet
* container, because directories can't be opened for reading. If that happens,
* then list the directory directly instead.
*/
if ("file".equals(url.getProtocol())) {
File file = new File(url.getFile());
if (log.isDebugEnabled()) {
log.debug("Listing directory " + file.getAbsolutePath());
}
if (file.isDirectory()) {
if (log.isDebugEnabled()) {
log.debug("Listing " + url);
}
children = Arrays.asList(file.list());
}
}
else {
// No idea where the exception came from so rethrow it
throw e;
}
}
// The URL prefix to use when recursively listing child resources
String prefix = url.toExternalForm();
if (!prefix.endsWith("/")) {
prefix = prefix + "/";
}
// Iterate over immediate children, adding files and recursing into directories
for (String child : children) {
String resourcePath = path + "/" + child;
resources.add(resourcePath);
URL childUrl = new URL(prefix + child);
resources.addAll(list(childUrl, resourcePath));
}
}
return resources;
} finally {
if (is != null) {
try {
is.close();
} catch (Exception e) {
// Ignore
}
}
}
}