Java反射机制

 

一、Java反射机制是什么?

1.1 反射原理

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

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

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

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

反射示例:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**

 * @program: Jdk1.8 Test

 * @description: 正射、反射简单调用示例

 **/
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());

 

        //二、反射调用过程
        
        //Java 11 泛型 Class<?> clz = Class.forName("Student");
        //"com.justin.java.lang.Student" 全路径 加上包名
        Class clz = Class.forName("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

 从这个简单的例子可以看出,一般情况下我们使用反射获取一个对象的步骤

1、获取类的 Class 对象实例

Class clz = Class.forName("com.example.testapplication.Person");
//Class<?> clz = Class.forName("com.example.testapplication.Person");

2、根据 Class 对象实例获取 Constructor 对象

Constructor constructor = clz.getConstructor();
//Constructor<?>[] constructors = clz.getConstructors();

 3、使用 Constructor 对象的 newInstance 方法获取反射类对象

Object object = constructor.newInstance();

 如果要调用某一个方法,则需要经过下面的步骤

1、获取方法的 Method 对象

Method method = clz.getMethod("setId", Integer.class);

 2、利用 invoke 方法调用方法

method.invoke(object, 22);

上述例子反射的调用过程,可以看到获取一个类的反射对象,主要过程为:

  • 获取类的Class实例对象
  • 根据Class实例对象获取Constructor对象
  • 再根据Constructor对象的newInstance方法获取到类的反射对象

获取到类的反射对象后,就可以对类进行操作了~ 例如,上述示例中对类的方法进行调用过程为:

  • 根据Class实例对象获取到类的Method对象
  • 再根据Method对象的invoke方法调用到具体类的方法

实现Java反射机制的类都位于java.lang.reflect包中:

  1. Class类:代表一个类

  2. Field类:代表类的成员变量(类的属性)

  3. Method类:代表类的方法

  4. Constructor类:代表类的构造方法

  5. Array类:提供了动态创建数组,以及访问数组的元素的静态方法

前面一点也提到了获取到类的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对象。

 这3种方式,最终在JVM堆区对应类的java.lang.Class对象都属于同一个,也就是内存地址相同,进行==双等号比较结果为true,原因是JVM类加载过程中使用的是同一个ClassLoader类加载器加载某个类,不论加载多少次,生成到堆区的java.lang.Class对象始终只有一个,除非自定义类加载器,破坏JVM的双亲委派机制,使得同一个类被不同类加载器加载,JVM才会把它当做两个不同的java.lang.Class对象

 (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注解对三种方式分别进行单元测试,再对这三种方式的组合进行单元测试

/**
 * @program: Jdk1.8Test
 * @description: Java反射机制中获取类的Class实例对象的常见三种方式及区别对比
 **/
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("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("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("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("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.classnew 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 什么是简单工厂模式?

Java中主要有23种设计模式,其中工厂模式就是其中一种,而简单工厂模式,顾名思义,也是属于工厂模式中的一种,只不过比较简单。简单工厂模式也可以叫做静态方法模式(因为工厂类一般都是在内部定义了一个静态方法)。
从现实生活角度来理解的话,工厂是专门负责生产产品的,同样在设计模式中,简单工厂模式我们可以理解为专门负责生产对象的一个类,称为“工厂类”。

3.2.2 简单工厂模式的作用

 简单工厂模式通过创建一个对应的工厂类,将类实例化的操作使用对象的操作进行分开,让使用者不用知道具体参数就可以实例化出所需要的具体产品类,从而避免了在客户端代码中显式指定,实现了解耦。即使用者可直接消费产品而不需要知道其生产的细节~

3.2.3 实现简单工程模式

实现简单工程模式的核心是创建一个工厂类,并且在内部定义了一个静态方法,传入不同的参数标识通过switch进行分组,通过new实例化创建不同的子类对象返回~

例子:

//步骤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("ProductA");
            productA.show();
        } 
        catch (NullPointerException e) 
        {
            System.out.println("没有A这款产品,无法生产~");
        }

        try 
        {
            Product productB = Factory.getInstance("ProductB");
            productB.show();
        } 
        catch (NullPointerException e) 
        {
            System.out.println("没有B这款产品,无法生产~");
        }

        try 
        {
            Product productC = Factory.getInstance("ProductC");
            productC.show();
        } 
        catch (NullPointerException e) 
        {
            System.out.println("没有C这款产品,无法生产~");
        }

        try 
        {
            Product productD = Factory.getInstance("ProductD");
            productD.show();
        } 
        catch (Exception e) 
        {
            System.out.println("没有D这款产品,无法生产~");
        }
    }
}

使用Java反射机制优化简单工厂模式后,可以看到,不论具体产品类更新多频繁,都不需要再修改工厂类,从而解决了普通简单工厂模式操作成本高系统复杂性高的问题~ 

3.2.5 简单工厂模式再次优化

(1)再次优化背景

简单工厂模式的工厂类采用Java反射机制进行优化后,此时的仍然存在这样一个问题,子类的全局定名(包名+类名)是写死的,但是实际上开发者在写代码时是很难提前预知所有的子类的全局定名(包名+类名)的,因此需要进行二次优化~ 

(2)再次优化实现思路

通过配置文件方式,统一定义类名对应全局定名(包名+类名),将配置文件存放到资源目录下,程序运行时通过ClassLoader类加载器动态获取到配置文件中定义的子类的全局定名~

 

(3)再次优化实现步骤

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

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

#//产品抽象类Product相关子类的全局定名(包名+类名)定义

ProductA = com.learn.java.lang.ProductA

ProductB = com.learn.java.lang.ProductB

ProductC = com.learn.java.lang.ProductC

ProductD = com.learn.java.lang.ProductD

#//注意:将Product.properties需要存放在src/main/resources资源目录下,

#//若资源目录不存在则需要手动创建

//优化后,修改调用工厂类
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 代理模式

 代理(Proxy)模式是一种设计模式,通过代理对象来访问目标对象,还可以在不修改目标对象的情况下,对代理对象进行拓展,增强目标对象的功能~

更通俗一点的说代理模式,就是想做某件事(买火车票),自己能买(直接去火车站买),却委托别人去买(没空还是代理点买吧),还可以让别人帮自己做其他事(订好酒店)~

 静态代理

(1)静态代理属于代理模式的一种代理方式,需要代理对象目标对象实现相同的接口
(2)静态代理的代理类是由程序员编写源码,编译后即可获取到代理类的class字节码文件,也就是在程序运行前就已经得到实际的代理类class字节码文件了

 动态代理 

(1)动态代理也属于代理模式的一种代理方式,不过只需要目标对象实现接口,代理对象不需要实现接口~
(2)动态代理的代理类编译后是没有class字节码文件的,而是在运行时利用Java反射机制动态的生成代理类的class字节码文件~

 

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

JDK 原生动态代理

JDK 原生动态代理,主要利用了JDK API
java.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:
 **/
public class UserDao implements IUserDao
{
    @Override
    public void insert() 
    {
        System.out.println("添加数据");
    }
}

//动态代理类UserProxy
/**
 * @program: Jdk1.8Test
 * @description: 动态代理类
 **/
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: 动态代理单元测试类
 **/
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 UserDao
//代理对象信息:class com.sun.proxy.$Proxy2
//添加数据前:手动开启事务

//添加数据

//添加数据后:手动提交事务

cglib动态代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

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

JDK原生动态代理中,获取代理示例对象过程中,获取目标对象的类加载器,通过target.getClass().getClassLoader(获取到目标对象的类加载器,target.getClass()方式获取目标对象的Class实例对象使用的就是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();
    }
}

