注:看了几篇反射的内容,记录一下,方便后续学习。
原作者连接:https://juejin.cn/post/6864324335654404104
场景(纯属虚构):
1.项目中定义了一个HashMap进行数据存储。
2.后面需求变动,使用linkedHashMap更合适,就用linkedHashMap替换HashMap。重新编译上线。
3.万恶的需求又变了,感觉还是HashMap来存储更好,再次修改,编译,部署。
对于这种需求频繁变更但变更不大的场景,频繁的更改源码肯定是一种不允许的 操作。
我们先定义一个“开关”,判断什么时候用哪一种数据结构。根据动态的入参决定使用哪一个数据结构。
public MapgetMap(String param) { Map map = null; if (param.equals("HashMap")) { map = new HashMap<>(); } else if (param.equals("linkedHashMap")) { map = new linkedHashMap<>(); } else if (param.equals("WeakHashMap")) { map = new WeakHashMap<>(); } return map; }
但是如果某一天还想用TreeMap,还要修改源码。这个时候,反射就排上了用场。
总结:
在代码运行之前,我们不确定将来要使用哪一种数据结构,只有程序在运行时才决定使用哪一种数据。反射,就可以在程序运行过程中动态的获取类信息和调用类方法。
使用反射后,就不需要通过new来增加新的数据结构了,只需要传入className就可以动态获取对应的对象。使用反射的好处:
1.不需要在编译器就把对象的类型确定下来。
2.如果发生需求变更,可以通过变更开关来实例化不同的数据结构。
3.如果扩展类有非常多,就会创建出非常多的分支。使用反射,就可以在运行时才确定使用哪一个数据类,在切换类时,无需要重新修改源码,编译程序。
public Map反射的基本使用getMap(String className) { Class clazz = Class.forName(className); Consructor con = clazz.getConstructor(); return (Map ) con.newInstance(); }
java反射有主要组成部分如下:
- Class:任何运行在内存中的所有类都是该Class类的实例对象,每个Class类对象内部都包含类的所有信息。通过反射干任何事,先找到Class准没错。
- Field:描述一个类的属性,内部包含了改属性的所有信息,例如数据类型,属性名,访问修饰符…。
- Constructor:描述一个类的构造方法,内部包含了构造方法的所有信息。例如参数类型,参数名字,访问修饰符…。
- Method:描述一个类的所有方法,包含了该方法的所有信息,云Constructour类似,不同之处是Method拥有返回值类型信息,因为构造方法是没有返回值的。
以下是原作者的图:
1. 获取类的Class对象
2. 构造类的实例化对象
3. 获取类中的变量Field
Field[] getFields():获取类中所有被public修饰的所有变量
Field getField(String name):根据变量名获取类中的一个变量,该变量必须被public修饰
Field[] getDeclaredFields():获取类中所有的变量,但无法获取继承下来的变量
Field getDeclaredField(String name):根据姓名获取类中的某个变量,无法获取继承下来的变量
4. 获取类中的方法
Method[] getMethods():获取类中被public修饰的所有方法
Method getMethod(String name, Class…> paramTypes):根据名字和参数类型获取对应方法,该方法必须被public修饰
Method[] getDeclaredMethods():获取所有方法,但无法获取继承下来的方法
Method getDeclaredMethod(String name, Class…> paramTypes):根据名字和参数类型获取对应方法,无法获取继承下来的方法
通过反射获取到某个 Method 类对象后,可以通过调用invoke方法执行。
invoke(Oject obj, Object… args):参数``1指定调用该方法的对象,参数2`是方法的参数列表值。
如果调用的方法是静态方法,参数1只需要传入null,因为静态方法不与某个对象有关,只与某个类有关。
5. 反射的应用场景
1.spring的ioc容器
定义bean标签
定义ClassPahXmlApplicationContext刷新应用上下文。
public class Main {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
ReflectDemo reflectDemo= (ReflectDemo ) ac.getBean("reflectDemo");
smallPineapple.getInfo(); // [小熊的年龄是:18]
}
}
ioc容器本质上就是一个工厂,通过该工厂传入bean标签中定义的id获取对应的实例。
bean的创建过程请自行查询资料。
2.反射 + 抽象工厂模式
传统的工厂模式,如果需要生产新的子类,需要修改工厂类,在工厂类中增加新的分支;
public class MapFactory {
public Map
通过反射,工厂类就不用修改任何东西。当子类确定下来时,工厂就可以生成该字类了。
反射+抽象工厂的核心思想时:
在运行时通过参数传入不同子类的全限定名获取到不同的 Class 对象,调用 newInstance() 方法返回不同的子类。细心的读者会发现提到了子类这个概念,所以反射 + 抽象工厂模式,一般会用于有继承或者接口实现关系。
public class MapFactory {
public Map
className 可以指定为 java.util.HashMap,或者 java.util.TreeMap 等等,根据业务场景来定。
3.jdbc加载数据库驱动类
在导入第三方库时,JVM不会主动去加载外部导入的类,而是等到真正使用时,才去加载需要的类,正是如此,我们可以在获取数据库连接时传入驱动类的全限定名,交给 JVM 加载该类。
public class DBConnectionUtil {
private static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
public static Connection getConnection() {
Connection conn = null;
// 加载驱动类
Class.forName(DRIVER_CLASS_NAME);
// 获取数据库连接对象
conn = DriverManager.getConnection("jdbc:mysql://···", "root", "root");
return conn;
}
}
在我们开发 SpringBoot 项目时,会经常遇到这个类,但是可能习惯成自然了,就没多大在乎,我在这里给你们看看常见的application.yml中的数据库配置,我想你应该会恍然大悟吧。
这里的 driver-class-name,和我们一开始加载的类是不是觉得很相似,这是因为MySQL版本不同引起的驱动类不同,这体现使用反射的好处:不需要修改源码,仅加载配置文件就可以完成驱动类的替换。
优点:
增加程序的灵活性
缺点:
1.破坏类的封装性,可以强制访问private修饰的信息。
2.性能损耗,反射相比直接实例化对象,带哦用方法,访问变量,中间需要非常多的检查步骤与分析步骤,jvm无法对他们优化。



