在上一篇的文章中,笔者简要地写了如何通过反射机制来获取类对象。对于一个Java中的类对象来说,通过反射机制能够访问到整个类中包含的所有东西,总结下来主要是:Class对象、Field对象、Method对象、Constructor对象和Modifier对象。今天我们来谈谈如何使用这些对象来构造实例化这个目标类的对象,再者,我们来深入学习一下反射。
1.使用反射获取的各类对象
主要是Class、Constructor、Field和Method。
package com.unicorn.reflect.service;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import org.junit.Test;
/**
* 通过反射改变域的值,也就是不通过setter来改变类属性的值
* @author Unicorn
*
*/
public class ModifyingField {
@Test
public void modifyFieldsVal(){
/**
* 回顾一下获取Class对象的办法:
* 笔者前面说过有四种,其实概括来说应该是三种
* 1.通过类对象的getClass()方法
* 2.通过类的静态属性.class
* 3.通过静态方法Class.forName(类的全限定名):做开发常用
*/
try {
Class<?> cl = Class.forName("com.unicorn.reflect.pojo.Emp");
Constructor<?> cons = cl.getDeclaredConstructor();//如果没有这个构造器会触发NoSuchMethodException
Object obj = cons.newInstance();//可以传入参数的.class来调用不同的构造器,如果没有参数,那么默认调用的是无参构造器
//如果这个构造器不存在,就会触发InstantiationException
//属性是可以通过字符串来获取相应的变量
Field f1 = cl.getDeclaredField("empName");//如果这个Field不存在,会触发一个NoSuchFieldException
//而且,如果这field的访问权限是private或者protected,反射时Java进行权限检查,这时是不允许访问的
//但是我们可以暴力访问,强制修改权限
f1.setAccessible(true);
f1.set(obj, "123");//这里使用了set,如果没有进行accessible暴力改变权限会触发IllegalAccessibleException
System.out.println(obj);
} catch (ClassNotFoundException | NoSuchFieldException
| SecurityException | IllegalArgumentException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package com.unicorn.reflect.service;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.junit.Test;
/**
* 通过反射来调用类中的方法,
* @author Unicorn
*
*/
public class ModifyingMethod {
@Test
public void callingMethods(){
try {
//获取类
Class<?> cl = Class.forName("com.unicorn.reflect.pojo.Emp");
//获取构造器
Constructor<?> cons = cl.getDeclaredConstructor(Long.class);
cons.setAccessible(true);
Object obj = cons.newInstance(6895L);
//获取方法
Method m = cl.getDeclaredMethod("setEmpName", String.class);//第一个参数基本是需要获取对象的名称
//如果访问权限是private
m.setAccessible(true);
m.invoke(obj, "笔者棒极了!");
System.out.println(obj);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
使用反射过程:
a.获取类的Class
b.获取类的Constructor
c.通过获取的Constructor来构造Object对象
d.访问Field or Method
2.深入学习反射
我们先来了解下在反射执行之前都发生了什么。
2.1.类的加载
当程序需要使用某个类的时候,假如这个类还没有被加载到内存(jvm->Java virtual machine)中,那么系统会三个步骤实现对这个类的初始化,简要来说就是:加载、连接和初始化。
什么是加载?
加载就是指将.class文件读入内存,并为其创建一个Class对象;任何类在被使用的时候,系统都会为该类创建一个Class对象。
什么是连接?
连接主要是包括三个步骤:
验证(检验这个类是否具有正确的内部结构冰河其他的类协调一致)
准备(负责为类的静态成员分配内存并设置默认初始化值)
解析(将类的二进制数据中的符号引用替换为直接引用)
什么是初始化?
这个阶段主要是开始执行Java的代码,负责为类变量(类的静态变量)赋予正确的初始化值,此时Java编译器把所有的类变量的术赤化语句和静态初始化器全部收集到< clinit >()方法中,该方法只能被jvm调用,专门来用于初始化。如下的5个情况才能主动触发类的初始化过程:
- 第一种:遇到new、getstatic、putstatic和invokestatic这四条字节码指令时,此时如果类还没有进行初始化,则需要先触发其初始化。
- 第二种:使用Java.lang.reflect.*;的方法对类进行反射调用时,如果类还没有进行初始化,则需要先触发其初始化。
- 第三种:当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 第四种:当jvm启动时,用户需要指定一个要执行的主类,jvm会先执行该主类。
- 第五种:当使用JDK1.5支持时,如果一个java.lang.incoke.MethodHandler实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
总结起来,规范类的初始化时间为:在首次主动使用时初始化(initialize on first active use)。
另一种简单的说法是:
- 创建类的实例–new,反射,克隆或反序列化
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化一个类的子类
- 直接使用java.exe命令来运行某个主类(main)
2.2类加载器
负责将.class文件加载到内存(jvm)中,并为之生成对应的Class对象。
类加载器的组成主要包括:
Boostrap ClassLoader(根类加载器,或称引导类加载器:负责Java核心类的加载,主要是在jdk中jre的lib目录下的rt.jar文件中)
Extension ClassLoader(拓展类加载器:负责JRE的扩展目录中jar包的加载,主要在在jdk中jre的lib目录下ext目录)
System ClassLoader(系统类加载器:负责在jvm启动时加载来自Java命令的.class文件,以及classpath环境变量所指定的jar包和类路径)
2.3反射机制
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
2.4反射中最重要的使用–动态代理
动态代理:在程序运行过程中产生的对象,也就是通过反射生成的对象。
动态代理技术主要包括Proxy(对接口类的代理,主要由jdk提供,这里主要做的是面向接口编程)和CGLib(类对象的代理,主要由cglib.jar提供,常在框架中使用)。
这次就先写到这了,下一篇主要写写这个动态代理技术,嘿嘿!
转载请注明出处,谢谢!