//输出结果
// 

上面通过Java JDBC连接数据库并进行操作,这里的连接是单一连接,直接通过DriverManager.getConnection这种Java原生的数据库连接方式建立的连接,现在实际的Java Spring项目当中,都是通过配置mybatis的数据库连接池来实现的,不过原理都是一样的,加载驱动也是利用了Java反射机制指定不同的驱动名称,实现不同数据库驱动的加载~

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

<!-- 基于tomcat jdbc连接池的数据源  -->
<bean id="dataSource" class="com.justin.datasource.TomcatDataSource" init-method="createPool">

    <!-- 基于dbcp连接池的数据源
    <bean id="dataSource" class="com.justin.datasource.DbcpDataSource" destroy-method="close"> -->

    <!-- 基于阿里druid连接池的数据源
    <bean id="dataSource" class="com.justin.datasource.DruidDataSource" destroy-method="close"> -->

    <property name="driverClassName" value="${app-data-source.driverClassName}" />

    <property name="url" value="${app-data-source.url}" />

    <property name="username" value="${app-data-source.username}" />

    <property name="password" value="${app-data-source.password}" />

    <!-- 初始化连接大小 -->
    <property name="initialSize" value="${app-data-source.initialSize}" />

    <!-- 连接池最大数量 -->
    <property name="maxActive" value="${app-data-source.maxActive}" />

    <!-- 连接池最大空闲 -->
    <property name="maxIdle" value="${app-data-source.maxIdle}" />

    <!-- 连接池最小空闲 -->
    <property name="minIdle" value="${app-data-source.minIdle}" />

    <!-- 获取连接最大等待时间 -->
    <property name="maxWait" value="${app-data-source.maxWait}" />

