参考博客:https://blog.csdn.net/briblue/article/details/54973413
引入
我们还是拿我们常用的FBI来说事哈,不过我们现在基本都不写这个了,现在常用的是ButterKnife黄油刀,那么除了黄油刀呢,我们还能使用其他的什么框架来实现呢?xUtils也是可以的,关于xUtils的使用,其实讲使用的那篇博客也简要讲了一下实现原理,我们这里再简单撸一遍,主要是把反射、动态代理等相关知识串一下。
反射
1.基本概念与三种获取class对象方法
1.概念:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
2.三种获取class对象方法:
① Object ——> getClass();即通过
②任何数据类型(包括基本数据类型)都有一个“静态”的class属性
③ 通过Class类的静态方法:forName(String className)(常用)
2.反射获取实例化对象、属性信息、包信息、方法
都是使用class对象的相应方法来获取调用,详细反射的使用
动态代理
这里仅贴出一张原理图,详细代理模式
然后我们再来梳理一下,xUtils主要是通过handlerType通过反射来获取所有注解,即通过activity的class文件,然后通过遍历,最后经过判断再将属性、方法通过反射获取和执行;如果你看源码,我们可以很清楚的发现,还有一个重要的原因,这个框架应该是个中国人写的,里面的注释都比较容易懂,毕竟中文是母语。
前面的博客或者xUtils源码你基本都看过一遍,你应该会发现一个getClassLoader()方法,而我前后都没有讲过这,这篇博客的重点就是这个啦!!!
ClassLoader加载器分析(重点)
Java语言系统自带有三个类加载器:
- Bootstrap ClassLoader 最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。
- Extention ClassLoader 扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。
- Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。
以上三个我们可以来证明一下,直接得到路径
System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(System.getProperty("java.class.path"));
通过打印信息,我们前两个看到的JRE目录下的jar包或者是class文件和ext文件,第三个我们可以找到对应的目录,看到一点熟悉的东西,即我们的.java文件编译的.class文件。
我们选择的这个类比较讲究,sun.misc.Launcher是一个java虚拟机的入口应用。
我们先可以通过源码,了解一下这三个类加载器的加载顺序。
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//初始化ExtClassLoader
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//初始化getAppClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
;
} catch (InstantiationException var6) {
;
} catch (ClassNotFoundException var7) {
;
} catch (ClassCastException var8) {
;
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
通过备注是不是感觉漏了一个BootstrapClassLoader,Launcher中并没有看见BootstrapClassLoader,但通过System.getProperty("sun.boot.class.path")得到了字符串bootClassPath,这个应该就是BootstrapClassLoader加载的jar包路径。还记得我们开局打印的这个路径吗?与上述结论吻合。加载顺序如下
1. Bootstrap CLassloder
2. Extention ClassLoader
3. AppClassLoader
上述我们找的是一个系统类,现在我们可以自己下一个类测试一下,
新建一个Test.java
public class Test{}
然后新建测试类执行如下代码
ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
我们会在控制台看到如下内容
ClassLoader is:[email protected]
我们可以知道要开启虚拟机,即我们一开始分析的那个类Launcher,然后APPClassLoader加载了我们编写的Test类。
但是当我们操作如下代码时
ClassLoader c2 = Integer.class.getClassLoader();
System.out.println("ClassLoader is:"+c2.toString());
会出现空指针异常
通过前面介绍的三种加载器的加载内容,也是能解释的过去的,只是这些并没有getClassLoader方法,Integer.class是由Bootstrap ClassLoader加载的。我们还是通过源码来看下,不过我们首先要明白两点
每个类加载器都有一个父加载器
父加载器,怎么样获取呢?很简单,通过getParent方法
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
System.out.println("ClassLoader\'s grand fatheris:"+cl.getParent().getParent().toString());
我们可以在上栗的后面再添加如下代码,成功的打印出来了ExtClassLoader和空指针异常
上述说明AppClassLoader的父加载器是ExtClassLoader,又是一个空指针异常,这表明ExtClassLoader也没有父加载器?这不自相矛盾吗?继续往下
父加载器不是父类
static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
二者结合,很容易看出来,APPClassLoader的父类并不是ExtClassLoader,那么为啥我们APPClassLoader的getParent()却取出了APPClassLoader???什么玩意??APPClassLoader和ExtClassLoader这两个都是URLClassLoader的子类啊???
NEXT
我们来看下这个getParent方法,发现只有在ClassLoader才有这个方法,简要如下
public abstract class ClassLoader {
private final ClassLoader parent;
private static ClassLoader scl;
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
...
}
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
public final ClassLoader getParent() {
if (parent == null)
return null;
return parent;
}
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
//通过Launcher获取ClassLoader
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}
}
以上为阉割版源码,不过我们可以获得如下信息
getParent()实际上返回的就是一个ClassLoader对象parent,parent的赋值是在ClassLoader对象的构造方法中,它有两个情况: 1. 由外部类创建ClassLoader时直接指定一个ClassLoader为parent。
2. 由getSystemClassLoader()方法生成,也就是在sun.misc.Laucher通过getClassLoader()获取,也就是AppClassLoader。
总结就是,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。
我们继续研究AppClassLoader和ExtClassLoader父加载器的问题,除了上述最先贴出的Launcher部分源码,还补充一部分如下
static class ExtClassLoader extends URLClassLoader {
/**
* create an ExtClassLoader. The ExtClassLoader is created
* within a context that limits which files it can read
*/
public static ExtClassLoader getExtClassLoader() throws IOException
{
final File[] dirs = getExtDirs();
try {
// Prior implementations of this doPrivileged() block supplied
// aa synthesized ACC via a call to the private method
// ExtClassLoader.getContext().
return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>() {
public ExtClassLoader run() throws IOException {
//ExtClassLoader在这里创建
return new ExtClassLoader(dirs);
}
});
} catch (java.security.PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
/*
* Creates a new ExtClassLoader for the specified directories.
*/
public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
}
}
}
结合这部分代码及一开始我们Launcher的部分源码,我们扣点重点的出来,
首先,
ClassLoader extcl;
extcl = ExtClassLoader.getExtClassLoader();
loader = AppClassLoader.getAppClassLoader(extcl);
得到ExtClassLoader的getparent(),即需要自身的构造器,即需要调用父类的构造方法,对应如下代码
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent);
}
发现还是需要ClassLoader对象parent,很明显,这里已经是null了,综上:ExtClassLoader的parent是null。
然后我们看第二个,得到传入的就是ExtClassLoader的实例,这里应该传的就是AppClassLoader的父加载器,即ExtClassLoader就是AppClassLoader的父加载器。
既然ExtClassLoader的父加载器为null,那么Bootstrap CLassLoader却可以当成它的父加载器这又是为何呢?▄█▀█●
Bootstrap ClassLoader是由C++编写的,略过
双亲委托
一直只闻其名,不闻其人啊
一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托。
盗图
用序列描述一下:
1. 一个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。
2. 递归,重复第1部的操作。
3. 如果ExtClassLoader也没有加载过,则由Bootstrap ClassLoader出面,它首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class下面的路径。找到就返回,没有找到,让子加载器自己去找。
4. Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader自己在java.ext.dirs路径中去查找,查找成功就返回,查找不成功,再向下让子加载器找。
5. ExtClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path路径下查找。找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。
上面的序列,详细说明了双亲委托的加载流程。我们可以发现委托是从下向上,然后具体查找过程却是自上至下。
看一下ClassLoader的源码,是如何展示双亲委托的,如下
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检测是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加载器不为空则调用父加载器的loadClass
c = parent.loadClass(name, false);
} else {
//父加载器为空则调用Bootstrap Classloader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//父加载器没有找到,则调用findclass
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//调用resolveClass()
resolveClass(c);
}
return c;
}
}
一般实现这个方法的步骤是
1. 执行findLoadedClass(String)去检测这个class是不是已经加载过了。
2. 执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
3. 如果向上委托父加载器没有加载成功,则通过findClass(String)查找。
Android里的类加载
都是用java写的,那就是一样的流程加载出来的,如果是用kotlin写的,我就不清楚了,还有就是Android项目中关于这些的使用应该不算多,但是我们可以了解一哈。