反射
环境:
Idea:2019.3.1
系统:windows10 家庭版
Jdk: 8
java中文api
一、 Java代码在计算机中经历的三个阶段
1. Source源代码阶段
我们编写的.java文件,通过javac.exe翻译成.class字节码文件,字节码文件中包含着
- 成员变量
- 构造方法
- 成员方法
还有比如类名等
2. Class类对象阶段
.class文件通过类加载器ClassLoader,加载为类对象
在java中有一个Class类,java.lang.Class
类
它是来描述所有.class字节码文件的共同特征和行为的类,不管是什么样的字节码文件都应该或多或少有以下特征
- 成员变量
将成员变量封装为Field []对象,因为可能有多个成员变量,所以它是一个数组 - 构造方法
将构造方法封装为Constructor []对象,因为可能有多个构造器,所以它是一个数组 - 成员方法
将成员方法封装为Method []对象,因为可能有多个成员方法,所以它是一个数组这三条即我们所说的反射机制
3. Runtime运行时阶段
通过第2阶段的class类的一些行为,比如构造方法,我们可以创建一个实例对象,然后执行相关操作
二、 概念
将类的各个组成部分封装为其他对象,这就是反射机制,反射是框架的灵魂
好处:
1.可以在程序运行过程中,操作这些变量
很明显的一个例子:
idea在运行中可以知道我们创建的变量中有哪些方法
这就是因为使用了反射,将String的所有方法封装到了method对象里面,然后遍历method数组就能帮我们显示所有的成员方法
2.可以解耦,提高程序的可扩展性
三、 获取Class对象的方法
1. java代码处于Source源代码阶段时
多用于配置文件,将类名定义在配置文件中,读取文件,加载类
Class.forName("全限定类名")
将字节码文件加载入内存,返回Class对象
全限定类名,即包名加类名的完整路径,我们加载jdbc的时候,使用过这个
示例:
Class.forName("com.jdbc.mysql.Driver");
ClassLoader.loadClass("全限定类名")
参考资料:ClassLoader.loadClass()与Class.forName()的区别
2. java代码处于Class类对象阶段时
多用于参数的传递
类名.class
通过类名的属性class获取
3. java代码处于Runtime运行阶段时
多用于通过对象获取字节码对象
对象.getClass()
getClass()方法在Object对象中定义
4. 测试
新建一个Person类
新建一个测试类,测试
package com.cbw;
import com.cbw.domain.Person;
public class test {
public static void main(String[] args) throws Exception {
//1.第一种方式创建Class类,Class.forName("全限定类名")
Class<?> aClass1 = Class.forName("com.cbw.domain.Person");
System.out.println("第一种方式创建:" + aClass1);
//2.第二种方式创建,类名.class获取
Class<Person> aClass2 = Person.class;
System.out.println("第二种方式创建:" + aClass2);
//3.第三种方式创建,对象.getClass()
Person person = new Person();
Class<? extends Person> aClass3 = person.getClass();
System.out.println("第三种方式创建:" + aClass3);
//4.比较这几个引用的地址是否相同
System.out.println(aClass1 == aClass2);
System.out.println(aClass1 == aClass3);
}
}
运行结果
这里有个细节,就是比较了这三个Class对象的引用地址,发现是一样的,所以是同一个Class对象
我们得出一个结论
同一个字节码文件(.class)在一次程序运行中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个
四、 Class对象的方法
1. 获取方法
1.获取成员变量
* Field [] getFields():获取所有public修饰的成员变量
* Field getField(String name):获取指定名称的public修饰的成员变量
* Field [] getDeclaredFields():获取所有的成员变量, 不考虑修饰符
* Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
Field对象方法:
1.设置值
void set(Object obj, Object value)
2.获取值
get(Object obj)
示例:
//1.获取Class对象
Class<?> aClass = Class.forName("com.cbw.domain.Person");
//2.获取成员变量对象 (使用的这个方法获取的成员变量必须是public修饰的)
Field name = aClass.getField("name");
//3.设置成员变量的值
//3.1 创建person对象
Person p = new Person();
//3.2 设置p的成员变量a的值
name.set(p,"张三")
//4.获取成员变量的值
Object value = name.get(p);
注意:
如果使用getDeclaredField这种方法,无视修饰符获取的时候,我们需要加入一行代码
name.setAccessible(true);//设置忽略访问权限修饰符的安全检查,这也叫作暴力反射,不这样设置会报错
2.获取构造方法
* Constructor<?> [] getConstructors()
* Constructor<T> getConstructor(类<?>...parameterTypes)
* Constructor<?> getDeclaredConstructor(类<?>... parameterTypes)
* Constructor<?> [] getDeclaredConstructor()
Constructor对象方法:
1.构造实例
T newInstance(Object...initargs) //这里面就传获取构造器的时候获取的哪个构造器对应的参数
示例:
//1.获取Class对象
Class<?> aClass = Class.forName("com.cbw.domain.Person");
//2.获取构造器对象
Constructor constructor = aClass.getConstructor(String.class, int.class);//这里我们使用的是有参构造方法,参数是什么,就要传什么类型的字节码文件
//3.使用构造器创建实例
Object person = constructor.newInstance("张三",15);
//也可以使用空构造方法创建实例
Constructor constructor = aClass.getConstructor();
Object person = constructor.newInstance();
//硬要使用空构造器的话,还有一种方法更简单,使用Class对象的方法,不过,在我写这篇文章的时候,这个方法已经过期了,但是也能够使用
Object person = aClass.newInstance();
//补充:
获取构造方法的这些方法和获取成员变量的是类似的,前两个只能获取public类型的构造方法,后两个可以无视修饰符获取构造方法,但是别忘了执行:
constructor.setAccessible(true);
3.获取成员方法
* Method [] getMethods()
* Method getMethod(String name, 类<?>... parameterTypes)
* Method [] getDeclaredMethods()
* Method getDeclaredMethod(String name, 类<?>... parameterTypes)
Method对象方法
1.执行方法
Object invoke(Object obj,Object......args);//第一个参数是该方法所属类的示例对象,后面的参数是该方法的参数
2.获取方法名
String getName();
示例:
//1.获取Class对象
Class<?> aClass = Class.forName("com.cbw.domain.Person");
//2.获取成员方法对象
//aClass.getMethod(方法名,参数列表的各个参数的字节码文件);无参方法可以只传方法名
Method eat_method1 = aClass.getMethod("eat");
Method eat_method2 = aClass.getMethod("eat",String.class);
//3.执行
Person p = new Person();
eat_method1.invoke(p);//p吃东西
eat_method2.invoke(p,"水果");//p吃水果
//补充:
1.还是一样,没带declared则只能获取public修饰的成员方法,带了的方法可以获取任意修饰符修饰的成员方法,但是别忘了
method.setAccessible(true);
2.获取多个方法的方法会将自己的,还有继承的类的方法都获取到,Object类的都能获取到
4.获取类名
* String getName()
示例:
//1.获取Class对象
Class<?> aClass = Class.forName("com.cbw.domain.Person");
//2.获取类名
//这个类名是全限定类名,从包名到类都有
String aClassName = aClass.getName();
五、 模拟写一个简易框架
Person类
package com.cbw.domain;
/**
* 用户类
*/
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void eat(){
System.out.println("吃烧烤");
}
}
ReflectFactory类
package com.cbw.domain;
import java.lang.reflect.InvocationTargetException;
/**
* 反射工厂类,可以创建任意实例,执行任意方法
*/
public class ReflectFactory {
/**
* 获取一个实例
* @param classpath
* @return
*/
public static Object getClassObject(String classpath) {
try {
//获取Class类
Class<?> aClass = Class.forName(classpath);
//返回一个实例对象
return aClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 执行obj对象的methodName方法
* @param obj
* @param methodName
*/
public static void methodInvoke(Object obj,String methodName){
try {
//执行方法
obj.getClass().getMethod(methodName).invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
propertie.properties配置文件
className=com.cbw.domain.Person
methodName=eat
Test测试类
package com.cbw;
import com.cbw.domain.ReflectFactory;
import java.io.InputStream;
import java.util.Properties;
public class test {
public static void main(String[] args) throws Exception {
//1.加载配置文件
Properties properties = new Properties();
//1.1获取类加载器
ClassLoader classLoader = test.class.getClassLoader();
//1.2获取配置文件的字节流
InputStream resourceAsStream = classLoader.getResourceAsStream("propertie.properties");
//1.3加载字节流
properties.load(resourceAsStream);
//2.获取配置文件下定义的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//3.创建实例
Object classObject = ReflectFactory.getClassObject(className);
//4.执行方法
ReflectFactory.methodInvoke(classObject,methodName);
}
}
运行结果
我们可以新建一个Student类
package com.cbw.domain;
public class Student {
public void study(){
System.out.println("学生学习");
}
}
然后修改配置文件
className=com.cbw.domain.Student
methodName=study
再次执行test方法
可以发现仅仅修改了配置文件,执行的对象和方法都能改变,这也就是利用反射实现了解耦的思想