</bean>

数据库配置信息jdbc.propertis

#数据库连接驱动

app-data-source.driverClassName=com.mysql.jdbc.Driver#数据库连接url
app-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数据库操作

怎么才能学好反射,我们需要弄懂以下几个问题:

1.反射是什么?

2.反射有什么用?

3.反射的实现原理?

4.怎么用反射?

下面我就针对以上的疑问,一一来讲解。

反射是什么?

反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。

反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意方法和属性,这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

一句话总结:反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

为什么要用反射?

Java Reflection功能非常强大,并且非常有用,比如:

  • 获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等

  • 获取任意对象的属性,并且能改变对象的属性

  • 调用任意对象的方法

  • 判断任意一个对象所属的类

  • 实例化任意一个类的对象

  • 通过反射我们可以实现动态装配,降低代码的耦合度,动态代理等。

怎么使用反射?

一般情况下我们通过反射创建类对象主要有两种方式:

通过 Class 对象的 newInstance() 方法

  • 通过 Constructor 对象的 newInstance() 方法

第一种:通过 Class 对象的 newInstance() 方法。

Class clz = Class.forName("com.mikechen.reflection.JiaGou");JiaGou jg= (JiaGou)clz.newInstance();

第二种:通过 Constructor 对象的 newInstance() 方法

Class clz = Class.forName("com.mikechen.reflection.JiaGou");Constructor constructor = clz.getConstructor();JiaGou jg= (JiaGou)constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法,下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

Class clz = Class.forName("com.mikechen.reflection.JiaGou");Constructor constructor = clz.getConstructor(String.class);JiaGou jg= (JiaGou)constructor.newInstance("mikechen的互联网架构");

接下来我们就可以通过具体的API调用获取到详细的属性或者方法等详细了。

1、获取类的成员变量的信息

//mikechen的互联网架构Field[] fields = cls.getDeclaredFields();

更加详细成员变量获取参考如下:

2、获得类方法

//mikechen的互联网架构Method[] methods = cls.getDeclaredMethods();

更加详细方法获取参考如下:

3、获得构造函数

//mikechen的互联网架构 Constructor[] constructors = cls.getDeclaredConstructors();

更加详细构造函数获取参考如下:

这样通过反射就可以做在运行时获取类的完整构造,并获得类信息了。

通过以上一个小案例了解了反射的使用,但如果你想对反射掌握得更好,还需深入理解反射背后的底层实现原理。

反射工作原理?

调用反射的总体流程如下:

1、当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class文件。

2、这些class文件在程序运行时会被ClassLoader加载到JVM中,当一个类被加载以后,JVM就会在内存中自动产生一个Class对象。

3、通过Class对象获取Field/Method/Construcor

我们一般平时是通过new的形式创建对象实际上就是通过这些Class来创建的,只不过这个class文件是编译的时候就生成的,程序相当于写死了给jvm去跑。

反射是什么呢?当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载。

原来使用new的时候,需要明确的指定类名,这个时候属于硬编码实现,而在使用反射的时候,可以只传入类名参数,就可以生成对象,降低了耦合性,使得程序更具灵活性。

反射的应用场景

举个例子我们的项目底层数据库有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设 com.mikechen.java.myqlConnection,com.mikechen.java.oracleConnection这两个类我们要用。

这时候我们在使用 JDBC 连接数据库时使用 Class.forName()通过反射加载数据库的驱动程序,如果是mysql则传入mysql的驱动类,而如果是oracle则传入的参数就变成另一个了。

Spring 框架的 IOC(动态加载管理 Bean),Spring通过配置文件配置各种各样的bean,你需要用到哪些bean就配哪些,spring容器就会根据你的需求去动态加载,你的程序就能健壮地运行。

还有Spring AOP(动态代理)功能都和反射有关系。

除此之外还有很多框架:mybatis、dubbo、rocketmq等等都会用到反射机制。

 

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值