Java反射机制原理、Class获取方式以及应用场景详解

目录
  • 学习背景
  • 一、Java反射机制是什么?
    • 1.1 反射原理
    • 1.2 反射例子
  • 二、Java反射机制中获取Class的三种方式及区别?
    • 2.1 Class的几种获取方式
    • 2.2 代码演示几种方式的区别
  • 三、Java反射机制的应用场景有哪些?
    • 3.1 应用场景
    • 3.2 简单工厂模式优化
      • 3.2.1 什么是简单工厂模式?
      • 3.2.2 简单工厂模式有什么用?
      • 3.2.3 如何实现简单工程模式?
      • 3.2.4 简单工厂模式优化
      • 3.2.5 简单工厂模式再次优化
    • 3.3 代理模式中的动态代理实现
      • 3.3.1 什么是代理模式?
      • 3.3.2 什么是静态代理?
      • 3.3.2 什么是动态代理?
    • 3.4 Java JDBC数据库操作实现
      • 3.4.1 利用反射加载JDBC驱动
      • 3.4.2 Java JDBC连接示例
  • 面试总结
  • 总结

学习背景

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

一、Java反射机制是什么?

1.1 反射原理

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

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

PS:不过说实话,直接看比较官方的定义还是有点难理解,再来更加通俗点的说吧~

(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对象

PS: 这3种方式,最终在JVM堆区对应类的java.lang.Class对象都属于同一个,也就是内存地址相同,进行==双等号比较结果为true,原因是JVM类加载过程中使用的是同一个ClassLoader类加载器加载某个类,不论加载多少次,生成到堆区的java.lang.Class对象始终只有一个,除非自定义类加载器,破坏JVM的双亲委派机制,使得同一个类被不同类加载器加载,JVM才会把它当做两个不同的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内存地址比较是否相同:true
MyClass.class和new MyClass().getClass内存地址比较是否相同:true
Class.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:创建抽象产品类

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这款产品,无法生产~");
        }
    }
}

优化结果:

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

3.2.5 简单工厂模式再次优化

(1)再次优化背景

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

(2)再次优化实现思路

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

(3)再次优化实现步骤

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

再次优化步骤2:配置类名对应全局定名(包名+类名)

创建属性配置文件Product.properties

//产品抽象类Product相关子类的全局定名(包名+类名)定义
ProductA = com.justin.java.lang.ProductA
ProductB = com.justin.java.lang.ProductB
ProductC = 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 代理模式中的动态代理实现

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这款产品,无法生产~");
        }
    }
}

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.Proxyjava.lang.relfect.InnvocationHandler 这两个类来实现~

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

例子:

用户接口类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动态代理

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反射机制通过传入不同的驱动名称,加载不同数据库的驱动~

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

链接:Mysql驱动架包mysql-connector-java-5.1.30.jar

链接:Oracle驱动架包ojdbc14-10.2.0.4.0.jar

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();
    }
}

PS:上面通过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数据库操作

总结

