Java入门
学!
ClassLoader
Java的类都不是一启动就全部加载了的,有些类只在被需要时才被临时加载进JVM。而实现类加载的就是Java的ClassLoader,这个东西感觉在学Java的时候会无数次的被提及,以及他相应的一些知识点。
类的加载时机
类的加载分为显式和隐式两种
显示加载:调用ClassLoader的loadClass,findClass方法,Class类的forName()方法
隐式加载:new getstatic putstatic invokestatic
四个指令会隐式的进行类的加载
常见ClassLoader
BootStrap ClassLoader
,启动类加载器。最顶层的类加载器,负责加载java核心类库,由C++实现,写在了JVM底层,已经超脱出了java类的范畴,所以当我们尝试去获取由BootStrap ClassLoader加载的类的加载器时,得到的结果会是null
Extension ClassLoader
,扩展类加载器。加载JAVA_HOME/jre/lib/ext/
路径下的jarApp ClassLoader
,系统类加载器。是默认的类加载器
然后就是我们自定义的ClassLoader了
双亲委派模型
应该说是一种规范。就是当第一级的ClassLoader去加载一个类的时候,先把这个类提供给他的父加载器进行加载,父加载器加载不到再自己动手。
并不是类的继承关系,而是一种规定的父加载器关系
BootStrap ClassLoader -> Extension ClassLoader -> App ClassLoader -> 自定义ClassLoader
双亲委派模型有各种各样的好处,比如什么不会重复加载类啦什么的
并且如果同一个类使用不同的ClassLoader加载进来会导致这两个类被认为是不同类
ClassLoader核心方法
loadClass
(加载指定的Java类)findClass
(查找指定的Java类)findLoadedClass
(查找JVM已经加载过的类)defineClass
(定义一个Java类)resolveClass
(链接指定的Java类)
类加载器都是java.lang.ClassLoader
的子类,Bootstrap不算。一般写一个自定义的ClassLoader,就是要能找到一个类的字节码文件并返回
findClass是统一加载类的函数,在这个函数里面一般先调用父加载器的findClass函数,找不到就自己根据路径去加载类字节码,用defineClass方法(是JVM的native方法)将类字节码注册到JVM里面去
反射
之前看Java反序列化的时候就学了一点了,现在再复习一下
反射说白了就是能在程序运行时通过一些手段来动态的加载类以及调用类的方法,访问类的成员变量之类的,廖雪峰反射这段讲的也蛮好的
反射操作的是Java的Class类,所以在进行反射操作的时候需要先获取到Class类对象
Class类
JVM在每次遇到一个没有加载的class的时候就会使用ClassLoader将对应类的.class文件(也就是javac编译出来的字节码文件)加载进来,然后为该类创建一个Class类的对象比如String类就是Class cls = new Class(String);
Class对象是由JVM创建的,且Class类的构造方法是private的,所以只有JVM能创建class实例
每个Class实例会指向一个数据类型(String,Runnable之类的),同时一个Class实例也包含该类的完整信息
获取Class实例
- 类名.class
- Class.forName(“类名”)
- classLoader.loadClass(“类名”)
访问一个class文件下的内部类时需要使用$,对于数组类型的需要使用奇怪的表示方式,如下Class<?> doubleArray = Class.forName("[D");//相当于double[].class Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");// 相当于String[][].class
获取Method实例
能获得任意类一般不是我们的重点,一般来说我们都是想调用某个类的特定方法,因此我们需要获得一个类的一个Method实例
可以通过Method class.getDeclaredMethod(name)
从Class实例中获取到对应类的一个方法Method实例
Method实例并不等于对应的方法,但可以通过method.invoke(Object,Object...)
在调用静态方法时类实例可以为null,参数没有可以不填
获取Field实例
访问反射获取的Class对象时可以越过类对对象的访问控制权限进行,即private等属性的变量也可以进行访问,通过class.getField(name)
可以访问到公有属性,Field class.getDeclaredField(name)
可以获取任意属性,函数名加s获取全部公有/任意属性
返回的均为Field对象,同样的,使用Field.get(Object)
和Field.set(Object,Object)
,获取类实例的name属性,或是对类实例的对应属性进行赋值但对应private属性还需先进行class.setAccessible(true)
,但是JVM可能存在SecurityManager
阻止对某些类使用setAccessible(true)
通过反射获取Unsafe类
Unsafe类提供了Java一些非常底层的操作,为java内部用API,不允许外部调用。不能被直接new出来,当使用ClassLoader进行加载的时候只允许被Bootstrap ClassLoader加载
可以通过反射的方式来获取Unsafe类
Unsafe类中有一个private static final Unsafe theUnsafe
成员变量,其即为Unsafe类的实例
可以通过如下方法来获取Unsafe类实例
Class clz = Class.forName("sun.misc.Unsafe");
Field f = clz.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe)f.get(null);
forName要写完整路径。。我一开始没写就没成
Unsafe类可以使用allocateInstance无视构造函数的构建一个类实例
在java8之前可以通过Unsafe类的defineClass绕过ClassLoader在JVM中创建Class对象
代理
代理的关键就是不编写实现类,直接在运行时创建某个接口(interface)的实例
通过一个InvocationHandler定义一个handler,其可对代理对象的方法进行增强,使用Proxy.newProxyInstance()创建一个interface实例,接受三个参数:ClassLoader,传入接口类的ClassLoader即可,要实现的接口数组(只传一个接口也行,但是一定得是数组),一个处理接口方法的InvocationHandler,代理对象的方法最终都会交给InvocationHandler的invoke方法执行
通过Proxy创建代理对象,然后将接口方法由InvocationHandler完成
只需要被代理类和接口实现了同一个接口,即可保证其内部的结构一致,就可以通过实例化接口生成的代理对象接受被代理类,并在此基础上进行代码增强
代理实际的实现原理也就是JVM在运行时用你给的ClassLoader临时生成了一个代理类的字节码并加载进了内存
Native方法
是一个Java调用非Java代码的接口,一般来说就是Java交给C/C++实现的功能
这个方法使用的一大原因就是跨平台,在很底层的实现上并不由Java实现,而是通过C/C++等语言对应不同的平台进行实现,然后把这些方法按照JNI(Java Native Interface)的规范进行编写,编译成动态链接库,打包在不同平台的JDK上,实现Java的跨平台