詳細解析Java反射機制原理和幾種Class獲取方式

2022-03-23 16:00:50
本篇文章給大家帶來了關於的相關知識,其中主要介紹了反射機制原理以及幾種class獲取方式和應用場景的相關問題,希望對大家有幫助。

推薦學習:《》

學習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記憶體地址比較是否相同: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 什麼是簡單工廠模式?

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.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 什麼是代理模式?

代理(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範例化物件並重寫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動態代理

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驅動

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獲取方式的詳細內容,更多請關注TW511.COM其它相關文章!