文章目录
1、classLoader基础知识
1.1、class文件介绍
当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个文件不存在的,则会引发系统异常。而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。
对于class的解释网上的大牛写的非常清楚,这里引用他的一段博文。总的来说就是java静态编译时期会加载一部分的class文件使得程序能够正常启动,但是也有一部分类所生成的class文件需要到java运行的时候才会进行导入,例如大名鼎鼎的java反射。
1.2、java的类加载器
- BootStrap ClassLoader:称为启动类加载器。负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar
- Extension ClassLoader:称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。
- App ClassLoader:称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。
1.3、类加载器的工作方式
ClassLoader使用的是双亲委托模型来搜索类的,每一个类的加载器都拥有一个父加载器的对象,类加载器在加载一个类时首先会调用其父类加载器来加载这个类。这样做的好处有:
1、因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2、考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。
默认的java的类加载之间的关系是App ClassLoader的父类加载器是Extension ClassLoader,Extension ClassLoader的父类加载器是BootStrap ClassLoader。而BootStrap ClassLoader并不是用java来实现的,他本身是用C++来实现的,所以在java中是获取不到这个BootStrap ClassLoader的对象的。
左边是依次向上查找类是否已经被加载,右边是自顶向下尝试加载相应的类。
1.4、两个类相同的判断方法
1、判断两个类名是否相同
2、判断是否由同一个类加载器实例加载的
如果两个类要相同,必须满足上面两个条件,但大家可以想一下,一般的自定义列都是由App ClassLoader来进行加载的,所以这种情况下一般如果类的名称相同,就可以认为其class是相同的。
1.5、jdk中Class类存在的意义
手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件),比如创建一个Shapes类,编译Shapes类后就会创建其包含Shapes类相关类型信息的Class对象,并保存在Shapes.class字节码文件中。实际上所有的类都是在对其第一次使用时动态加载到JVM中的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中(实际上加载的就是这个类的字节码文件),然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。
也就是说一个Class对象里有相应的类对象的基础信息,例如静态的成员变量值,成员函数。每一个类的对象都可以通过getclass函数来获取这个类对象对应的class信息。
2、Class类函数分析
在初始化一个类,生成一个实例的时候,一般有两种方式:
1、使用new关键字:User user = new User();
2、使用反射机制:可以使用Class类的newInstance方法创建对象。这个newInstance方法调用无参的构造函数创建对象。
//创建方法1
User user = (User)Class.forName("根路径.User").newInstance();
//创建方法2
User user = User.class.newInstance();
2.1、Class类的newInstance方法的优势
/*我们可以从xml中动态读取相应的类名称*/
String className = readfromXMlConfig;
/*根据这个类名来获取相应的Class类对象,注意这里是jvm内存中一个类对应唯一的class对象,不是class对应的类对象*/
class c = Class.forName(className);
/*可以利用newInstance来生成一个类对象,同时ExampleInterface如果为这个类的接口,就可以直接进行逆赋值*/
ExampleInterface factory = (ExampleInterface)c.newInstance();
显而易见,上面这种做法可以有效的提高程序的灵活性,提供给了我们降耦的手段。但是使用newInstance的方式,必须要注意1、这个类已经加载 2、这个类已经连接了。
让我们再来总结一下这两者的区别:
-
newInstance(): 弱类型。低效率。是实现IOC、反射、面对接口编程和依赖倒置等技术方法的必然选择!
-
new: 强类型。相对高效。能调用任何public构造。new只能实现具体类的实例化,不适合于接口编程。
2.3、Class相关函数源码分析
public static Class<?> forName(String className)
throws ClassNotFoundException {
/*可以根据类名来得到相应的Class对象*/
Class<?> caller = Reflection.getCallerClass();
/*这是C实现的代码,根据Class属性值和加载器来从jvm中将对应的Class取出来,前提是jvm中已经有了这个Class*/
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
public T newInstance()
throws InstantiationException, IllegalAccessException
{
/*如果有安全管理器,那么首先需要判断允许访问这个Class类*/
if (System.getSecurityManager() != null) {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
}
// NOTE: the following code may not be strictly correct under
// the current Java memory model.
/*是否存在缓存的构造器,在我们成功进行一次构造时就会缓存相应的构造器,如果下次没有更换构造器,那么可以直接中缓存中取*/
if (cachedConstructor == null) {
/*判断是否是Class类型,因为不能新建一个Class的对象,Class对象是一种统称,必须要有相应准确的类名,才能进行newInstance*/
if (this == Class.class) {
throw new IllegalAccessException(
"Can not call newInstance() on the Class for java.lang.Class"
);
}
try {
Class<?>[] empty = {};
/*获取相应的构造器,具体构造器的选择在下面分析*/
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
/*禁用构造函数上的可访问性检查,因为无论如何我们都必须在此处进行安全检查(堆栈深度对于构造函数的安全检查而言是错误的)*/
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
c.setAccessible(true);
return null;
}
});
cachedConstructor = c;
} catch (NoSuchMethodException e) {
throw (InstantiationException)
new InstantiationException(getName()).initCause(e);
}
}
Constructor<T> tmpConstructor = cachedConstructor;
/*安全检查(类似于java.lang.reflect.Constructor)*/
int modifiers = tmpConstructor.getModifiers();
if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
if (newInstanceCallerCache != caller) {
Reflection.ensureMemberAccess(caller, this, null, modifiers);
newInstanceCallerCache = caller;
}
}
/*运行构造器来调用无参构造函数*/
try {
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
总结:forName函数可以根据class名称来得到相应的class的各项属性值,然后在newInstance中进行各项安全检查后调用相应的构造器来调用相应的无参构造函数来进行构造生成一个对象。
2.4、如何对一个Class对象选择合适的构造器
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
在Class类的newInstance中有这么一个语句,这个语句就是选择了这个class类匹配的构造器,具体来看下这个函数
private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
int which) throws NoSuchMethodException
{
/*因为我们这边传入的参数是Member.DECLARED,这个表示检查的是已经定义的类,所以这边which == Member.PUBLIC值为0
返回这个类对应的已声明的类,具体实现时里面调用了一个C实现的getDeclaredConstructors0的函数来返回相应的构造器*/
Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
/*根据parameterTypes来选择合适的构造器*/
for (Constructor<T> constructor : constructors) {
if (arrayContentsEq(parameterTypes,
constructor.getParameterTypes())) {
/*复制构造器*/
return getReflectionFactory().copyConstructor(constructor);
}
}
throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
}
从上面我们也可以看出,我们可以获取到这个类对应的已声明或者公共的类的构造器,例如我在User类中,就可以构造一个User的已经声明和类和一些公共的类。
注:因为小编个人实力,和涉及到一些C实现的库函数查看不到,个人觉得2.4节可能不是完全准确,如有描述的不正确的地方,希望能够大家能够指出。