想要理解反射,我认为一定要从底层开始理解才能真正懂得什么是反射。
首先我们写好的.java文件在经过编译后会转换为.class文件存入JVM的方法区内,现在我们要知道.class字节码文件中都有什么,这个文件中存有我们类中的所有属性方法以及构造器,甚至修饰符等等,除了注解以外的要素都有,都映射在JVM的方法区内。
好,现在我们提出反射的原理,反射就是将.class字节码文件中的所有要素都看成对象,所以现在,我要通过反射操作去获取到类中的方法属性和构造器,有没有可能。答案是有!我们都把这些元素看成对象了,我凭什么不能拿,凭什么不能改,能!!!
我们该怎么获取到这些东西呢?
我们想改.class文件中的内容,首先我们要获得调用内容的对象吧,一般我们是怎么获得对象的
new 一个对象();
但在反射里我们怎么做的
//首先我们要获得这个类
//这里是完整的包名到类名
Class<ReflectTest> ref = Class.forName("com.atAchao.reflect.ReflectTest");
Object refInstance = ref.getConstrctor().newInstance();
这里refInstance就是当前的对象,但我们并不能直接像原来写代码一样,直接通过对象.方法调用方法,因为我们此时调用的方法,参数等等都存在JVM的方法区中,不通过.Class类对象是取不出来的。
//这里我们需要ref对象去获取类内的方法和属性等等
Method method = ref.getMethod("方法名", String.class);
得到方法之后,通过invoke方法来使用方法
method.invoke(调用对象, 具体参数);
我们介绍完了反射大概怎么用,现在要知道具体的方法
以下方法都是通过Class类对象调用的
Method[] getMethods();
获得类内所有非私有方法,以及父类的非私有方法
Method[] getDeclaredMethods();
【暴力】获得类内所有方法,包括私有方法,但不包括父类中的任何方法
Method getMethod(String methodName, Class... parameterTypes);
获得类内methodName方法,并提供参数列表的【类型.class】参数
Method getDeclaredMethod(String methodName, Class... parameterTypes);
【暴力】获得类内methodName方法,并提供参数列表的【类型.class】参数
Object invoke(Object obj, Object... parameterValues);
这里通过上面两个方法对象调用invoke方法,传入第一个参数为想要调用方法的真正对象,之后为具体参数
Field[] getFields();
获得类内所有参数,返回值类型为数组
Field[] getDeclaredFields();
【暴力】获得类内所有参数,返回值类型为数组
Field getField(String fieldName);
获取类内一个指定参数
Field getDeclaredField(String fieldName);
【暴力】获取类内一个指定参数
void set(Object obj, Object value);
这里通过上面两个属性对象调用set方法,传入第一个参数为想要调用方法的真正对象,之后为具体值
Object get(Object obj);
这里也是通过上面两个属性对象调用get方法,参数为想要调用方法的真正对象
void setAccessible(boolean flag);
反射操作中 Constructor, Method ,Field 对象都可以调用该方法解决 私有化反射对象权限操作问题
这个方法是在暴力获取到方法后给予方法权限,使之能正常运行
public static void setAccessible(AccessibleObject[] array, boolean flag)
AccessibleObject 类工具方法,所需参数是 AccessibleObject 数组和对应的权限标记,flag 通常为 true。
Field Method Constructor 都是 AccessibleObject 子类
反射的用途,为什么搞这么复杂,比new一个对象复杂多了,所以到底在哪能用到反射呢
我认为用反射的原因在于,提高代码的灵活性,降低耦合度,并且能很好的提高代码的复用性,同时反射能隐藏掉很多细节,让使用者有刚好的体验
举几个例子吧
例子一:JDBC
Class.forName("com.mysql.jdbc.Driver");
//获取与数据库连接的对象-Connetcion
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/java001", "root", "root");
//获取执行sql语句的statement对象
statement = connection.createStatement();
//执行sql语句,拿到结果集
resultSet = statement.executeQuery("SELECT * FROM users");
后来变成了下面的样子
//获取配置文件的读入流
InputStream inputStream = UtilsDemo.class.getClassLoader()
.getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(inputStream);
//获取配置文件的信息
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
//加载驱动类
Class.forName(driver);
为什么变成这样呢,原因很简单,不想一行一行的看,找出要修改的地方,一行一行的修改,这样很麻烦,我把要改的地方写在配置文件里,统一修改不香吗,我想换数据库换数据库,我想改密码改密码,但读取配置文件的核心代码我一点也不用变,代码变灵活,降低耦合度,接口式编程,形成一套代码能对应多种配置文件。
例子二:写Serlvet
//通过html的name属性,获取到值
String username = request.getParameter("username");
String password = request.getParameter("password");
String gender = request.getParameter("gender");
//复选框和下拉框有多个值,获取到多个值
String[] hobbies = request.getParameterValues("hobbies");
String[] address = request.getParameterValues("address");
//获取到文本域的值
String description = request.getParameter("textarea");
springMVC怎么写的呢
@RequestMapping(value = "/bean")
@ResponseBody
public String missionSave(Config config) {
String name= config.getName();
}
自动装配,这是怎么实现的,就是通过反射实现的,通过字段名与参数名相同来自动获得相应的值,隐藏了许多细节,用户体验更好了。
其实在开发过程中,使用反射的机会真的不多,但一般我们在做自己的框架或组件的时候,就要用到反射了。
如有不足,私信或者评论指正,肥肠感激。🙋