反射概述
官方定义:
Reflection is a feature in the Java programming language. It allows an executing Java program to examine or "introspect" upon itself, and manipulate internal properties of the program. For example, it's possible for a Java class to obtain the names of all its members and display them.
反射是指一个运行中的Java程序可以检查、修改自己内部的属性,也可称之为自省。反射是Java有别于其它编程语言的一大特性。从reflection的字面意思看,就是倒影、反射,就好比你照镜子,通过倒影就能知道自己长什么样,理一理头发就能改变发型。
反射原理
JVM会动态加载Class,一个Class实例包含了该类的所有完整信息,如:包名、类名、各个字段、各个方法、父类、实现的接口等。因此,如果获取了某个类或对象的Class实例,就可以通过它获取到对应类的所有信息。
Class类对象的三种实例化模式
其中第一种是静态方法会产生实例化对象,而其他的不会产生实例化对象
我们来看一个例子就更好理解了
package Reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Reflection {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//加载类,返回Class类型的对象aClass
String s = "Reflection.Cat";
Class<?> aClass = Class.forName(s);
//通过aClass得到加载的类Reflection.Cat的对象实例
Object o = aClass.newInstance();
System.out.println("o的运行对象为:"+o.getClass());
//通过aClass得到加载类的"hi"方法对象
//即在反射中,可以把方法视为对象(万物皆对象)
Method method1 = aClass.getMethod("hi");
method1.invoke(o);
}
}
class Cat{
private String name;
public Cat(){
System.out.println("构造器被调用");
}
public void hi(){
System.out.println("方法被调用");
}
}
输出
这个是采取了反射实例化对象的方法来,注意newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
然后我们根据这个来画一张流程图就更加能理解反射了
反射相关类
- java.lang.Class 代表一个类,Class对象表示某个类加载后在堆中的对象
- Method:代表类的方法,Method 对象
- Field:代表类的成员变量
- Constructor:代表类的构造方法
这些类在 java.lang.reflection
Class类详解
基本介绍:
- Class也是类因此也是继承的Object类
- Class类对象不是new出来的,而是系统创建的
- 对于某个类的Class类对象,在内存中只有一份,因此类只加载一次
- 每个类的实例都会记得自己是由哪个Class实例所产生的
- 通过Class对象可以完整地得到一个类的完整结构,通过一系列的API
- Class类是放在堆中的
- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据
第一点在类图上就能很好的体现了
关于第二点我们来追一下源码就知道了
发现是一个loadClass的方法创建的,其他实例化模式同理,只是new更加直观
第三点也举例一下就行
输出
第四点就是涉及到了class的常用方法了
class的常用方法
Field[] getFields() //返回一个包含Field对象的数组,存放该类或接口的所有可访问公共属性(含继承的公共属性)。
Field[] getDeclaredFields() //返回一个包含Field对象的数组,存放该类或接口的所有属性(不含继承的属性)。
Field getField(String name) //返回一个指定公共属性名的Field对象。
Method[] getMethods() //返回一个包含Method对象的数组,存放该类或接口的所有可访问公共方法(含继承的公共方法)。
Method[] getDeclaredMethods() //返回一个包含Method对象的数组,存放该类或接口的所有方法(不含继承的方法)。
Constructor[] getConstructors() //返回一个包含Constructor对象的数组,存放该类的所有公共构造方法。
Constructor getConstructor(Class[] args)//返回一个指定参数列表的Constructor对象。
Class[] getInterfaces() //返回一个包含Class对象的数组,存放该类或接口实现的接口。
T newInstance() //使用无参构造方法创建该类的一个新实例。
String getName() //以String的形式返回该类(类、接口、数组类、基本类型或void)的完整名。
第六点和第七点根据这个图就很好理解了
这里再写一个常用方法的例子
package Reflection;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Reflection {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//1:加载类,返回Class类型的对象aClass
String s = "Reflection.Cat";
Class<?> aClass = Class.forName(s);
//2:输出aClass
System.out.println(aClass); //显示aClass是哪个类的运行对象
System.out.println(aClass.getClass());//显示aClass的运行类型
//3:得到包名
System.out.println(aClass.getPackage().getName());
//4:得到类名
System.out.println(aClass.getName());
//5:通过aClass得到加载的类Reflection.Cat的对象实例
Object o = aClass.newInstance();
System.out.println("o的运行对象为:"+o.getClass());
//6:通过aClass得到加载类的"hi"方法对象
//即在反射中,可以把方法视为对象(万物皆对象)
Method method1 = aClass.getMethod("hi");
method1.invoke(o);
//7:通过反射获取属性
Field b = aClass.getDeclaredField("name");//属性为私有属性
b.setAccessible(true);//暴力访问
Object c = b.get(o);//取值
System.out.println(c);
}
}
class Cat{
private String name;
public Cat(){
System.out.println("构造器被调用");
}
public void hi(){
System.out.println("方法被调用");
}
}
输出
获取Class类的方式
1:已知一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()获取
eg:Class aClass = Class.forName("Reflectiom.Cat")
2:若已知具体的类,通过类的calss获取,该方式最为安全可靠,性能最高
eg:Class aClass = Cat.class
3:已知某个类的实例,调用该实例的getClass()方法获取Class对象
eg:Class aClass = 对象.getClass
4:其他方式
eg:ClassLoader aClass = 对象.getClass().getClassLoader();
Class bClass = aClass.loadClass("类的全名称")
5:基本数据(int,char,booleam,float等)
eg:Class aClass = 基本数据类型.class
6:基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
eg:Class aClass = 包装类.TYPE
例子
package Reflection;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Reflection {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//1:Class.forName
Class<?> aClass = Class.forName("Reflection.Cat");
System.out.println("第一种:" + aClass);
//2:类名.class
Class bClass = Cat.class;
System.out.println("第二种:" + bClass);
//3:对象.getClass()
Cat cClass = new Cat();
Class dClass = cClass.getClass();
System.out.println("第三种:" + dClass);
//4:类加载器获取到类到Class对象
ClassLoader classLoader = cClass.getClass().getClassLoader();//得到类加载器
Class<?> eClass = classLoader.loadClass("Reflection.Cat");
System.out.println("第四种:" + eClass);
//5:基本数据得到Class类
Class<Integer> integerClass = int.class;
System.out.println("第五种:" + integerClass);
//6:基本数据类型对应到包装类得到Class
Class<Integer> type = Integer.TYPE;
System.out.println("第六种:" + type);
}
}
class Cat{
private String name;
public Cat(){
System.out.println("构造器被调用");
}
public void hi(){
System.out.println("方法被调用");
}
}
输出
再总结一下哪些类型有Class对象
1:外部类和内部类 2:接口 3:数组 4:枚举 5:注解 6:基本数据类型 7:void
静态加载和动态加载
反射机制是java实现动态语言的关键,就是通过反射实现类动态加载
再来区分一下静态加载和动态加载的区别
1:静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
2:动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性
理解很简单就不多解释了
类加载流程
类加载的时机
- 当创建对象时(new)
- 当子对象被加载时
- 调用类中的静态成员时
- 通过反射
类加载流程图
重点在类加载的三个阶段
加载阶段
JVM在在该阶段的主要目的是将字节码从不同的数据源(可能是class文件,也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象
这里又不得不提一下类加载器与双亲委派了
类加载器:
JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:
类加载器介绍:
BootstrapClassLoader(启动类加载器)
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,加载System.getProperty(“sun.boot.class.path”)所指定的路径或jar。
ExtensionClassLoader(标准扩展类加载器)
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包。载System.getProperty(“java.ext.dirs”)所指定的路径或jar。
AppClassLoader(系统类加载器)
负责记载classpath中指定的jar包及目录中class
CustomClassLoader(自定义加载器)
属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现。
类加载器顺序:
我们再来看一下loadClass源码就更好理解了
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 首先,检查是否已经被类加载器加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 存在父加载器,递归的交由父加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 直到最上面的Bootstrap类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
就是当一个Cat.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。
更加仔细的可以看看这篇文章:https://www.cnblogs.com/luckforefforts/p/13642685.html
继续回到我们刚才的加载图
连接阶段-验证
- 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 文件格式验证(是否以魔数oxcafebabe开头),元数据验证,字节码验证和符号引用验证
- 可以考虑使用-Xverify:none参数来关闭大部分的类验证措,缩短虚拟机加载的时间
在源码中我们会生成一个SecurityManager的对象用于验证
连接阶段-准备
- JVM会在该阶段对静态变量,分配内存并初始化(对于数据类型的默认值),这些变量所使用的内存都将在方法区中分配
例子
class test{
public int n1 = 10;//实例变量,不是静态属性,因此在准备阶段是不会分配内存的
public static int n2 = 20;//默认初始化值为0
public static final int n3 = 30;//static final是常量,一旦赋值就不能变
}
连接阶段-解析
- 虚拟机将常量池内的符号引用替换成直接引用的过程。
初始化
- 到初始化阶段,才是真正开始执行类中定义的java程序代码,此阶段是执行<clint>()方法的过程。
- <clint>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量赋值动作和静态代码块中的语句,并进行整合
- 虚拟机会保证一个类的<clint>()方法在多线程环境中被正确地加锁,同步,如果多个线程区同时初始化一个类,那么只有一个线程区执行这个方法,其他线程都需要阻塞等待,直到这个活动线程执行完毕。
反射的应用
反射获取类的结构信息
我自己写了个例子
package ClassLoder;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ClassLoder {
public static void main(String[] args) throws ClassNotFoundException {
//得到Class对象
Class<?> aClass = Class.forName("ClassLoder.test");
//得到全类名
System.out.println(aClass.getName());
//获取简单类名
System.out.println(aClass.getSimpleName());
//获取所有pulic属性,包括父类的
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println("本类和父类的public属性:" + field.getName());
}
//获取本类所有属性
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类所有属性:" + declaredField.getName());
}
//获取所有pulic方法,包括父类的
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println("本类和父类的public方法:" + method.getName());//父类还有Object
}
//获取本类所有方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Field field : declaredFields) {
System.out.println("本类的所有方法:" + field.getName());
}
//获取本类所有的public修饰的构造器
Constructor<?>[] constructors = aClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("本类的public构造器" + constructor.getName());
}
//本类的所有构造器
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("本类所有构造器" + declaredConstructor.getName());
}
//以package的形式返回包信息
System.out.println(aClass.getPackage());
//以Class的形式返回父类信息
Class<?> superclass = aClass.getSuperclass();
System.out.println("父类信息" + superclass);
//得到接口返回信息
Class<?>[] interfaces = aClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("接口返回信息:" + anInterface);
}
//得到注解信息
AnnotatedType[] annotatedInterfaces = aClass.getAnnotatedInterfaces();
for (AnnotatedType annotatedInterface : annotatedInterfaces) {
System.out.println("注解信息:" + annotatedInterface);
}
}
}
class Class001{
public String hobby;
public void dispaly(){}
public Class001(){}
}
class test extends Class001 implements IA,IB{
@Deprecated
//属性
public String name;
protected int age;
String job;
private double sal;
//方法
public void m1(){}
protected void m2(){}
void m3(){}
private void m4(){}
//构造器
public test(){}
public test(String name,String age){}
private test(String name,String age,double sal){}
}
interface IA{}
interface IB{}
就是所见及所得,很简单的
然后我们重点来看这几个
java.lang.reflect.Field类
- getModifiers:以int的形式返回修饰符(默认为0,public为1,private为2,protected为4,final为6,static为8)
- getType:以Class的形式返回类型
- getNamei:返回属性名
例子
package ClassLoder;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ClassLoder {
public static void main(String[] args) throws ClassNotFoundException {
//得到Class对象
Class<?> aClass = Class.forName("ClassLoder.test");
//获取本类所有属性,如果是组合关系就相加
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类所有属性:" + declaredField.getName()
+" ,本类属性的修饰符:" + declaredField.getModifiers()
+" ,该属性的类型返回Class类为:" + declaredField.getType());
}
}
}
class test{
@Deprecated
//属性
public String name;
protected int age;
String job;
private double sal;
//方法
public void m1(){}
protected void m2(){}
void m3(){}
private void m4(){}
//构造器
public test(){}
public test(String name,String age){}
private test(String name,String age,double sal){}
}
输出
java,lang.reflect.Method类
- getModifiers:以int的形式返回修饰符(默认为0,public为1,private为2,protected为4,static为8,final为16)
- getReturnType:以Class的形式返回类型
- getNamei:返回方法名
- getParameterTypes:以Class[]返回参数类型数组
java.lang.reflect.Constructor类
- getModifiers:以int的形式返回修饰符
- getNamei:返回构造器名(全类名)
- getParameterTypes:以Class[]返回参数类型数组
和前面差不多的就不用举例了
反射爆破创建实例
首先我们得要知道怎么通过反射机制创建实例
- 方式一:调用类中的public修饰无参构造器
- 方式二:调用类中指定构造器
然后是相关类的操作
Class类的相关方法
- newInstance:调用类中的无参构造器,获取对应类的对象
- getConstructor(Class...clazz):根据参数列表,获取对应的构造器对象
- getDecalaredConstructor(Class...clazz):根据参数列表,获取对应的构造器对象
Constructor类相关方法
- setAccessible:爆破
- newInstance(Object...obj):调用构造器
实例
package ReflectionInstance;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectionInstance {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//1:通过User类得到Class类
Class<?> aClass = Class.forName("ReflectionInstance.User");
//2:通过public的无参构造器创建实例
Object o = aClass.newInstance();
System.out.println(o);
//3:通过public的有参构造器创建实例
Constructor<?> constructor = aClass.getConstructor(String.class);
Object m1 = constructor.newInstance("小明");
System.out.println(m1);
//4:通过非public的有参构造器创建实例
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);//爆破【暴力破解,使用反射可以访问私有的构造器
Object m2 = declaredConstructor.newInstance("小红", 6);
System.out.println(m2);
}
}
class User{
private int age = 10;
private String name = "李华";
//无参构造器
public User(){}
//public有参构造器
public User(String name){
this.name=name;
}
//private有参构造器
private User(String name,int age){
this.age=age;
this.name=name;
}
public String toString(){
return "User [age=" + age +",name=" + name +"]";
}
}
输出
反射爆破操作属性
涉及到的方法
- getDeclaredField:根据属性名来获取Field对象
- setAccessible:爆破
- set(实例对象.值),syso(对象.get(实例对象)):访问
- 如果是静态属性,则set和get中的实例对象可以写成null
实例
package ReflectionProperties;
import java.lang.reflect.Field;
public class ReflectionProperty {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//1:得到Student类对应的Class对象
Class<?> aClass = Class.forName("ReflectionProperties.Student");
//2:实例化对象
Object o = aClass.newInstance();//o的运行类型救赎Student
//2:反射得到操作public的age属性
Field age = aClass.getField("age");
age.set(o,10);
System.out.println(o);
//3:反射操作name属性[私有并且静态]
Field name = aClass.getDeclaredField("name");
name.setAccessible(true);//爆破私有属性
name.set(null,"李华");
System.out.println(o);
}
}
class Student{
public int age;
private static String name;
public Student(){}
public String toString(){
return "Student [age=" + age + ",name=" + name + "]";
}
}
输出
反射爆破操作方法
同样需要注意几点
- getDeclaredMethod:根据方法名和参数列表获取Method方法对象
- newInstance:获取实例化对象
- setAccessible:暴破
- invoke(实例化对象,实参列表):访问
- 如果是静态方法,则invoke的实例化对象参数可以写成null
实例
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflecAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//1:得到Test类的Class对象
Class<?> aClass = Class.forName("Test");
//2:得到实例化对象
Object o = aClass.newInstance();
//3:反射调用public的display方法对象
Method display = aClass.getMethod("display",String.class);
display.invoke(o,"李华");
//4:调用private static的say方法
Method hit = aClass.getDeclaredMethod("hit",String.class);
hit.setAccessible(true);
hit.invoke(o,"爱你孤身走暗巷~");
}
}
class Test{
public int age;
private static String name;
public Test(){}
private void hit(String c){
System.out.println(c);
}
public void display(String s){
System.out.println("hi,"+s);
}
}
输出
反射的更多利用:https://www.w3cschool.cn/java/java-reflex.html
反射总结
反射优点
- 增加程序的灵活性,避免将程序写死到代码里。
- 代码简洁,提高代码的复用率,外部调用方便。
反射缺点
1.性能问题
使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。
反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程序中使用反射。
2.使用反射会模糊程序内部逻辑
程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。
3.安全限制
使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是个问题了。
4.内部暴露
由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
所以,总的来说,Java反射机制实际上是一把双刃剑,我们只有熟练掌握Java反射的优缺点,才能妥善使用Java反射这一利器,为我们的编程扫清障碍而不至于影响到我们的程序本身。
声明:本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担! 本网站采用BY-NC-SA协议进行授权!转载请注明文章来源! 图片失效请留言通知博主及时更改!