这里写自定义目录标题
重点掌握
- 理解 Class 类
- 理解 Java 的类加载机制
- 学会使用 ClassLoader 进行类加载
- 理解反射的机制
- 掌握 Constructor、Method、Field 类的用法
类的加载
- 类的加载
- 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
- 加载
- 就是指将class文件读入内存,并为之创建一个Class对象。
- 任何类被使用时系统都会建立一个Class对象。
- 连接
- 验证 是否有正确的内部结构,并和其他类协调一致
- 准备 负责为类的静态成员分配内存,并设置默认初始化值
- 解析 将类的二进制数据中的符号引用替换为直接引用
- 初始化
类的初始化时机
- 类的初始化
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
类加载器
-
类加载器
负责将.class文件加载到内存中,并为之生成对应的Class对象。
虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。 -
类加载器的组成
Bootstrap ClassLoader 根类加载器
Extension ClassLoader 扩展类加载器
System ClassLoader 系统类加载器 -
Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载
比如System,String等。在JDK中JRE的lib目录下rt.jar文件中 -
Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载。
在JDK中JRE的lib目录下ext目录 -
System ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
ClassLoader(类装载器):
类装载器是用来把类(class)装载进 JVM 的。JVM 规范定义了两种类型的类装载器:启动类装载器(bootstrap)和用户自定义装载器(user-defined class loader)。 JVM在运行时会产生3个类加载器组成的初始化加载器层次结构 ,如下图所示:
演示类加载机制的层次关系
public class ClassLoaderDemo {
public static void main(String[] args) {
ClassLoader classloader;
//获取系统缺省的ClassLoader
classloader = ClassLoader.getSystemClassLoader();
System.out.println(classloader);
while (classloader != null) {
//取得父的ClassLoader
classloader = classloader.getParent();
System.out.println(classloader);
}
try {
Class cl = Class.forName("java.lang.Object");
classloader = cl.getClassLoader();
System.out.println("java.lang.Object's loader is " + classloader);
cl = Class.forName(“tcpc1.Student");
classloader = cl.getClassLoader();
System.out.println("ClassLoaderDemo's loader is " + classloader);
} catch (Exception e) {
System.out.println("Check name of the class");
}
}
执行结果如下:
//表示系统类装载器实例化自类sun.misc.Launcher
A
p
p
C
l
a
s
s
L
o
a
d
e
r
s
u
n
.
m
i
s
c
.
L
a
u
n
c
h
e
r
AppClassLoader sun.misc.Launcher
AppClassLoadersun.misc.LauncherAppClassLoader@19821f
//表示系统类装载器的parent实例化自类sun.misc.Launcher
E
x
t
C
l
a
s
s
L
o
a
d
e
r
s
u
n
.
m
i
s
c
.
L
a
u
n
c
h
e
r
ExtClassLoader sun.misc.Launcher
ExtClassLoadersun.misc.LauncherExtClassLoader@addbf1
//表示系统类装载器parent的parent为bootstrap,无法直接获取
null
//表示类Object是由bootstrap装载的
java.lang.Object’s loader is null
//表示用户类是由系统类装载器装载的
ClassLoaderDemo’s loader is sun.misc.Launcher$AppClassLoader@19821f
反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
Java反射的应用
- Spring框架:IOC(控制反转)
- Hibernate框架:关联映射等
- 白盒测试
Java反射相关的API
- java.lang包下
Class:表示一个正在运行的 Java 应用程序中的类和接口,是Reflection的起源 - java.lang.reflect包下
Field 类:代表类的成员变量(也称类的属性)
Method类:代表类的方法
Constructor 类:代表类的构造方法
Array类:提供了动态创建数组,以及访问数组的元素的静态方法
反射——获取字节码对象
三种获取字节码文件对象的方式
方式1:
Person p = new Person();
Class c = p.getClass();
通过对象获取字节码文件对象
方式2:
Class c2 = Person.class;
任意数据类型都具备一个class静态属性,看上去要比第一种方式简单.
方式3:
Class c3 = Class.forName(“Person”);
将类名作为字符串传递给Class类中的静态方法forName即可。
反射——获取字节码对象成员(field)
- 获取成员
- 获取所有成员
getFields,getDeclaredFields - 获取单个成员
getField,getDeclaredField - 修改成员的值
set(Object obj,Object value) 将指定对象变量上的此 Field 对象表示的字段设置为指定的新值
- 获取所有成员
Field对象:
通过Class对象的getFields()或者getField()方法可以获得该类所包括的全部Field属性或指定Field属性。Field类提供了以下方法来访问属性
getXxx(Object obj):获取obj对象该Field的属性值。此处的Xxx对应8个基本数据类型,如果该属性类型是引用类型则直接使用get(Object obj)
setXxx(Object obj,Xxx val):将obj对象的该Field赋值val。此处的Xxx对应8个基本数据类型,如果该属性类型是引用类型则直接使用set(Object obj, Object val)
setAccessible(Boolean flag):若flag为true,则取消属性的访问权限控制,即使private属性也可以进行访问
public class FieldTest {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("cn.jbit.reflection.Student");
Object obj = clazz.newInstance();
// 调用getDeclaredField("name") 取得name属性对应的Field对象
Field f = clazz.getDeclaredField("name");
// 取消属性的访问权限控制,即使private属性也可以进行访问。
f.setAccessible(true);
// 调用get()方法取得对应属性值。
System.out.println(f.get(obj)); //相当于obj.getName();
// 调用set()方法给对应属性赋值。
f.set(obj, "lkl"); //相当于obj.setName("lkl");
// 调用get()方法取得对应属性修改后的值。
System.out.println(f.get(obj));
}
}
反射——获取字节码对象成员方法
- 获取普通方法
- 获取所有方法
getMethods
getDeclaredMethods - 获取单个方法
getMethod
getDeclaredMethod - 暴力访问
method.setAccessible(true);
- 获取所有方法
Method对象
通过Class对象的getMethods() 方法可以获得该类所包括的全部方法, 返回值是Method[]
通过Class对象的getMethod()方法可以获得该类所包括的指定方法, 返回值是Method
每个Method对象对应一个方法,获得Method对象后,可以调用其invoke() 来调用对应方法
Object invoke(Object obj,Object [] args):obj代表当前方法所属的对象的名字,args代表当前方法的参数列表,返回值Object是当前方法的返回值,即执行当前方法的结果。
public class TestMethod {
public int add(int x, int y) { return x + y; }
public void shout(String name) {System.out.println("my name is"+name);}
public static void main(String[] args) throws Exception {
// 创建该类的一个对象
Class clazz = TestMethod.class;
Object obj = clazz.newInstance();
// 调用该对象的add方法
Method amethod=clazz.getMethod("add",new Class[]{int.class, int.class});
Object result = amethod.invoke(obj, new Object[] { 5, 7 });
System.out.println(result);
// 调用该对象的shout方法
Method smethod=clazz.getMethod("shout",new Class[]{String.class});
smethod.invoke(obj, new Object[] { "lkl" });
}
}
反射——动态代理
- 动态代理
- 代理:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象。
举例:春季回家买票让人代买 - 动态代理:在程序运行过程中产生的这个对象
而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理 - 在代理过程中,可以在本类基础上添加新的功能,使其功能更强大
- 在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib。
- Proxy类中的方法创建动态代理类对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
最终会调用InvocationHandler的方法 - InvocationHandler
Object invoke(Object proxy,Method method,Object[] args
- 代理:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象。
反射总结
反射提高了Java程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创建和控制任何类的对象,无需提前硬编码目标类
反射是其它一些常用语言,如C、C++、Fortran 或者Pascal等都不具备的Java反射技术应用领域很广,如软件测试、JavaBean等
许多流行的开源框架例如Struts、Hibernate、Spring在实现过程中都采用了该技术
反射的缺点
- 性能问题
使用反射基本上是一种解释操作,用于字段和方法接入时要远慢 于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很 高的系统框架上,普通程序不建议使用。 - 使用反射会模糊程序内部逻辑
程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。