天天看点

Java语言进阶篇:Java反射原理与Android类加载引入反射动态代理ClassLoader加载器分析(重点)Android里的类加载

参考博客: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对象的相应方法来获取调用,详细反射的使用

动态代理

Java语言进阶篇:Java反射原理与Android类加载引入反射动态代理ClassLoader加载器分析(重点)Android里的类加载

这里仅贴出一张原理图,详细代理模式

然后我们再来梳理一下,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());
           

会出现空指针异常

Java语言进阶篇:Java反射原理与Android类加载引入反射动态代理ClassLoader加载器分析(重点)Android里的类加载

通过前面介绍的三种加载器的加载内容,也是能解释的过去的,只是这些并没有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和空指针异常

Java语言进阶篇:Java反射原理与Android类加载引入反射动态代理ClassLoader加载器分析(重点)Android里的类加载

上述说明AppClassLoader的父加载器是ExtClassLoader,又是一个空指针异常,这表明ExtClassLoader也没有父加载器?这不自相矛盾吗?继续往下

父加载器不是父类

static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
           
Java语言进阶篇:Java反射原理与Android类加载引入反射动态代理ClassLoader加载器分析(重点)Android里的类加载

二者结合,很容易看出来,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找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托。

盗图

Java语言进阶篇:Java反射原理与Android类加载引入反射动态代理ClassLoader加载器分析(重点)Android里的类加载

用序列描述一下:

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项目中关于这些的使用应该不算多,但是我们可以了解一哈。