[Java]反射——框架设计的灵魂

tech2026-02-13  0

前沿: 反射——框架设计的灵魂 使用框架,不会反射也没关系,因为框架已经写好了,并不需要用反射; 开发框架,写框架,需要用到反射; 理解反射,掌握反射,在学习框架,使用框架时,也能使用的更好。

1. 反射概念

反射:将类的各个组成部分封装为其他对象,这就是反射机制

将类的成员变量封装为Filed对象;将类的构造方法封装为Constructor对象;将类的成员方法封装为Method对象。

Java代码在计算机中经历的三个阶段:

Source 源代码阶段 源代码阶段代码还在硬盘Class 类对象阶段Runtime 运行时阶段

字节码文件Person.class要变成Person对象(Person对象在内存中),即需要把字节码文件Person.class加载进内存后才能有Person对象。所以在第二个阶段需要用类加载器 ClassLoader 把字节码文件加载进内存。 在内存中如何描述字节码文件Person.class?在Java中万物皆对象,所以在内存中会有一个对象来描述这个字节码文件,这个对象即为 Class 类对象。 所有第三阶段的对象,如Person对象都是通过这些 Class 类对象创建出来的。

Java中有一个 Class 类,描述所有字节码文件,这些物理文件的共同特征和行为。 不管是什么字节码,或多或少都有一些:成员变量、构造方法、成员方法。所以Class 类对象有三部分比较重要的东西:成员变量、构造方法、成员方法。这三者是三部分不同的东西,每一部分有每一部分的特征,把这三部分分别封装为不同的对象:将类的成员变量封装为Filed对象;将类的构造方法封装为Constructor对象;将类的成员方法封装为Method对象。

反射好处 在程序的运行过程中,操作这些对象。可以解耦,降低程序的耦合性,降低程序的紧密程度,来提高程序的可扩展性。

2. 2. 获取字节码Class类对象的三种方式

如果Java代码在第一个阶段,意味着Java只有字节码文件,并没有进内存,我们需要手动将它加载进内存,生成字节码Class类对象。 Class.forName(“全限定类名”) :将字节码文件加载进内存,返回Class类对象。 全限定类名=包名.类名 forName(“全限定类名”) 是静态方法,通过类名 Class 可以直接打点调用。

如果在第二阶段,已经将字节码文件加载进内存,也就是说这个Class类对象已经有了,那么不需要加载它,只需要获取它即可。 类名.class:通过类名的属性class获取

如果在第三阶段,已经有对象,可以通过对象的方法来获取。 对象.getClass() getClass()方法是在Object类中定义的,被所有的对象继承。

结论 同一个字节码文件(XXX.class)在一次程序运行过程中,只会被加载一次,不论通过以上哪一种方式获取的Class类对象都是同一个。

三种方式适用场合

Class.forName(“全限定类名”) 多用于配置文件,将类名定义在配置文件中。读取配置文件,加载类。 因为Class.forName(“全限定类名”)传的是字符串,所以从配置文件中读出来的也是字符串。类名.class 多用于参数的传递。传参,传Class类对象的话,用此方式较为方便。对象.getClass() 多用于对象的获取字节码的方式。有对象了,想获取字节码文件对象,可以用此方式获取。

3. Class类对象功能

获取功能

获取成员变量们 Field[] getFields():获取所有public修饰的成员变量(以下同理)Field getField(String name):获取指定名称的 public修饰的成员变量(以下同理)Field[] getDeclaredFields():获取所有成员变量,不考虑修饰符(以下同理)Field getDeclaredField(String name) 获取构造方法们 Constructor<?>[] getConstructors()Constructor getConstructor(类<?>… parameterTypes)Constructor getDeclaredConstructor(类<?>… parameterTypes)Constructor<?>[] getDeclaredConstructors() 获取成员方法们 Method[] getMethods()Method getMethod(String name, 类<?>… parameterTypes) 方法具有三要素:方法名、返回值列表、参数列表确定一个方法有两要素:方法名、参数列表 因为方法名一样,参数列表不一样 Method[] getDeclaredMethods()Method getDeclaredMethod(String name, 类<?>… parameterTypes) 获取全限定类名 String getName()

成员变量(Field)对象对应的方法 操作:

设置值 void set(Object obj, Object value)获取值 get(Object obj)忽略访问权限修饰符的安全检查 setAccessible(true):暴力反射 在反射面前没有是隐私的 Field、Constructor、Method里都有这么一个方法

构造方法(Constructor)对象对应的方法 构造器对象用来创建对象: * T newInstance(Object… initargs) * 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法——Class对象.newInstance()(常用此操作)

成员方法(Method)对象对应的方法

执行方法: Object invoke(Object obj, Object… args)获取方法名称: String getName():获取方法名

4. 案例1

