理论
每个被我们写好的类被编译后都生成了对应的class文件,当被类加载器加载后,就会产生对应的Class类的实例,所以我们写在Java里面的东西自然而然,它也具备,我把它分成三个部分看,成员变量,构造方法,成员方法,然后Java提供了对应方法进行获取。
获取Class类实例的方法
-
Class.forName(“全限定名”);
* 将字节码文件加载进内存,返回Class对象
* 全限定名:包名+类名 -
类名.class;
* 通过类名的属性class获取 -
对象实例.getClass();
* 通过Object对象的方法getClass()获取
PS:
在运行过程中,一个字节码文件(xxx.class)只会被加载一次,不论通过哪种方式获取Class对象都会是同一个
Class实例的方法
-
获取Class基本信息
1.String getName();
* 获取全限定名
2.String getSimpleName();
* 获取简单类名 -
获取成员变量
1.Field[] getFileds();
* 获取所有public修饰的成员变量
2.Field getField(String name);
* 获取指定public修饰的成员变量
3.Field[] getDeclaredFileds();
* 获取所有声明的成员变量
4.Field getDeclaredField(String name);
* 获取指定声明的成员变量 -
获取构造方法
1.Constructor[] getConstructors();
* 获取所有public修饰的构造方法
2.Constructor getConstructor(Class… parameterTypes);
* 获取指定public修饰的构造方法
3.Constructor[] getDeclaredConstructors();
* 获取所有声明的构造方法
4.Constructor getDeclaredConstructor(Class … parameterTypes);
* 获取指定声明的构造方法 -
获取成员方法
1.Method[] getMethods();
* 获取所有public修饰的成员方法,包括父类的
2.Method getMethod(String name,Class… parameterTypes);
* 获取指定public修饰的成员方法
3.Method[] getDeclaredMethods();
* 获取所有声明的成员方法
4.Method getDeclaredMethod(String name,Class… parameterTypes);
* 获取指定声明的成员方法
Failed对象功能
-
设置值
void set(Object obj,Object value); -
获取值
Object get(Object obj); -
忽略访问权限修饰符的安全检查
void setAccessible(true); -
获取Class类型
Class getType();
Constructor对象功能
-
创建Class对象实例
T newInstance(Object… initargs); -
如果使用空参构造方法创建,可以简化操作
使用Class对象的 T newInstance(); -
忽略访问权限修饰符的安全检查
void setAccessible(true);
Method对象功能
-
调用(执行)方法
Object invoke(Object obj, Object… args); -
获取方法名称
String getName(); -
忽略访问权限修饰符的安全检查
void setAccessible(true);
实践
模拟框架实现调用任意方法,创建任意对象的功能
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
/**
* 需求:编写一个工具类 提供创建任意类 调用任意方法的功能
* 分析:
*
* @author JianAi
* @create 2019--01--11 11:54
*/
public class ReflectUtil {
public static void main(String[] args) throws Exception {
//创建一个Properties对象
Properties properties = new Properties();
//配置文件路径
String propertiesName = "proper.properties";
//获取需要实例化的对象的Class对象
Class<?> aClass =
Objects.requireNonNull(createNewObject(propertiesName,properties)).getClass();
//获取需要调用的方法的方法对象
Method method = invokeMethod(propertiesName, aClass,properties);
//忽略权限检查
Objects.requireNonNull(method).setAccessible(true);
//获取到构造方法
Constructor<?> constructor = aClass.getDeclaredConstructor();
//忽略权限检查
constructor.setAccessible(true);
//调用方法 实现调用方法的功能 传入的参数是一个实例化对象 如果是有参数的方法 后续还需要加上参数对应的.class 有几个写几个缺一不可
method.invoke(constructor.newInstance());
//获取到的参数数组
List<Class<?>> parameters = getParameters(propertiesName, aClass,properties);
//打印
System.out.println(parameters);
}
/**
* 获取传入的方法的参数
* @param propertiesName 配置文件路径
* @param clazz Class实例对象
* @return 参数类型数组
* @throws IOException
*/
private static List<Class<?>> getParameters(String propertiesName, Class<?> clazz,Properties properties) throws IOException {
//获取一个流对象 读取文件
try (FileInputStream fr = new FileInputStream(propertiesName)) {
//加载读取到的文件
properties.load(fr);
//获取配置文件中的方法名
String methodName = properties.getProperty("methodName");
//创建一个数组接收参数类型
List<Class<?>> list = new ArrayList<>();
//获取全部方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
method.setAccessible(true);
if (methodName.equals(method.getName())) {
//获取配置文件中方法的参数个数
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
//将获取到的参数类型添加进数组
list.add(parameter.getType());
//测试代码
Class<?> type = parameter.getType();
System.out.println(type);
}
}
}
return list;
}
}
/**
*
* @param propertiesName 配置文件路径
* @param clazz Class实例对象
* @return 方法对象
*/
private static Method invokeMethod(String propertiesName, Class<?> clazz,Properties properties) {
//获取一个流对象 读取文件
try (FileInputStream fr = new FileInputStream(propertiesName)) {
//加载读取到的文件
properties.load(fr);
//获取配置文件中的方法名
String methodName = properties.getProperty("methodName");
//获取到需要被调用的方法(这里其实有bug,如果调用有参数的方法,那还需要在后面传入参数的.class,比如String.class)
Method method = clazz.getDeclaredMethod(methodName);
//忽略权限检查
method.setAccessible(true);
//返回对应的方法实例对象
return method;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
*
* @param propertiesName 配置文件路径
* @return 实例对象
*/
private static Object createNewObject(String propertiesName,Properties properties) {
//获取一个流对象 读取文件
try (FileInputStream fr = new FileInputStream(propertiesName)) {
//加载读取到的文件
properties.load(fr);
//获取配置文件中的类名
String className = properties.getProperty("className");
//获取类加载器并加载配置文件中的class实例化对象
Class<?> clazz =
Class.forName("reflect.ReflectUtil").getClassLoader().loadClass(className);
//获取构造器 这里使用了暴力反射 防止无法获取到私有化的构造器(比如单例设计模式的构造器是私有化) 但是这里破坏了java的封装
Constructor<?> constructor = clazz.getDeclaredConstructor();
//忽略权限检查(没有这行代码实际你能获取到的还是只有声明了public的构造器)
constructor.setAccessible(true);
// 返回一个实例化对象(个人觉得这有点不完美,因为返回的是Object对象,如果有优化方案希望大佬能教教我)
return constructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
className=reflect.Person
methodName=eat
//配置文件
总结
Demo坑没填完,眼睛疼,有时间在抽空填。
总的来说,反射暂时了解就行,这样对使用框架配置文件的时候,心里有个底,为什么这么写就能实现,而不是一脸懵逼的看着大佬们的博客去无脑配置。希望有一天我这个菜鸟也能飞起来,如果哪位大佬对我的代码有优化方案,还希望能留言,衷心感谢。