Java动态性之反射机制
一、动态语言
程序运行时,可以改变程序结构或变量类型,典型的语言:python,ruby,javascript等
function test(){
var s="var a=3; var b=5; alert(a+b);";
eval(s);
}
c,c++,java不是动态语言,java可以称之为“准动态语言”。但是java有一定的动态性,我们可以利用反射机制,字节码操作获得类似动态语言的特性。
二、反射机制
- 反射机制(reflection)指的是可以在运行时加载,探知,使用编译期间完全未知的类。
- 程序在运行状态中,可以动态加载一个只有名称的类,对于任意一个已加载的类,都能够知道这个类所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。
-
Class c= Class.forName("com.bjsxt.test.User");
- 加载完类之后,在堆内存汇总,就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
Class类信息:
- java.lang.Class类十分特殊,用来表示java汇总类型(class、interface、enum、annotation)本身。
- Class类的对象包含了某个被加载类的结构,一个被加载的类对应一个Class对象。
- 当一个类被加载,或当类加载器的defineClass()被JVM调用,JVM便会自动产生一个Class对象。
- Class类是反射机制的根源,针对任何想动态创建,运行的类,唯有先获得相应的Class对象。
获取Class对象:
- 运用getClass()
- 运用Class.forName()(最常用!!!)
- 运用.class语法
反射机制的常见作用:
- 动态加载类,动态获取类的信息(属性,方法,构造器)
- 动态构造对象
- 动态调用类和对象的任意方法,构造器
- 动态调用和处理属性
- 获取泛型信息
- 处理注解
package com.csu.marden;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.csu.marden.bean.User;
/**
* 应用反射机制,获取类的信息(类的名字,属性,方法,构造器)
* 通过反射API动态的操作:构造器,方法,属性
* @author marden
*
*/
public class Demo1 {
public static void main(String[] args) throws Exception{
Class clazz=Class.forName("com.csu.marden.bean.User");
//获取类的名字
System.out.println(clazz.getName());
System.out.println(clazz.getSimpleName());
//获取属性信息
Field [] fields=clazz.getFields(); //只能获取public的属性
for(Field s:fields){
System.out.println(s);
}
Field [] fields1=clazz.getDeclaredFields(); //可以获取所有权限的属性
for(Field s:fields1){
System.out.println(s);
}
Field f=clazz.getDeclaredField("uname"); //获取指定名称的属性
System.out.println(f);
//获取方法信息
Method [] method=clazz.getMethods(); //只能获取public的方法
for(Method m:method){
System.out.println(m);
}
Method [] method1=clazz.getDeclaredMethods(); //可以获取所有权限的方法
for(Method m:method1){
System.out.println(m);
}
Method m1=clazz.getDeclaredMethod("setPhone", String.class); //获取指定名称的方法
Method m2=clazz.getDeclaredMethod("getPhone", null);
System.out.println(m1);
System.out.println(m2);
//获取构造器
Constructor [] constructors1=clazz.getConstructors(); //只能获取public的构造器
for(Constructor c:constructors1){
System.out.println(c);
}
Constructor [] constructors2=clazz.getDeclaredConstructors(); //可以获取所有权限的构造器
for(Constructor c:constructors2){
System.out.println(c);
}
Constructor constructor3=clazz.getDeclaredConstructor(null); //获取指定名称的构造方法
Constructor constructor4=clazz.getDeclaredConstructor(int.class,int.class,String.class,String.class);
System.out.println(constructor3);
System.out.println(constructor4);
//通过反射API调用构造方法,构造对象
User u1=(User) clazz.newInstance(); //其实是调用了User的无参构造
System.out.println(u1);
Constructor constructor5=clazz.getDeclaredConstructor(int.class,int.class,String.class,String.class);
User u2=(User) constructor5.newInstance(1001,18,"marden","1545115"); //获取指定构造器,并使用该构造器创建对象
System.out.println(u2);
//通过反射API调用普通方法
User u3=(User) clazz.newInstance();
Method m3=clazz.getDeclaredMethod("setUname", String.class);
m3.invoke(u3, "王五");
System.out.println(u3);
//通过反射API操作属性
User u4=(User) clazz.newInstance();
Field f1=clazz.getDeclaredField("uname");
f1.setAccessible(true); //这个属性不需要做安全检查,可以直接访问,若没有这条语句,无法修改private的属性
f1.set(u4, "赵六");
System.out.println(u4);
}
}
反射截止性能问题:
- setAccessible:启用和禁用访问安全检查的开关,值为true,则指示反射的对象在使用时应该取消Java语言访问检查,值为false,则指示反射的对象应该实施Java语言访问检查。并不是为true就能访问,为false就不能访问。禁用安全检查,可以提高反射的运行速度。
- 可以考虑使用:cglib/javaassist字节码操作
三、类加载全过程
JVM将class文件加载到内存,并对数据进行校验,解析和初始化,最终形成JVM可以使用的Java类型的过程。
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。(这个过程需要类加载器参与)
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。验证:确保加载的类信息符合JVM规范,没有安全方面的问题。准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。解析:虚拟机常量池的符号引用替换为直接引用的过程。
初始化:初始化阶段是执行类构造器方法的过程。类构造器方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先初始化其父类。虚拟机会保证一个类的构造器方法在多线程环境汇总被正确枷锁和同步。当访问一个Java类的静态域时,只有真正声明这个域的类才会被初始化。
类加载器的层次结构:(树状结构)
- 引导类加载器:它用来加载Java的核心库,使用原生代码来实现的,并不继承自java.lang.ClassLoader。
- 扩展类加载器:用来加载Java的扩展库,Java虚拟机的实现会提供一个扩展库目录,该类加载器在此目录里面查找并加载Java类。
- 应用程序类加载器:它根据Java应用的类路径,一般来说,Java应用的类都是由它来完成加载的。
- 自定义类加载器:开发人员可以通过集成java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
注意:除了引导类加载器使用原生代码实现,扩展类加载器,应用程序加载器,自定义加载器均继承自java.class.ClassLoader类。该类的职责就是根据一个指定的类的名称,找到或生成其对应的字节代码,然后从这些字节代码中定义一个Java类,即java.lang.Class类的一个实例。持此之外,ClassLoader还负责加载Java应用所需的资源,如图像文件,配置文件等。
类加载器的代理模式
代理模式:交给其他加载器来加载指定的类。
双亲委托机制:父类加载器优先加载。就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈时,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。双亲委托机制是为了保证Java核心库的类型安全。
注意:并不是所有类加载器都是用双亲委托机制。如tomcat服务器类加载器,它是首先尝试去加载某个类,如果找不到再代理给父类加载器。
本文地址:https://blog.csdn.net/m0_37671741/article/details/107594641
上一篇: Java开发核心技术卷(一)读书笔记
下一篇: RabbitMQ中生产者丢消息的情况