需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法 实现: 配置文件反射 步骤: 将需要创建的对象的全类名和需要执行的方法定义在配置文件中;在程序中加载并读取配置文件;使用反射技术来加载类文件进内存;创建对象;获取方法对象;执行方法 ReflectTest.java文件: //1.加载配置文件 //1.1 创建Properties对象 Properties pro = new Properties(); //1.2 加载配置文件,转换为一个集合 //1.2.1 获取class目录下的配置文件 /* 第一步:类名.class:拿到当前类的字节码 第二步:.getClassLoader():获取这个字节码的类加载器 第三步:.getResourceAsStream(filePath):根据类加载器读取指定的配置文件并转换为字节流 */ ClassLoder classLoder = ReflectTest.class.getClassLoader();//获取了字节码文件对应的类加载器,是由这个类加载器把ReflectTest加载进内存的 /* URL getResource(String name):获取资源的路径 InputStream getResourceAsStream(String name):获取资源对应的字节流 */ InputStream is = classLoder.getResourceAsStream("pro.properties"); pro.load(is); //2.获取配置文件中定义的数据 String className = pro.getProperty("className");//获取配置文件中的加载类的全类名 String methodName = pro.getProperty("methodName");//获取配置文件中的需要执行的方法名称 //3.加载该类进内存(反射) Class cls = Class.forName(className); //4.创建对象 Object obj = cls.newInstance(); //5.获取方法对象 Method method = cls.getMethod(methodName); //6.执行方法 method.invoke(obj);

Q:改代码和改配置文件有啥区别? A:庞大的系统,改Java代码,代码一旦改了,需要重新测试,重新编译,重新上线; 但是仅仅改配置文件的话,配置文件只是一个物理文件,改完之后就完事了。而且改配置文件的方式让程序的扩展性更强一些。

将来只要在配置文件中看到有一个地方配了全限定类名,第一时间应该反应过来:用的是反射机制。

5. 案例2:一个创建Bean对象的工厂

Bean和JavaBean

Bean:在计算机英语中,有可重用组件的含义

JavaBean:用Java语言编写的可重用组件

javabean != 实体类javabean范围远大于实体类

一个创建Bean对象的工厂,它就是创建我们的service和dao对象

工厂模式 工厂模式优势: 解耦(降低类之间的依赖关系),使我们不用每次重新编译、重新部署,包括重新启动Tomcat。

解耦思路

第一步:需要一个配置文件配置service和dao 配置文件可以是xml也可以是properties(这里采用properties)配置文件的内容:唯一标志=全限定类名(key=value) 第二步:通过读取配置文件中配置的内容,反射创建对象

单例对象和多例对象

单例对象:只被创建一次,从而类中的成员也就只会初始化一次。多例对象:每个对象都是重新初始化的。多例对象被创建多次,执行效率没有单例对象高。我们需要每次都是同一个对象,即单例对象。 .newInstance()会调用默认构造函数创建对象 我们的方法只能.newInstance()一次。如果创建完之后不存起来由于Java的垃圾回收机制,它会在长时间不用时被回收,当下次再用时肯定就没有了。所以.newInstance()用完,对象创建出来之后应该马上存起来。 此案例中,我们定义一个Map,用于存放我们要创建的对象。(如下代码中所示)

bean.properties文件:

accountService=service.impl.AccountServiceImpl accountDao=dao.impl.AccountDaoImpl

BeanFactory.java文件:

package factory; import java.io.InputStream; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 一个创建Bean对象的工厂 * * Bean:在计算机英语中,有可重用组件的含义 * JavaBean:用Java语言编写的可重用组件 * javabean != 实体类 * javabean范围远大于实体类 * * 一个创建Bean对象的工厂 它就是创建我们的service和dao对象的。 * * 第一个:需要一个配置文件来配置我们的service和dao * 配置文件的内容:唯一标志=全限定类名(key=value) * 第二个:通过读取配置文件中配置的内容,反射创建对象 * * 我们的配置文件可以是xml也可以是properties */ public class BeanFactory { //定义一个Properties对象 private static Properties props; //定义一个Map,用于存放我们要创建的对象。我们把它称之为容器。 private static Map<String, Object> beans; //使用静态代码块为Properties对象赋值 static { try { //实例化对象 props = new Properties(); //获取properties文件的流对象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");//读取配置文件 props.load(in); //实例化容器 beans = new HashMap<String, Object>(); //取出配置文件中所有的Key Enumeration keys = props.keys();//返回一个枚举类型 //遍历枚举 while(keys.hasMoreElements()){ //取出每个Key String key = keys.nextElement().toString(); //根据key获取value String beanPath = props.getProperty(key); //反射创建对象 Object value = Class.forName(beanPath).newInstance(); //把key和value存入容器中 beans.put(key, value); } // for (String key : beans.keySet()) { // System.out.println(key); // System.out.println(beans.get(key)); // } }catch (Exception e){ throw new ExceptionInInitializerError("初始化properties失败!"); } } /** * 根据Bean的名称获取bean对象 * @param beanName * @return * public static Object getBean(String beanName){ Object bean = null; try { String beanPath = props.getProperty(beanName); // System.out.println("全限定类名:" + beanPath); // 用反射的方式创建对象 bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造函数创建对象 }catch (Exception e){ e.printStackTrace(); } return bean; }*/ /** * 根据bean的名称获取对象 * @param beanName * @return */ public static Object getBean(String beanName){ return beans.get(beanName);//从map中获取beanName对应的对象 } }

使用: xxxServlet.java文件中:

IAccountService as = (IAccountService) BeanFactory.getBean("accountService");

AccountServiceImpl.java文件中:

private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
最新回复(0)