到此这篇关于Java反射机制原理、Class获取方式以及应用场景的文章就介绍到这了,更多相关Java反射原理及Class获取内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java 采用反射获取class属性值的实现代码

    原理:Java的反射能够获取属性的名称,然后通过invoke调用类的某个方法.比如有个属性叫userName,这个类写了个方法叫getUserName,通过invoke调用getUserName这个方法.代码如下 复制代码 代码如下: import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class ParameterB

  • JAVA反射机制中getClass和class对比分析

    java有两个获得类名的方法getClass()和class(),这两个方法看似一样,实则不然.这两个方法涉及到了java中的反射. 所谓反射,可以理解为在运行时期获取对象类型信息的操作.传统的编程方法要求程序员在编译阶段决定使用的类型,但是在反射的帮助下,编程人员可以动态获取这些信息,从而编写更加具有可移植性的代码.严格地说,反射并非编程语言的特性,因为在任何一种语言都可以实现反射机制,但是如果编程语言本身支持反射,那么反射的实现就会方便很多. 类型类 要知道类型信息在运行时是如何表示的,这是

  • Java反射学习 getClass()函数应用

    Java反射学习 所谓反射,可以理解为在运行时期获取对象类型信息的操作.传统的编程方法要求程序员在编译阶段决定使用的类型,但是在反射的帮助下,编程人员可以动态获取这些信息,从而编写更加具有可移植性的代码.严格地说,反射并非编程语言的特性,因为在任何一种语言都可以实现反射机制,但是如果编程语言本身支持反射,那么反射的实现就会方便很多. 1,获得类型类 我们知道在Java中一切都是对象,我们一般所使用的对象都直接或间接继承自Object类.Object类中包含一个方法名叫getClass,利用这个方

  • Java反射获取class对象方式解析

    1.获取Class对象 在 Java API 中,提供了获取 Class 类对象的三种方法: 第一种,使用 Class.forName 静态方法. 前提:已明确类的全路径名. 第二种,使用 .class 方法. 说明:仅适合在编译前就已经明确要操作的 Class 第三种,使用类对象的 getClass() 方法. 适合有对象示例的情况下 User类 package com.reflection; /** * Created by Liuxd on 2018-08-15. */ public cl

  • java 反射getClass .class 的使用方法示例

    本文实例讲述了java 反射getClass .class 的使用方法.分享给大家供大家参考,具体如下: java反射机制 怎么从一个实例上的获得类反射 /** * obj 实例的对象 * getClass() 获得该实例类的反射 * @return * Class<? extends Object> */ obj.getClass(); //例子 String str1 = "123"; Class<?> strClass1 = str1.getClass()

  • Java反射(Class类,Class对象获取)

    目录 Java反射超详解 1.反射基础 1.1Class类 1.2类加载 2.反射的使用 2.1Class对象的获取 2.2Constructor类及其用法 2.3Field类及其用法 Java反射超详解 1.反射基础 Java反射机制是在程序的运行过程中,对于任何一个类,都能够知道它的所有属性和方法:对于任意一个对象,都能够知道它的任意属性和方法,这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制. Java反射机制主要提供以下这几个功能: 在运行时判断任意一个对象所属的类

  • Java反射机制原理、Class获取方式以及应用场景详解

    目录 学习背景 一.Java反射机制是什么? 1.1 反射原理 1.2 反射例子 二.Java反射机制中获取Class的三种方式及区别? 2.1 Class的几种获取方式 2.2 代码演示几种方式的区别 三.Java反射机制的应用场景有哪些? 3.1 应用场景 3.2 简单工厂模式优化 3.2.1 什么是简单工厂模式? 3.2.2 简单工厂模式有什么用? 3.2.3 如何实现简单工程模式? 3.2.4 简单工厂模式优化 3.2.5 简单工厂模式再次优化 3.3 代理模式中的动态代理实现 3.3.

  • Java 反射机制原理与用法详解

    本文实例讲述了Java 反射机制原理与用法.分享给大家供大家参考,具体如下: 反射反射,程序员的快乐! 1.什么是反射? Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:并且能改变它的属性.而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言.从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是

  • java数组的三种扩容方式以及程序实现详解

    因为数组是在内存中连续的一段存储空间,所以数组一旦被创建,空间就固定了,长度是不能扩增的. 数组的长度是固定的,如果需要扩充**,必须创建新数组,原数组的长度要复制到新数组中 .** java中,数组类型的变量传值的时候,事实上传递的是数组的地址 . Java数组扩容的原理 1)Java数组对象的大小是固定不变的,数组对象是不可扩容的. 2)利用数组复制方法可以变通的实现数组扩容. 3)System.arraycopy()可以复制数组. 4)Arrays.copyOf()可以简便的创建数组副本.

  • Volley源码之使用方式和使用场景详解

    概述 Volley是Google在2013年推出的一个网络库,用于解决复杂网络环境下网络请求问题.刚推出的时候是非常火的,现在该项目的变动已经很少了.项目库地址为https://android.googlesource.com/platform/frameworks/volley 通过提交历史可以看到,最后一次修改距离今天已经有一段时间了.而volley包的release版本也已经很久没有更新了. author JeffDavidson<jpd@google.com> SunMar1316:3

  • PHP基于反射机制实现插件的可插拔设计详解

    本文实例讲述了PHP基于反射机制实现插件的可插拔设计.分享给大家供大家参考,具体如下: 说PHP和ASP等同的朋友们可以就此打住了,PHP支持反射,而且还是非常的强大.好了,我们开始今天的话题. 功能描述: 页面拥有一个主导航菜单,里头有默认连接若干. 插件统一存放在一个目录,插件载入后会自动在导航菜单中增加上自己所需的链接. 插件载入时可执行一定的操作. 动态增删插件无需改动代码. 最终效果: 首页,插件1,插件2 "首页"是系统自带的菜单项."插件1"和&quo

  • Golang语言的多种变量声明方式与使用场景详解

    目录 01介绍 02变量声明方式 标准声明变量 不显式赋初始值声明变量 省略类型声明变量 短变量声明 显式类型转换 变量列表声明 变量声明块 03使用场景 包级变量 全局变量 局部变量 04注意事项: 05总结 01介绍 在程序设计中,编译器必须将代表数据的变量名称替换成该数据所在的内存地址.变量的名称.类型及内存地址通常会维持固定,但该内存地址所存储的数据在程序执行期间则可能会改变. Golang 语言编译器需要先明确变量的内存边界,才可以使用变量.通过声明变量使用的类型,编译器可以明确变量的

  • Java中通过Class类获取Class对象的方法详解

    前言 本文主要给大家介绍的是关于Java通过Class类获取Class对象的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 阅读API的Class类得知,Class 没有公共构造方法.Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的 获取Class对象的三种方式(实例采用Person类) 方式1:通过Object类的getObject()方法 Person p = new Person(); Class c

  • JavaScript中的this原理及6种常见使用场景详解

    一.this原理 this是JavaScript的一个关键字,函数调用时才会出现: 因为函数是在一定的环境中运行的,调用函数时肯定需要知道是[谁调用的]?就用到了this进行指向: 那么this到底指向的是什么? this 既不指向函数自身,也不指函数的词法作用域,而是调用函数时的对象! 二.使用场景 (一)普通函数的调用,this指向的是Window var name = '卡卡'; function cat(){ var name = '有鱼'; console.log(this.name)

  • Java反射机制概念、原理与用法总结

    本文实例讲述了Java反射机制概念.原理与用法.分享给大家供大家参考,具体如下: 反射机制是什么 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. 反射机制能做什么 反射机制主要提供了以下功能: ① 在运行时判断任意一个对象所属的类: ② 在运行时构造任意一个类的对象: ③ 在运行时判断任意一个类所具有的成员变量和方法: ④ 在运行时调用任意一个

  • Java十分钟精通反射机制原理

    什么是反射? 反射机制是在运行状态中,它为Java提供一种"操作对象"的能力,在运行状态下,通过Class文件对象,可以调用到任何类里面的属性.方法.以及构造方法,包括私有的,所有的类在反射机制面前都是透明的 自己的概括:通过Class文件对象可以看到这个类里面的所有东西,并且可以使用和修改 反射的前提是获取Class文件对象((字节码对象),那么一共有三种方式获取: Class.forName("全类名") ----通过Class类的静态方法(最常用) 类名.cl

随机推荐