反射:框架设计的灵魂
- 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码。
- 反射:将类的各个组成部分封装为其他对象,这就是反射机制。
好处:
- 可以在程序运行过程中,操作这些对象。
- 可以解耦,提高程序的可扩展性。
反射机制有什么用?
- 通过java语言中的反射机制可以操作字节码文件(class文件)。
- 运行时分析类的能力。
- 在运行时检查对象,例如,编写一个适用于所有类的
toString
方法。 - 实现泛型数组操作代码。
- 利用 Method 对象,这个对象很像C++中的函数指针。
反射机制的相关类在哪个包下?
java.lang.reflect.*;
反射机制相关的重要的类有哪些?
java.lang.class
:代表整个字节码,代表-一个类型, 代表整个类。
java.lang.reflect.Method
:代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor
:代表字节码中的构造方法字节码。代表类中的构造方法。
java.lang.reflect.Field
:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。
Class类
在程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标志。这个信息会跟踪每个对象所属的类。虚拟机利用运行时类型信息选择要执行的正确的方法。
可以使用特殊的Java类访问这些信息。保存这些信息的类名为Class
。Object
类中的 getClass()
将会返回一个Class类型的实例。
个人理解:运行时类型标志:每个对象都以各自的(类)模板创建对象,在下图中Class类对象阶段各个类的类对象包含了每个类的成员变量、构造方法、成员方法等信息。虚拟机运行时会利用这些类对象的信息创建实例对象,并根据这些信息结合实际对象执行相应对象的方法。每个实例对象可以通过
实例对象.getClass()
获得其对应的类对象。每个实例对象可能都有一个“指针”指向其对应的类对象。
要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class
实例?:
-
Class.forName("className")
:将字节码文件加载进内存,返回Class对象
(多用于配置文件,将类名定义在配置文件中。读取文件,加载类)className:如果类在一个包里,包的名字也作为类名的一部分。
如果className是一个类名或接口名,这个方法可以正常执行。否则,
Class.forName("className")
方法将抛出一个检查型异常(checked exception)。无论何时使用这个方法,都应该提供一个异常处理器(exception handler)。Class.forName() 发生了什么?
重点:
如果你只希望一个类的静态代码块执行,其他代码一律不执行,
可以使用:
Class.forName("完整类名");
这个方法的执行会导致类加载,类加载时,静态代码块执行。
在启动时,包含main方法的类被加载。它会加载所有需要的类。这些被加载的类又需要加载他们需要的类,以此类推。对于一个大型的应用程序来说,这将花费很长的时间,用户会因此感到不耐烦。可以使用下面这个技巧给用户一种启动速度比较快的假象。不过,要确保包含main方法的类没有显示的引用其他的类。首先,显示一个启动的画面;然后,通过调用
Class.forName
手工的加载其他类。 -
T.class
:通过类名的属性class获取(多用于参数的传递)
T是任意Java类型(或void关键字),
T.class
将代表匹配的类对象。一个Class对象实际和是哪个表示的是一个类型,可能是类,也可能不是类。(例如,int不是类,但
int.class
是一个Class类型的对象。) -
实例对象.getClass()
: getClass()方法在0bject类中定义着。
(多用于对象的获取字节码的方式)
Class对象描述了一个特定类的属性:成员变量、构造方法、成员方法。
java语言中任何一种类型,包括基本数据类型,它都有
.class
属性。java语言中任何一个对象都有一个方法:
getClass()
。因为Object类中含有getClass()
方法,所有类都直接或间接继承自Object类。
Class类实际上是一个泛型类。在大多数问题中,可以忽略类型参数,而使用原始的Class类。
public static void main(String[] args) throws ClassNotFoundException {
//1.Class. forName("全类名")
Class cls1 = Class.forName("cn.mycompany.mypackage.Person");
System.out.println(cls1);// class cn.mycompany.mypackage.Person
//2.类名.class
Class cls2 = Person.class;
System.out.println(cls2);// class cn.mycompany.mypackage.Person
//3.对象.getClass()
Person person = new Person() ;
Class cls3 = person.getClass();
System.out.println(cls3);// class cn.mycompany.mypackage.Person
System.out.println(cls1==cls2==cls3)// true ==判断的是对象的内存地址
}
结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次, 不论通过哪一种方式获取的Class对象都是同一 个。
虚拟机为每一个类型管理一个唯一的Class对象。因此,可以利用
==
运算符实现两个类对象的比较。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OsHGOEzJ-1614328619734)(E:\Typora\images\image-20210224181337077.png)]
利用反射分析类的能力
反射机制最重要的内容——检查类的结构。
Class对象功能
获取功能:
-
获取成员变量
Field[] getFields()//获取所有public修饰的成员变量 Field getField(String name)//获取指定名称的public修饰的成员变量 Field[] getDeclaredFields()//获取所有的成员变量,不考虑修饰符 Field getDeclaredField(String name)//获取指定名称的成员变量,不考虑修饰符
-
获取构造方法
Constructor<T>[] getConstructors()//获取所有public修饰的构造方法 Constructor<T> getConstructor(Class<T>... parameterTypes)//获取指定参数类型的public修饰的构造方法 Constructor<T>[] getDeclaredConstructors()//获取所有构造方法 Constructor<T> getDeclaredConstructor(Class<T>... parameterTypes)//获取指定参数类型的构造方法
-
获取成员方法
Method[] getMethods()//获取本类及其父类所有被public修饰的方法 Method getMethod(String name,Class<T>... parameterTypes)//获取指定参数类型的被public修饰的方法 Method[] getDeclaredMethods()//获取本类及其父类所有方法 Method getDeclaredMethod(String name,Class<T>...parameterTypes)//获取指定参数类型的方法
-
获取类名
String getName()//获取完整类名 String getSimpleName()//获取简类名
鉴于历史原因,
getName
方法在应用于数组类型的时候会返回有些奇怪的名字:Double[].class.getName()
返回 “[Ljava.lang.Double;”int[].class.getName()
返回"[I"
Field:成员变量
操作:
- 设置值
void set(object obj object value)
- 获取值
get(object obj)
- 忽略访问权限修饰符的安全检查
setAccessible(true):暴力反射
public static void main(String[] args) throws Exception {
//获取Person的Class对象
Class personClass = Person.class;
//1. Field[] getFields()获取所有public修饰的成员变量
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
//2.Field getField(String name)
Field fieldVar = personClass.getField("fieldVar");
//获取成员变量fieldVar的值
Person person = new Person();
Object value = fieldVar.get(person);
System.out.println(value);
//设置fieldVar的值
fieldVar.set(person,"joker");
System.out.println(person);
//Field[] getDeclaredFields(): 获取所有的成员变量,不考虑修饰符
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
//Field getDeclaredField(String name)
Field privateFieldVar = personClass.getDeclaredField("privateFieldVar");
//忽略访问权限修饰符的安全检查
privateFieldVar.setAccessible(true);//暴力反射
value = privateFieldVar.get(person);
System.out.println(value);
}
Constructor:构造方法
创建对象:
T newInstance(object... initargs)
newInstance
方法相当于C++中的虚拟构造器概念。如果使用空参数构造方法创建对象,操作可以简化:Class对象的
newInstance
方法
public static void main(String[] args) throws Exception {
//获取Person的Class对象
Class personClass = Person.class;
Constructor constructor = personClass.getConstructor(String.class,int.class);
System.out.println( constructor);
//创建对象
Object person = constructor.newInstance(...initargs:... );
System.out.println(person);
Constructor constructor = personClass.getConstructor() ;
System.out.println(constructor);
//创建对象
person = constructor.newInstance();
System.out.println(person);
person = personClass.newInstance();
//底层调用的是该类型的无参数构造方法,如果没异常有无参数构造方法会出现“实例化”异常
System.out.println(person) ;
}
有一个已经废弃的
Class.toInstance
方法,它也可以用无参数构造器构造一个实例。不过,如果构造器抛出一个检查型异常,这个异常将不做出任何检查重新抛出。这违反了编译时异常检查的原则。与之不同,Constructor.newInstance
会把所有的构造器异常包装到一个InvocationTargetException
中。异常分类:
非检查型(unchecked)异常
很多常见,例如:越界错误或者访问null引用,都属于非检查型异常。编译器并不希望你为这些异常提供处理器。程序员应该集中精力避免这些错误的发生,而不是将经历花在编写异常处理器上。
检查型(checked)异常
对于检查型异常,编译器将会检查程序员是否知道这个异常并做好准备来处理后果。
Method :方法对象
执行方法:
Object invoke(object obj,object... args)
获取方法名称:
String getName
public static void main(String[] args) throws Exception {
//获取Person的Class对象
Class personClass = Person.class;
//获取指定名称的方法
Method eat_method = personClass.getMethod("eat");
Person person = new Person();
//执行方法
eat_method.invoke(person);
eat_method = personClass.getMethod( name: "eat",String.class);
//执行方法
eat_method.invoke(p,"葡萄");
//获取所有public修饰的方法
Method[] methods = personClass.getMethods();//获取该类及其父类所有被public修饰的方法
for (Method method : methods) {
System.out.println(method) ;
String name = method.getName( );
System.out.println(name);
//method.setAccessible(true); 暴力反射
}
//获取类名
String className = personClass. getName( ) ;
System.out.println(className);//全类名 cn.mycompany.mypackage.Person
}
在java.lang.reflect
包中的三个类 Field、Method、Constructor分别用于描述类的字段、方法、构造器。
这三个类都有一个叫做 getName
的方法,用来返回字段、方法或构造器的名称。
在Field类有一个 getType
方法用来返回描述字段类型的一个对象,这个对象的类型同样是Class。
Method和Constructor有报告参数类型的方法,Method类还有一个报告返回类型的方法。
这三个类都有一个名为getModifiers
的方法,它将返回一个整数,用不同的0/1位描述所使用的修饰符,如public和static。可以利用java.lang.reflect
包中的Modifier
类的静态方法分析getModifiers
返回的这个整数,利用Modifier.toString
方法返回修饰符的字符串形式。
Class类中的 getFields
、getMethods
和 getConstructors
方法将分别返回这个类支持的公共字段、方法、构造器数组,其中包裹超类的公共成员。Class类的getDeclaredFields
、getDeclaredMethods
、getDeclaredConstructors
方法将分别返回类中声明的全部字段、方法和构造器的数组,其中包括私有成员、包成员和受保护成员,但不包括超类的成员。
案例
需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
实现:
- 配置文件
- 反射
步骤:
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类文件进内存
- 创建对象
- 执行方法
className=cn.mycompany.mypackage.Person
methodName=eat
public class ReflectDemo{//不改变该类的任何代码可以创建任意类的对象,执行任意方法。
public static void main(String[] args){
//1.加载配置文件
//1.1创建Properties对象
Properties properties = new Properties();
//1.2加载配置文件,转换为一个Map集合
//1.2.1获取class目录下的配置文件
ClassLoader classLoader = ReflectDemo.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("reflectDemo.properties");
properties.load(inputStream);
//2.获取配置文件中定义的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke (obj);
}
}
验证了反射机制的灵活性。
java代码写一遍,再不改变java源代码的基础之上,可以做到不同对象的实例化。非常灵活。( 符合OCP开闭原则:对扩展开放,对修改关闭。)
后期学习高级框架,而工作过程中也都是使用高级框架,
包括: ssm ssh
Spring SpringMVC MyBatis
Spring Struts Hibernate
这些高级框架底层实现原理:都采用了反射机制,所以反射机制还是重要的。
学会了反射机制有利于你理解剖析框架底层的源代码。
类加载器
关于JDK中自带的类加载器:
什么是类加载器?
ClassLoader,专门负责加载类的命令/工具。
JDK中自带了3个类加载器:
- 启动类加载器
rt. jar
- 扩展类加载器
ext/*. jar
- 应用类加载器
classpath
假设有这样一段代码:String s = "flow" ;
代码在开始执行之前,会将所需要类全部加载到JVM当中。通过类加载器加载,看到以上代码类加载器会找String.class
文件,找到就加载,那么是怎么进行加载的呢?
首先通过“启动类加载器”加载。
注意:启动类加载器专门加载:C:\Program Files\Java\jdk1 .8.0_101\jre\lib\rt.jar
rt.jar中都是JDK最核心的类库。
如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载。
注意:扩展类加载器专门加载: C: \Program Files\Java\jdk1.8.0_101\jre\lib\ext.jar
如果"扩展类加载器"没有加载到,那么会通过“应用类加载器"加载。
注意:应用类加载器专门加载:classpath中的类。
java中为了保证类加载的安全,使用了双亲委派机制。
优先从启动类加载器中加载,这个称为“父"。”父"无法加载到,再从扩展类加载器中加载,这个称为“母"。双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。