学习Java的小伙伴,可能听过Java反射机制,但是熟悉又有点陌生,本文主要是通过思考面试中经常被问到的几个Java反射机制的问题,再通过理论知识结合代码实例及应用场景进行讲解,加深自己对Java反射机制的认知和理解,也希望能帮助到有需要的小伙伴~
????一、Java反射机制是什么? ????1.1 反射原理(1)Java反射机制(Java Reflection)是Java语言中一种动态(运行时)访问、检测 & 修改它本身的能力,主要作用是动态(运行时)获取类的完整结构信息 & 调用对象的方法~更简单点的说就是Java程序在运行时(动态)通过创建一个类的反射对象,再对类进行相关操作,比如:
获取该对象的成员变量 & 赋值调用该对象的方法(含构造方法,有参/无参)判断该对象所属的类
PS:不过说实话,直接看比较官方的定义还是有点难理解,再来更加通俗点的说吧~
(2)一般情况下,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过new实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射~
(3)而反射则是一开始并不知道要初始化的是什么类,无法使用new来实例化创建对象,主要是通过JDK提供的反射API来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射~
????1.2 反射例子代码如下:
输出结果:
正射调用过程Student id:1反射调用过程Student id:2
上述例子反射的调用过程,可以看到获取一个类的反射对象,主要过程为:
获取类的Class实例对象根据Class实例对象获取Constructor对象再根据Constructor对象的newInstance方法获取到类的反射对象获取到类的反射对象后,就可以对类进行操作了~ 例如,上述示例中对类的方法进行调用过程为:
根据Class实例对象获取到类的Method对象再根据Method对象的invoke方法调用到具体类的方法前面一点也提到了获取到类的Class实例对象,上面示例反向调用过程中我们是通过Class.forName("类的全局定名")这种方式来获取到类的Class实例对象,除了这种,常用的还有其他两种,往下讲解~
????二、Java反射机制中获取Class的三种方式及区别? ????2.1 Class的几种获取方式(1)获取类的java.lang.Class实例对象,常见的三种方式分别为:
通过MyClass.class获取,这里的MyClass指具体类~~通过Class.forName("类的全局定名")获取,全局定名为包名+类名通过new MyClass().getClass()获取,这里的MyClass指具体类~
(2)通过MyClass.class获取,JVM会使用ClassLoader类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回java.lang.Class对象
(3)通过Class.forName("类的全局定名")获取,同样,类会被JVM加载到内存中,并且会进行类的静态初始化工作,返回java.lang.Class对象
(4)通过new MyClass().getClass()获取,这种方式使用了new进行实例化操作,因此静态初始化和非静态初始化工作都会进行,getClass方法属于顶级Object类中的方法,任何子类对象都可以调用,哪个子类调用,就返回那个子类的java.lang.Class对象
PS: 这3种方式,最终在JVM堆区对应类的java.lang.Class对象都属于同一个,也就是内存地址相同,进行==双等号比较结果为true,原因是JVM类加载过程中使用的是同一个ClassLoader类加载器加载某个类,不论加载多少次,生成到堆区的java.lang.Class对象始终只有一个,除非自定义类加载器,破坏JVM的双亲委派机制,使得同一个类被不同类加载器加载,JVM才会把它当做两个不同的java.lang.Class对象
????2.2 代码演示几种方式的区别创建一个实体类,分别在实体类中创建类的静态代码块、动态代码块、有参构造方法、无参构造方法,方便测试几种方式的区别及内存地址是否相同~
(1)实体类:
(2)单元测试类:通过@Test注解对三种方式分别进行单元测试,再对这三种方式的组合进行单元测试~
逐个执行单元,得出测试结果为:
* test1()方法一、MyClass.class方式=========
* test2()方法二、Class.forName方式=========静态代码块:staticStr=Hi,staticInt=2021
* test3()方法三、new MyClass().getClass方式=========静态代码块:staticStr=Hi,staticInt=2021动态代码块~无参构造方法~
* test12()方法一、MyClass.class方式=========二、Class.forName方式=========静态代码块:staticStr=Hi,staticInt=2021
* test13()方法一、MyClass.class方式=========三、new MyClass().getClass方式=========静态代码块:staticStr=Hi,staticInt=2021动态代码块~无参构造方法~
* test23()方法二、Class.forName方式=========静态代码块:staticStr=Hi,staticInt=2021三、new MyClass().getClass方式=========动态代码块~无参构造方法~
* test()方法四、三种方式内存地址比较=========静态代码块:staticStr=Hi,staticInt=2021动态代码块~无参构造方法~比较结果=========MyClass.class和Class.forName内存地址比较是否相同:trueMyClass.class和new MyClass().getClass内存地址比较是否相同:trueClass.forName和new MyClass().getClass内存地址比较是否相同:true
通过test1、test2、test3的测试结果验证了2.1 三种方式及区别中黄色标记部分的区别说明,即:
MyClass.class不会做任何类的初始化工作Class.forName会进行类的静态初始化工作new MyClass().getClass静态初始化和非静态初始化工作都会进行使用这三种方式任意一种最终在JVM加载到内存中都会是内存地址相同的
而test23组合得到的测试结果,说明静态代码块只会被加载一次~
讲了这么多,除了知道基本原理和基本使用之外,更重要的还是要知道它的一些比较实际的应用场景,往下介绍~
????三、Java反射机制的应用场景有哪些? ????3.1 应用场景 工厂模式中的简单工厂模式优化代理模式中的动态代理方式实现Java JDBC数据库操作 ????3.2 简单工厂模式优化 ????3.2.1 什么是简单工厂模式?Java中主要有23种设计模式,其中工厂模式就是其中一种,而简单工厂模式,顾名思义,也是属于工厂模式中的一种,只不过比较简单。简单工厂模式也可以叫做静态方法模式(因为工厂类一般都是在内部定义了一个静态方法)。从现实生活角度来理解的话,工厂是专门负责生产产品的,同样在设计模式中,简单工厂模式我们可以理解为专门负责生产对象的一个类,称为“工厂类”。
????3.2.2 简单工厂模式有什么用?简单工厂模式通过创建一个对应的工厂类,将类实例化的操作与使用对象的操作进行分开,让使用者不用知道具体参数就可以实例化出所需要的具体产品类,从而避免了在客户端代码中显式指定,实现了解耦。即使用者可直接消费产品而不需要知道其生产的细节~
????3.2.3 如何实现简单工程模式?实现简单工程模式的核心是创建一个工厂类,并且在内部定义了一个静态方法,传入不同的参数标识通过switch进行分组,通过new实例化创建不同的子类对象返回~
实现例子:
步骤1:创建抽象产品类
步骤2:创建具体产品类:
步骤3:创建简单工厂类
步骤4:调用简单工厂类
????3.2.4 简单工厂模式优化(1)简单工厂模式弊端
操作成本高:每增加一个接口的子类,必须修改工厂类的逻辑系统复杂性提高:每增加一个接口的子类,都必须向工厂类添加逻辑这两点弊端从前面的例子SimpleFactory工厂类的实现,可以看出简单工厂模式中对工厂类SimpleFactory的维护成本有点大,因为实际中可能会很频繁的去更新具体产品类,每一次变更都需要去修改工厂类,此时就可以利用Java反射机制对简单工厂模式进行优化~
(2)简单工厂模式的优化思路
采用Java反射机制,通过传入子类全局定名(包名+类名) 动态的创建不同的子类对象实例,从而使得在不增加产品接口子类和修改工厂类的逻辑的情况下还能实现了工厂类对子类实例对象的统一创建~
(3)简单工厂模式的优化步骤
步骤1:创建工厂类
采用Java反射机制对工厂类进行优化,主要是将className即子类全局定名(包名+类名)作为入参,通过Class.forName方式获取类的java.lang.Class实例对象,再通过Class实例对象的getInstance方法获取到具体子类的实例对象~
步骤2:调用工厂类
优化结果:
使用Java反射机制优化简单工厂模式后,可以看到,不论具体产品类更新多频繁,都不需要再修改工厂类,从而解决了普通简单工厂模式操作成本高和系统复杂性高的问题~
????3.2.5 简单工厂模式再次优化(1)再次优化背景
简单工厂模式的工厂类采用Java反射机制进行优化后,此时的仍然存在这样一个问题,子类的全局定名(包名+类名)是写死的,但是实际上开发者在写代码时是很难提前预知所有的子类的全局定名(包名+类名)的,因此需要进行二次优化~
(2)再次优化实现思路
通过配置文件方式,统一定义类名对应全局定名(包名+类名),将配置文件存放到资源目录下,程序运行时通过ClassLoader类加载器动态获取到配置文件中定义的子类的全局定名~
(3)再次优化实现步骤
再次优化步骤1:相关优化与第一次优化保持不变~
再次优化步骤2:配置类名对应全局定名(包名+类名)
创建属性配置文件Product.properties
注意:将Product.properties需要存放在src/main/resources资源目录下,若资源目录不存在则需要手动创建~
再次优化步骤3:修改调用工厂类
运行结果:
生产了产品A生产了产品B生产了产品C
????3.3 代理模式中的动态代理实现 ????3.3.1 什么是代理模式?代理(Proxy)模式是一种设计模式,通过代理对象来访问目标对象,还可以在不修改目标对象的情况下,对代理对象进行拓展,增强目标对象的功能~
什么?还是不太理解?
更通俗一点的说代理模式,就是想做某件事(买火车票),自己能买(直接去火车站买),却委托别人去买(没空还是代理点买吧),还可以让别人帮自己做其他事(订好酒店)~
代理模式又分为静态代理、动态代理,往下介绍~
????3.3.2 什么是静态代理?(1)静态代理属于代理模式的一种代理方式,需要代理对象和目标对象实现相同的接口(2)静态代理的代理类是由程序员编写源码,编译后即可获取到代理类的class字节码文件,也就是在程序运行前就已经得到实际的代理类class字节码文件了
????3.3.2 什么是动态代理?动态代理
(1)动态代理也属于代理模式的一种代理方式,不过只需要目标对象实现接口,代理对象不需要实现接口~(2)动态代理的代理类编译后是没有class字节码文件的,而是在运行时利用Java反射机制动态的生成代理类的class字节码文件~
动态代理最常用的是JDK原生动态代理和cglib动态代理,往下介绍~
JDK 原生动态代理
JDK 原生动态代理,主要利用了JDK API的java.lang.reflect.Proxy和java.lang.relfect.InnvocationHandler 这两个类来实现~
通过java.lang.reflect.Proxy代理类的newProxyInstance方法,传递3个参数,分别是:目标对象的加载器 通过MyClass.getClass().getClassLoader方式获取目标对象的实现接口类型 通过Object.getClass().getInterfaces()方式获取InnvocationHandler事件处理器 通过new取
例子:
用户接口类IUserDao
目标对象类UserDao
动态代理类UserProxy
动态代理单元测试类
单元测试执行结果
目标对象信息:class com.justin.java.reflect.UserDao代理对象信息:class com.sun.proxy.$Proxy2添加数据前:手动开启事务添加数据添加数据后:手动提交事务
cglib动态代理
cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
Spring AOP结合了cglib动态代理和JDK原生动态代理来实现,这里不过多介绍,有兴趣小伙伴可以查阅资料学习下~
????3.3.3 动态代理中如何利用Java反射机制?
JDK原生动态代理中,获取代理示例对象过程中,获取目标对象的类加载器,通过target.getClass().getClassLoader(获取到目标对象的类加载器,target.getClass()方式获取目标对象的Class实例对象使用的就是Java反射机制来实现的~
????3.4 Java JDBC数据库操作实现 ????3.4.1 利用反射加载JDBC驱动相信很多小伙伴都知道Java JDBC连接数据库主要分为七大步骤,其中第一步加载JDBC驱动,利用Java反射机制通过传入不同的驱动名称,加载不同数据库的驱动~
链接:Mysql驱动架包mysql-connector-java-5.1.30.jar
链接:Oracle驱动架包ojdbc14-10.2.0.4.0.jar
????3.4.2 Java JDBC连接示例创建测试库表及数据
Java MySQL JDBC连接七大步骤~
执行结果:
1,张一,男,21
Java Oracle JDBC连接七大步骤~
PS:上面通过Java JDBC连接数据库并进行操作,这里的连接是单一连接,直接通过DriverManager.getConnection这种Java原生的数据库连接方式建立的连接,现在实际的Java Spring项目当中,都是通过配置mybatis的数据库连接池来实现的,不过原理都是一样的,加载驱动也是利用了Java反射机制指定不同的驱动名称,实现不同数据库驱动的加载~
数据库连接池配置spring-mybatis.xml
数据库配置信息jdbc.propertis
面试总结一、Java反射机制是什么?
1、Java反射机制(Java Reflection)是Java语言中一种动态(运行时)访问、检测 & 修改它本身的能力,主要作用是动态(运行时)获取类的完整结构信息 & 调用对象的方法~
更简单点的说就是Java程序在运行时(动态)通过创建一个类的反射对象,再对类进行相关操作,比如:
获取该对象的成员变量 & 赋值调用该对象的方法(含构造方法,有参/无参)判断该对象所属的类2、更通俗点的说,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过new实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射~
3、而反射则是一开始并不知道要初始化的是什么类,无法使用new来实例化创建对象,主要是通过JDK提供的反射API来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射~
二、Java反射机制中获取Class的三种方式及区别?
1、获取类的java.lang.Class实例对象,常见的三种方式分别为:
通过MyClass.class获取通过Class.forName("类的全局定名")获取通过new MyClass().getClass()获取2、通过MyClass.class获取,JVM会使用ClassLoader类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回java.lang.Class对象
3、通过Class.forName("类的全局定名")获取,同样,类会被JVM加载到内存中,并且会进行类的静态初始化工作,返回java.lang.Class对象
4、通过new MyClass().getClass()获取,这种方式使用了new进行实例化操作,因此== 静态初始化和非静态初始化工作都会进行 == ,getClass方法属于顶级Object类中的方法,任何子类对象都可以调用,哪个子类调用,就返回那个子类的java.lang.Class对象
5、这3种方式,最终在JVM堆区对应类的java.lang.Class对象都属于同一个,也就是内存地址相同,进行==双等号比较结果为true,原因是JVM类加载过程中使用的是同一个ClassLoader类加载器加载某个类,不论加载多少次,生成到堆区的java.lang.Class对象始终只有一个,除非自定义类加载器,破坏JVM的双亲委派机制,使得同一个类被不同类加载器加载,JVM才会把它当做两个不同的java.lang.Class对象
三、Java反射机制的应用场景有哪些?
工厂模式中的简单工厂模式优化代理模式中的动态代理方式实现Java JDBC数据库操作 总结到此这篇关于Java反射机制原理、Class获取方式以及应用场景的文章就介绍到这了,更多相关Java反射原理及Class获取内容请搜索七叶笔记以前的文章或继续浏览下面的相关文章希望大家以后多多支持七叶笔记!