>首页> IT >

详细解析Java反射机制原理和几种Class获取方式

时间:2022-03-23 13:34:26       来源:转载
本篇文章给大家带来了关于java的相关知识,其中主要介绍了反射机制原理以及几种class获取方式和应用场景的相关问题,希望对大家有帮助。

推荐学习:《java学习教程》

学习Java的小伙伴,可能听过Java反射机制,但是熟悉又有点陌生,本文主要是通过思考面试中经常被问到的几个Java反射机制的问题,再通过理论知识结合代码实例应用场景进行讲解,加深自己对Java反射机制的认知和理解,也希望能帮助到有需要的小伙伴~

一、Java反射机制是什么?

1.1 反射原理

(1)Java反射机制(Java Reflection)是Java语言中一种动态(运行时)访问、检测 & 修改它本身的能力,主要作用是动态(运行时)获取类的完整结构信息 & 调用对象的方法~ 更简单点的说就是Java程序在运行时(动态)通过创建一个类的反射对象,再对类进行相关操作,比如:

获取该对象的成员变量 & 赋值调用该对象的方法(含构造方法,有参/无参)判断该对象所属的类

(2)一般情况下,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过new实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射~

(3)而反射则是一开始并不知道要初始化的是什么类,无法使用new来实例化创建对象,主要是通过JDK提供的反射API来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射~

1.2 反射例子

代码如下:

