1 类的加载、连接和初始化
Java类加载器除了根类加载器之外,其他类加载器都是使用Java语言编写的
1.1 JVM和类
- 同一个JVM的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区,不同的JVM并不会共享数据
- JVM进程的终止:
- 程序运行到最后正常的结束
- 程序运行到使用
System.exit()
或Runtime.getRuntime().exit()
代码处结束程序 - 程序执行过程中遇到未捕获的异常或错误而结束
- 程序所在平台强制结束了JVM进程
1.2 类的加载
- 类加载指的是将类的
class
文件读入内存,并为之创建一个java.lang.Class
对象 - 类的加载由类加载器完成,类加载器由JVM提供,JVM提供的这些加载器通常被称为系统类加载器,除此之外,开发者可以通过继承
ClassLoader
基类来创建自己的类加载器
1.3 类的连接
- 连接阶段负责把类的二进制数据合并到JRE中
- 类连接的三阶段
- 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
- 准备:类准备阶段则负责为类的类变量分配内存,并设置默认初始值
- 解析:将类的二进制数据中的符号引用替换成直接引用
1.4 类的初始化
- 虚拟机负责对类进行初始化,主要就是对类变量进行
- JVM初始化一个类包含如下几个步骤:
- 假如这个类还没有被加载和连接,则程序先加载和连接这个类
- 假如这个类的直接父类还没有被初始化,则先初始化其直接父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句
- JVM最先初始化的永远是
java.lang.Object
1.5 类初始化的时机
- 初始化的时机:
- 创建类的实例:使用
new
创建、通过反射来创建、通过反序列化来创建 - 调用某个类的类方法
- 访问某个类或接口的类变量,或为该类变量赋值
- 使用反射的方式来强制创建某个类或接口对应的
java.lang.Class
对象 - 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
- 创建类的实例:使用
- 注意:
final
类变量,如果在编译时值就可以确定,那么在编译时就定下来了,使用它不会导致类的初始化,如果值不能确定,将会导致类的初始化 - 当使用
ClassLoader
类的loadClass()
方法来加载某个类时,该方法只是加载该类,并不会执行该类的初始化;使用Class
类的forName()
静态方法才会导致强制初始化该类
2 类加载器
类加载器负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象
2.1 简介
在Java中,一个类使用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类使用其全限定类名和其类加载器作为其唯一标识
- JVM三个类加载器组成的初始化类加载器层次结构:
- Bootstrap ClassLoader:根类加载器,负责加载Java的核心类
- Extension ClassLoader:扩展类加载器,负责加载JRE的扩展目录中JAR包的类
- System ClassLoader:系统类加载器,负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径
2.2 类加载机制
- 类加载机制:
- 全盘负责:类加载器负责加载和这个类相关的所有类
- 父类委托:首先使用父类加载器加载,加载失败的话再采用自己的类路径中加载该类
- 缓存机制:保证加载过的类全部被缓存下来,当程序需要使用某个类时,首先从缓存中检索,只有不存在的时候才会重新加载
- 类加载器之间的父子关系并不是类继承上的父子关系,这里的父子关系是类加载器实例之间的关系,根类加载器 > 扩展类加载器 > 系统类加载器 > 用户类加载器
- JVM的根类加载器不是Java实现的,程序通常无法访问根类加载器,因此访问扩展类加载器的父类加载器时返回
null
。
// 获取系统类加载器
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器:" + systemLoader);
/*
获取系统类加载器的加载路径——通常由CLASSPATH环境变量指定
如果操作系统没有指定CLASSPATH环境变量,默认以当前路径作为
系统类加载器的加载路径
*/
Enumeration<URL> em1 = systemLoader.getResources("");
while(em1.hasMoreElements()){
System.out.println(em1.nextElement());
}
// 获取系统类加载器的父类加载器:得到扩展类加载器
ClassLoader extensionLader = systemLoader.getParent();
System.out.println("扩展类加载器:" + extensionLader);
System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs"));
System.out.println("扩展类加载器的parent: " + extensionLader.getParent());
- 类加载器加载
Class
的步骤:
- 检测此
Class
是否载入过,如果有直接进入第8步,否则执行第2步; - 如果父类加载器不存在(parent是根类加载器,或本身是根类加载器),则跳到第4步执行,如果父类加载器存在,则接着执行第3步;
- 请求使用父类加载器去载入目标类,如果成功载入则跳到第8步,否则接着执行第5步;
- 请求使用根类加载器来载入目标类,如果成功载入则跳到第8步,否则跳到第7步;
- 当前类加载器尝试寻找Class文件,如果找到则执行第6步,否则跳到第7步;
- 从文件中载入Class,成功载入后跳到第8步;
- 抛出ClassNotFoundException异常;
- 返回对饮的java.lang.Class对象。
- 检测此
3 反射
由于Java中的对象在运行时会出现两种类型:编译时类型和运行时类型,程序需要在运行时发现对象和类的真实信息,方式:
- 假设在编译时和运行时都完全知道类型的具体信息,在这种情况下,可以先使用
instanceof
运算符进行判断,再利用强制类型转换符将其转换成其运行时类型的变量即可 - 编译时根本无法预知该对象和类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真是信息,这就必须使用反射
能够分析类能力的程序称为反射(reflective),反射机制的功能极其强大,可以用来:
- 在运行时分析类的能力;
- 在运行时查看对象;
- 实现通用的数组操作代码;
- 利用Method对象,这个对象很像C++中的函数指针。
反射是一种功能强大且复杂的机制,使用它的主要人员是工具构造者。
3.1 通过反射分析类
3.1.1 获得Class对象
在程序运行期间,JRE始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类,JVM利用运行时类型信息选择相应的方法执行,这些信息可以通过Class
类来访问。Object
类(所有类的父类)中的getClass()
方法将会返回一个Class
类型的实例,一个Class
对象将标识一个特定类的属性。虚拟机为每个类型管理一个Class
对象,因此,可以用==
运算符实现两个类对象比较的操作。在获取类名称的时候,可以利用forName
与newInstance
配合来创建一个对应对象,但是假如该类没有默认构造器(构造器参数为0)的话,就会抛出一个异常。
获得Class类对象的三种方式:
getClass()
方法:需要对应对象的一个实例forName()
方法:需要对应对象的完整类名称T.class
方法:需要对应对象的类型T
注意:
Class
是一个类,而Class
对应的类型并不一定是类,例如:int.class
3.1.2 从Class中获取信息
在java.lang.reflect
包中有三个类Field
、Method
、Constructor
,分别用于描述类的域、方法、构造器。这三个类都有一个叫做getName()
的方法,用来返回项目的名称。Field
类有一个getType
方法,用来返回描述域所属类型的Class
对象。Method
和Constructor
有能够报告参数类型的方法,Method
类还有一个可以报告返回类型的方法。这三个类还有一个叫做getModifiers
的方法,它将返回一个整型数值,用不同的位开关描述public
和static
这样的修饰符使用情况。另外,可以利用java.lang.reflect
包中的Modifier
类的静态方法分析getModifiers
返回的整型数值。
Class
类中的getFields
、getMethods
和getConstructors
方法将分别返回类提供的public
域、方法和构造函数,其中包括超类公有成员。Class
类的getDeclareFields
、getDeclaredMethods
、getDeclaredConstructors
方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但是不包括超类的成员。
3.1.3 Java 8新增的方法参数反射
- 新增
Executable
抽象类,该对象代表可执行的类成员,该类派生出了Constructor
、Method
两个子类 - 提供的方法:
- 获取修饰该方法或构造器的注解信息
- isVarArgs():判断该方法或构造器是否包含数量可变的形参
- getModifiers():获取该方法或构造器的修饰符
- getParameterCount():获取该构造器或方法的形参个数
- getParameters():获取该构造器或方法的所有形参
- 获取修饰该方法或构造器的注解信息
- 新增Parameter API,每个Parameter对象代表方法或构造器的一个参数,提供大量方法来获取申明该参数的泛型信息,
- getModifiers():获取修饰该形参的修饰符
- getName():获取形参名
- getParameterizedType():获取带泛型的形参类型
- getType():获取形参类型
- isNamePresent():该方法返回该类的class文件中是否包含了方法的形参名信息
3.2 通过反射操作类
程序可以通过Method
对象来执行对应的方法,通过Constructor
对象来调用对应的构造器创建实例,能通过Field
对象直接访问并修改对象的成员变量值
3.2.1 创建对象
通过反射来生成对象的两种方式:
- 使用
Class
对象的newInstance()
方法来创建该Class
对象对应类的实例,这种方式要求该Class
对象的对应类有默认构造器,而执行newInstance()
方法时实际上是利用默认构造器来创建该类的实例。 - 先使用
Class
对象获取指定的Constructor
对象,再调用Constructor
对象的newInstance()
方法来创建该Class
对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。
- 使用
利用指定的构造器来创建Java对象的步骤:
- 获取该类的
Class
对象; - 利用
Class
对象的getConstructor()
方法来获取指定的构造器; - 调用
Constructor
的newInstance()
方法来创建Java对象。
- 获取该类的
注意:通过反射创建对象时性能要稍低一些,实际上只有程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。
3.2.2 调用方法
- 获得某个类的
Class
对象后,就可以通过该Class
对象的getMethods()
方法或getMethod()
方法来获取方法了,每个Method
对象对应一个方法,可以通过该Method
对象来调用它对应的方法,在Method
里包含一个invoke()
方法,该方法签名:Object invoke(Object obj, Object... args);
该方法中的obj
是执行该方法的主调,后面的args
是执行该方法时传入该方法的实参。 - 调用
private
方法:setAccessible(boolean flag)
;将Method
对象的accessible
设置为指定的boolean
值,值为true
,指示该Method
在使用时应该取消Java语言的访问权限审查;值为false
,指示该Method
在使用时要实施Java语言的访问权限检查
3.2.3 访问成员变量
- 通过
Class
对象的getFields()
或getField()
方法可以获取该类所包括的全部成员变量或指定成员变量,Field
提供了如下两组方法来读取或设置成员变量值。
- getXxx(Object obj):获取
obj
对象的该成员变量的值。此处的Xxx
对应8种基本类型,如果该成员变量的类型是引用型,则取消get
后面的Xxx
- setXxx(Object obj, Xxx val):将
obj
对象的该成员变量设置成val
值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用型,则取消set
后面的Xxx
- getXxx(Object obj):获取
3.2.4 操作数组
- 在
java.lang.reflect
包下还提供了一个Array
类,Array
对象可以代表所有的数组。程序可以通过使用Array
来动态地创建数组,操作数组元素等 Array
提供了如下几类方法:
- static Object newInstance(Class<?> componentType, int… length):创建一个具有指定的元素类型、指定维度的新数组
- static xxx getXxx(Object array, int index):返回Array数组中第index个元素
- static void setXxx(Object array, int index, xxx val):将array数组的第index个元素的值设为val
4 代理机制
使用代理可以在运行时创建一个实现了一组给定接口的新类,这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用,主要用于编写框架和底层基础代码
4.1 代理的作用
- 代理类可以在运行时创建全新的类,这样的代理类能够实现指定的接口,并具有下列方法:
- 指定接口所需要的全部方法;
Object
类中的全部方法。
- 调用处理器:由于不能在运行时定义这些方法的新代码,需要通过调用处理器(实现了
InvocationHandler
接口的类对象,在这个接口中只有一个方法:Object invoke(Object proxy, Method method, Object[] args);
,无论何时调用代理对象的方法,调用处理器的invoke
方法都会被调用,并向其传递Method
对象和原始的调用参数)来实现。 - 使用代理的原因:
- 路由对远程服务器的方法调用
- 在程序运行期间,将用户接口事件与动作关联起来
- 为调试、跟踪方法调用
4.2 Java动态代理机制
在Java的·java.lang.reflect·包下提供了一个·Proxy·类和一个·InvocationHandler·接口,通过使用这个类和接口可以生成动态代理类或动态代理对象。
4.2.1 使用Proxy和InvocationHandler创建动态代理
Proxy
提供了用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy
来创建动态代理类;如果需要为一个或多个接口动态地创建实例,也可以使用Proxy
来创建动态代理实例。
Proxy
提供了如下两个方法来创建动态代理类和动态代理实例:
- static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces):创建一个动态代理类所对应的
Class
对象,该代理类将实现interfaces
所指定的多个接口。第一个ClassLoader
参数指定生成动态代理类的类加载器。 - static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):直接创建一个动态代理对象,该代理对象的实现类实现了
interfaces
指定的系列接口,执行代理对象的每个方法都会被替换执行InvocationHandler
对象的invoke
方法。
- static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces):创建一个动态代理类所对应的
- InvocationHandler:每一个动态代理类都必须要实现
InvocationHandler
这个接口,并且每个代理类的实例都关联到了一个handler
,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler
这个接口的invoke
方法来进行调用。 - invoke方法:
Object invoke(Object proxy, Method method, Object[] args);
- proxy:被代理的对象
- method:被代理的对象的某个方法的
Method
对象 - args:调用真实对象某个方法时的参数
- 通过
Proxy
类的Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
方法创建一个代理对象,该方法有三个参数:
- 类加载器:
null
(使用默认加载器);定义了由哪个ClassLoader对象来对生成的代理对象进行加载 - Class对象数组,每个元素都是需要实现的接口;给被代理的对象提供一组接口,这个代理对象实现了该接口(多态),这样就可以调用这组接口中的方法
- 调用处理器:表示的是当动态代理对象在调用方法的时候,关联的
InvocationHandler
对象
- 类加载器:
interface Person {
void walk();
void sayHello(String name);
}
class MyInvokationHandler implements InvocationHandler {
/*
执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
其中:
proxy:代表动态代理对象
method:代表正在执行的方法
args:代表调用目标方法时传入的实参。
*/
public Object invoke(Object proxy, Method method, Object[] args) {
System.out.println("----正在执行的方法:" + method);
if (args != null) {
System.out.println("下面是执行该方法时传入的实参为:");
for (Object val : args) {
System.out.println(val);
}
}
else {
System.out.println("调用该方法没有实参!");
}
return null;
}
}
public class ProxyTest {
public static void main(String[] args) throws Exception {
// 创建一个InvocationHandler对象
InvocationHandler handler = new MyInvokationHandler();
// 使用指定的InvocationHandler来生成一个动态代理对象
Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);
// 调用动态代理对象的walk()和sayHello()方法
p.walk();
p.sayHello("孙悟空");
}
}
- 注意:通过
Proxy.newProxyInstance
创建的代理对象是在JVM运行时动态生成的一个对象,它并不是我们的InvocationHandler
类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$
开头,proxy
为中,最后一个数字表示对象的标号。
4.2.2 动态代理和AOP
- 为了解决代码的耦合和重复,又不愿在程序中以硬编码方式调用额外的代码,可以使用动态代理方法实现,采用动态代理可以非常灵活地实现解耦
interface Dog {
// info方法声明
void info();
// run方法声明
void run();
}
class GunDog implements Dog {
// 实现info()方法,仅仅打印一个字符串
public void info() {
System.out.println("我是一只猎狗");
}
// 实现run()方法,仅仅打印一个字符串
public void run() {
System.out.println("我奔跑迅速");
}
}
class DogUtil {
// 第一个拦截器方法
public void method1() {
System.out.println("=====模拟第一个通用方法=====");
}
// 第二个拦截器方法
public void method2() {
System.out.println("=====模拟通用方法二=====");
}
}
class MyInvokationHandler implements InvocationHandler
{
// 需要被代理的对象
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
DogUtil du = new DogUtil();
// 执行DogUtil对象中的method1。
du.method1();
// 以target作为主调来执行method方法
Object result = method.invoke(target , args);
// 执行DogUtil对象中的method2。
du.method2();
return result;
}
}
class MyProxyFactory {
// 为指定target生成动态代理对象
public static Object getProxy(Object target) throws Exception {
// 创建一个MyInvokationHandler对象
MyInvokationHandler handler = new MyInvokationHandler();
// 为MyInvokationHandler设置target对象
handler.setTarget(target);
// 创建、并返回一个动态代理
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() , handler);
}
}
public class Test {
public static void main(String[] args) throws Exception {
// 创建一个原始的GunDog对象,作为target
Dog target = new GunDog();
// 以指定的target来创建动态代理
Dog dog = (Dog)MyProxyFactory.getProxy(target);
dog.info();
dog.run();
}
}
- AOP(Aspect Oriented Programming,面向切面编程):AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法,AOP代理的方法可以在执行目标方法之前、之后插入一些通用处理。