package com.justin.java.lang;import java.lang.reflect.Constructor;import java.lang.reflect.Method;/** * @program: Jdk1.8 Test * @description: 正射、反射简单调用示例 * @author: JustinQin * @create: 2021/8/22 13:23 * @version: v1.0.0 **/public class Student {    private int id;    public void setId(int id) {        this.id = id;    }    public int getId() {        return id;    }    public static void main(String[] args) throws Exception{        //一、正射调用过程        Student student = new Student();        student.setId(1);        System.out.println("正射调用过程Student id:" + student.getId());        //二、反射调用过程        Class clz = Class.forName("com.justin.java.lang.Student");        Constructor studentConstructor = clz.getConstructor();        Object studentObj = studentConstructor.newInstance();                Method setIdMethod = clz.getMethod("setId", int.class);        setIdMethod.invoke(studentObj, 2);        Method getIdMethod = clz.getMethod("getId");        System.out.println("正射调用过程Student id:" + getIdMethod.invoke(studentObj));    }}

输出结果:

正射调用过程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对象

2.2 代码演示几种方式的区别

创建一个实体类,分别在实体类中创建类的静态代码块动态代码块有参构造方法无参构造方法,方便测试几种方式的区别及内存地址是否相同~

(1)实体类:

public class MyClass {    private static final String staticStr = "Hi";    private static int staticInt = 2021;    private String id;    static {        System.out.println("静态代码块:staticStr=" + staticStr + ",staticInt=" + staticInt);    }    {        System.out.println("动态代码块~");    }    public MyClass() {        System.out.println("无参构造方法~");    }    public MyClass(String id) {        System.out.println("有参构造方法~");        this.id = id;    }    public String getId() {        return id;    }    public void setId(String id) {        this.id = id;    }    @Override    public String toString() {        return "MyClass{" +                "id="" + id + "\"" +                "}";    }}

(2)单元测试类:通过@Test注解对三种方式分别进行单元测试,再对这三种方式的组合进行单元测试~

package com.justin.java.lang;import org.junit.Test;/** * @program: Jdk1.8Test * @description: Java反射机制中获取类的Class实例对象的常见三种方式及区别对比 * @author: JustinQin * @create: 2021/8/22 15:04 * @version: v1.0.0 **/public class MyClassTest {    @Test    public void test1() {        System.out.println("一、MyClass.class方式=========");        Class class1 = MyClass.class;    }    @Test    public void test2() throws ClassNotFoundException {        System.out.println("二、Class.forName方式=========");        Class class2 = Class.forName("com.justin.java.lang.MyClass");    }    @Test    public void test3() {        System.out.println("三、new MyClass().getClass方式=========");        Class class3 = new MyClass().getClass();    }    @Test    public void test12() throws ClassNotFoundException {        System.out.println("一、MyClass.class方式=========");        Class class1 = MyClass.class;        System.out.println("二、Class.forName方式=========");        Class class2 = Class.forName("com.justin.java.lang.MyClass");    }    @Test    public void test13() {        System.out.println("一、MyClass.class方式=========");        Class class1 = MyClass.class;        System.out.println("三、new MyClass().getClass方式=========");        Class class3 = new MyClass().getClass();    }    @Test    public void test23() throws ClassNotFoundException {        System.out.println("二、Class.forName方式=========");        Class class2 = Class.forName("com.justin.java.lang.MyClass");        System.out.println("三、new MyClass().getClass方式=========");        Class class3 = new MyClass().getClass();    }    @Test    public void test() throws ClassNotFoundException {        System.out.println("四、三种方式内存地址比较=========");        Class class1 = MyClass.class;        Class class2 = Class.forName("com.justin.java.lang.MyClass");        Class class3 = new MyClass().getClass();        System.out.println("比较结果=========");        System.out.println("MyClass.class和Class.forName内存地址比较是否相同:" + (class1 == class2));        System.out.println("MyClass.class和new MyClass().getClass内存地址比较是否相同:" + (class1 == class3));        System.out.println("Class.forName和new MyClass().getClass内存地址比较是否相同:" + (class2 == class3));    }}

逐个执行单元,得出测试结果为:

* 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

通过test1test2test3的测试结果验证了2.1 三种方式及区别中黄色标记部分的区别说明,即:

MyClass.class不会做任何类的初始化工作Class.forName会进行类的静态初始化工作new MyClass().getClass静态初始化和非静态初始化工作都会进行使用这三种方式任意一种最终在JVM加载到内存中都会是内存地址相同

test23组合得到的测试结果,说明静态代码块只会被加载一次~

讲了这么多,除了知道基本原理和基本使用之外,更重要的还是要知道它的一些比较实际的应用场景,往下介绍~

三、Java反射机制的应用场景有哪些?

3.1 应用场景

工厂模式中的简单工厂模式优化代理模式中的动态代理方式实现Java JDBC数据库操作

3.2 简单工厂模式优化

3.2.1 什么是简单工厂模式?

3.2.2 简单工厂模式有什么用?

3.2.3 如何实现简单工程模式?

实现例子:

步骤1:创建抽象产品类

public interface Product {    public abstract void show();}

步骤2:创建具体产品类:

public class ProductA implements Product {    @Override    public void show() {        System.out.println("生产了产品A");    }}public class ProductB implements Product {    @Override    public void show() {        System.out.println("生产了产品B");    }}public class ProductC implements Product {    @Override    public void show() {        System.out.println("生产了产品C");    }}

步骤3:创建简单工厂类

public class SimpleFactory {    /**     * 实现简单工厂模式     * @param pName 产品标识     * @return 返回具体的产品     */    public static Product createProduct(String pName){        switch (pName){            case "A":                return new ProductA();            case "B":                return new ProductB();            case "C":                return new ProductC();            default:                return null;        }    }}

步骤4:调用简单工厂类

public class SimpleFactoryTest {    public static void main(String[] args) {        try {            SimpleFactory.createProduct("A").show();        } catch (NullPointerException e) {            System.out.println("没有A这款产品,无法生产~");        }        try {            SimpleFactory.createProduct("B").show();        } catch (NullPointerException e) {            System.out.println("没有B这款产品,无法生产~");        }        try {            SimpleFactory.createProduct("C").show();        } catch (NullPointerException e) {            System.out.println("没有C这款产品,无法生产~");        }        try {            SimpleFactory.createProduct("D").show();        } catch (NullPointerException e) {            System.out.println("没有D这款产品,无法生产~");        }    }}

3.2.4 简单工厂模式优化

(1)简单工厂模式弊端

操作成本高:每增加一个接口的子类,必须修改工厂类的逻辑系统复杂性提高:每增加一个接口的子类,都必须向工厂类添加逻辑

这两点弊端从前面的例子SimpleFactory工厂类的实现,可以看出简单工厂模式中对工厂类SimpleFactory的维护成本有点大,因为实际中可能会很频繁的去更新具体产品类,每一次变更都需要去修改工厂类,此时就可以利用Java反射机制对简单工厂模式进行优化~

(2)简单工厂模式的优化思路采用Java反射机制,通过传入子类全局定名(包名+类名)动态的创建不同的子类对象实例,从而使得在不增加产品接口子类和修改工厂类的逻辑的情况下还能实现了工厂类对子类实例对象的统一创建~

(3)简单工厂模式的优化步骤步骤1:创建工厂类采用Java反射机制对工厂类进行优化,主要是将className子类全局定名(包名+类名)作为入参,通过Class.forName方式获取类的java.lang.Class实例对象,再通过Class实例对象的getInstance方法获取到具体子类的实例对象~

public class Factory {    public static Product getInstance(String className) {        Product realProduct = null;        try {            Class pClass = Class.forName(className);            realProduct = (Product) pClass.newInstance();        } catch (Exception e) {            e.printStackTrace();        }        return realProduct;    }}

步骤2:调用工厂类

public class FactoryTest {    public static void main(String[] args) {        try {            Product productA = Factory.getInstance("com.justin.java.lang.ProductA");            productA.show();        } catch (NullPointerException e) {            System.out.println("没有A这款产品,无法生产~");        }        try {            Product productB = Factory.getInstance("com.justin.java.lang.ProductB");            productB.show();        } catch (NullPointerException e) {            System.out.println("没有B这款产品,无法生产~");        }        try {            Product productC = Factory.getInstance("com.justin.java.lang.ProductC");            productC.show();        } catch (NullPointerException e) {            System.out.println("没有C这款产品,无法生产~");        }        try {            Product productD = Factory.getInstance("com.justin.java.lang.ProductD");            productD.show();        } catch (Exception e) {            System.out.println("没有D这款产品,无法生产~");        }    }}

优化结果:

3.2.5 简单工厂模式再次优化

(1)再次优化背景

(2)再次优化实现思路

(3)再次优化实现步骤

再次优化步骤1:相关优化与第一次优化保持不变~

再次优化步骤2:配置类名对应全局定名(包名+类名)创建属性配置文件Product.properties

//产品抽象类Product相关子类的全局定名(包名+类名)定义ProductA = com.justin.java.lang.ProductAProductB = com.justin.java.lang.ProductBProductC = com.justin.java.lang.ProductC

注意:将Product.properties需要存放在src/main/resources资源目录下,若资源目录不存在则需要手动创建~

再次优化步骤3:修改调用工厂类

public class FactoryTest {    @Test    public void test() throws IOException {        ClassLoader classLoader = this.getClass().getClassLoader();        Properties prop = new Properties();        prop.load(classLoader.getResourceAsStream("Product.properties"));        String className = "";        try {            className = prop.getProperty("ProductA");            Product productA = Factory.getInstance(className);            productA.show();        } catch (NullPointerException e) {            System.out.println("没有A这款产品,无法生产~");        }        try {            className = prop.getProperty("ProductB");            Product productA = Factory.getInstance(className);            productA.show();        } catch (NullPointerException e) {            System.out.println("没有B这款产品,无法生产~");        }        try {            className = prop.getProperty("ProductC");            Product productA = Factory.getInstance(className);            productA.show();        } catch (NullPointerException e) {            System.out.println("没有C这款产品,无法生产~");        }    }}

运行结果:

生产了产品A生产了产品B生产了产品C

3.3 代理模式中的动态代理实现

3.3.1 什么是代理模式?

什么?还是不太理解?

代理模式又分为静态代理、动态代理,往下介绍~

3.3.2 什么是静态代理?

3.3.2 什么是动态代理?

动态代理

动态代理最常用的是JDK原生动态代理cglib动态代理,往下介绍~

JDK 原生动态代理

JDK 原生动态代理,主要利用了JDK APIjava.lang.reflect.Proxyjava.lang.relfect.InnvocationHandler这两个类来实现~

通过java.lang.reflect.Proxy代理类的newProxyInstance方法,传递3个参数,分别是:目标对象的加载器通过MyClass.getClass().getClassLoader方式获取目标对象的实现接口类型通过Object.getClass().getInterfaces()方式获取InnvocationHandler事件处理器通过new实例化对象并重写invoke方法方式获取

例子:

用户接口类IUserDao

public interface IUserDao {    //添加数据    public void insert();}

目标对象类UserDao

/** * @program: DataStructures * @description: * @author: JustinQin * @create: 2021/8/23 23:32 * @version: v1.0.0 **/public class UserDao implements IUserDao{    @Override    public void insert() {        System.out.println("添加数据");    }}

动态代理类UserProxy

/** * @program: Jdk1.8Test * @description: 动态代理类 * @author: JustinQin * @create: 2021/8/23 23:31 * @version: v1.0.0 **/public class UserProxy {    private Object target; //目标对象    public UserProxy(Object target) {        this.target = target;    }    /**     * 利用JDK API获取到代理对象     * @return     */    public Object getProxyInstance() {        //目标对象的加载器        ClassLoader loader = target.getClass().getClassLoader();        //目标对象的实现接口类型        Class[] interfaces = target.getClass().getInterfaces();        //InnvocationHandler事件处理器实例对象        InvocationHandler h = new InvocationHandler() {            @Override            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                System.out.println("添加数据前:手动开启事务");                // 执行目标对象方法                Object value = method.invoke(target, args);                System.out.println("添加数据后:手动提交事务");                return null;            }        };        //传入3个参数,创建代理类的实例对象,并返回        return Proxy.newProxyInstance(loader, interfaces,h);    }}

动态代理单元测试类

/** * @program: 动态代理单元测试类 * @description: * @author: JustinQin * @create: 2021/8/23 23:42 * @version: v1.0.0 **/public class UserProxyTest {    @Test    public void test() {        IUserDao target = new UserDao();        System.out.println("目标对象信息:" + target.getClass());        //获取代理类实例对象        IUserDao proxy = (IUserDao) new UserProxy(target).getProxyInstance();        System.out.println("代理对象信息:" + proxy.getClass());        //执行代理方法        proxy.insert();    }}

单元测试执行结果

目标对象信息:class com.justin.java.reflect.UserDao代理对象信息:class com.sun.proxy.$Proxy2添加数据前:手动开启事务添加数据添加数据后:手动提交事务

cglib动态代理

Spring AOP结合了cglib动态代理JDK原生动态代理来实现,这里不过多介绍,有兴趣小伙伴可以查阅资料学习下~

3.3.3 动态代理中如何利用Java反射机制?

3.4 Java JDBC数据库操作实现

3.4.1 利用反射加载JDBC驱动

相信很多小伙伴都知道Java JDBC连接数据库主要分为七大步骤,其中第一步加载JDBC驱动,利用Java反射机制通过传入不同的驱动名称,加载不同数据库的驱动~

Class.forName("com.mysql.jdbc.Driver"); //加载MySQL驱动Class.forName("oracle.jdbc.driver.OracleDriver"); //加载Oracle驱动

3.4.2 Java JDBC连接示例

创建测试库表及数据

create DATABASE test;-- DROP TABLE IF EXISTS test.user;create table test.user(id int(7) primary key not null auto_increment,name varchar(255),sex char(1),age int(3))ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;insert into TEST.user(name,sex,age) values("张一","男",21);insert into TEST.user(name,sex,age) values("张二","女",22);insert into TEST.user(name,sex,age) values("张三","男",23);

Java MySQL JDBC连接七大步骤~

public static void main(String[] args) throws ClassNotFoundException, SQLException {        //1.加载JDBC驱动        Class.forName("com.mysql.jdbc.Driver");        //2.获取数据库的连接(Connection)对象        Connection connection = DriverManager.getConnection(                "jdbc:mysql://localhost/test", //mysql连接url,test表示你要连接的数据库名                "root", //数据库用户名                "abc@123456"); //密码        //3.获取数据库的操作(PrepareStatement)对象        PreparedStatement prepareStatement = connection.prepareStatement("select * from TEST.user where id = ?");        //4.设置传入参数        prepareStatement.setInt(1, 1);        //5.上传sql语句到服务器执行(excute),并返回结果集(ResultSet)        ResultSet result = prepareStatement.executeQuery();        //6.处理返回的ResultSet结果集        while (result.next()) {            System.out.print(result.getInt("id") + ",");            System.out.print(result.getString("name") + ",");            System.out.print(result.getString("sex") + ",");            System.out.print(result.getInt("age"));            System.out.print("\n");        }        //7.释放相关资源:Connection对象、PrepareStatement对象、ResultSet对象。        connection.close();        prepareStatement.close();        result.close();    }

执行结果:

1,张一,男,21

Java Oracle JDBC连接七大步骤~

public class JdbcOracleTest {    public static void main(String[] args) throws ClassNotFoundException, SQLException {        //1.加载JDBC驱动        Class.forName("oracle.jdbc.driver.OracleDriver");        //2.获取数据库的连接(Connection)对象        Connection connection = DriverManager.getConnection(                "jdbc:oracle:thin:@127.0.0.1:1521:orcl",//oracle连接url                "root", //数据库用户名                "abc@123456"); //密码        //3.获取数据库的操作(PrepareStatement)对象        PreparedStatement prepareStatement = connection.prepareStatement("select * from TEST.user where id = ?");        //4.设置传入参数        prepareStatement.setInt(1, 1);        //5.上传sql语句到服务器执行(excute),并返回结果集(ResultSet)        ResultSet result = prepareStatement.executeQuery();        //6.处理返回的ResultSet结果集        while (result.next()) {            System.out.print(result.getInt("id")+",");            System.out.print(result.getString("name")+",");            System.out.print(result.getString("sex")+",");            System.out.print(result.getInt("age"));            System.out.print("\n");        }        //7.释放相关资源:Connection对象、PrepareStatement对象、ResultSet对象。        connection.close();        prepareStatement.close();        result.close();    }}

数据库连接池配置spring-mybatis.xml

    

数据库配置信息jdbc.propertis

#数据库连接驱动app-data-source.driverClassName=com.mysql.jdbc.Driver#数据库连接urlapp-data-source.url=jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=UTF-8#数据库用户app-data-source.username=root#数据库用户密码(加密)app-data-source.password=abc@123456#连接池初始化大小app-data-source.initialSize=10#连接池最大数量app-data-source.maxActive=50#连接池最大空闲app-data-source.maxIdle=20#连接池最小空闲app-data-source.minIdle=5#获取连接最大等待时间app-data-source.maxWait=30000

面试总结

一、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教程》

以上就是详细解析Java反射机制原理和几种Class获取方式的详细内容,更多请关注php中文网其它相关文章!

关键词: 工厂模式 构造方法 目标对象