欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java反射機(jī)制原理、Class獲取方式以及應(yīng)用場(chǎng)景詳解

 更新時(shí)間:2022年04月20日 10:03:08   作者:吾日三省賈斯汀  
反射機(jī)制是JAVA的核心知識(shí)點(diǎn)之一,大多數(shù)框架的實(shí)現(xiàn)原理就是利用了反射機(jī)制,掌握反射機(jī)制會(huì)使你學(xué)習(xí)框架更加輕松高效,這篇文章主要給大家介紹了關(guān)于Java反射機(jī)制原理、Class獲取方式以及應(yīng)用場(chǎng)景的相關(guān)資料,需要的朋友可以參考下

??學(xué)習(xí)背景

學(xué)習(xí)Java的小伙伴,可能聽(tīng)過(guò)Java反射機(jī)制,但是熟悉又有點(diǎn)陌生,本文主要是通過(guò)思考面試中經(jīng)常被問(wèn)到的幾個(gè)Java反射機(jī)制的問(wèn)題,再通過(guò)理論知識(shí)結(jié)合代碼實(shí)例及應(yīng)用場(chǎng)景進(jìn)行講解,加深自己對(duì)Java反射機(jī)制的認(rèn)知和理解,也希望能幫助到有需要的小伙伴~

??一、Java反射機(jī)制是什么?

??1.1 反射原理

(1)Java反射機(jī)制(Java Reflection)是Java語(yǔ)言中一種動(dòng)態(tài)(運(yùn)行時(shí))訪問(wèn)、檢測(cè) & 修改它本身的能力,主要作用是動(dòng)態(tài)(運(yùn)行時(shí))獲取類的完整結(jié)構(gòu)信息 & 調(diào)用對(duì)象的方法~
更簡(jiǎn)單點(diǎn)的說(shuō)就是Java程序在運(yùn)行時(shí)(動(dòng)態(tài))通過(guò)創(chuàng)建一個(gè)類的反射對(duì)象,再對(duì)類進(jìn)行相關(guān)操作,比如:

獲取該對(duì)象的成員變量 & 賦值調(diào)用該對(duì)象的方法(含構(gòu)造方法,有參/無(wú)參)判斷該對(duì)象所屬的類

PS:不過(guò)說(shuō)實(shí)話,直接看比較官方的定義還是有點(diǎn)難理解,再來(lái)更加通俗點(diǎn)的說(shuō)吧~

(2)一般情況下,我們使用某個(gè)類,都會(huì)知道這個(gè)類,以及要用它來(lái)做什么,可以直接通過(guò)new實(shí)例化創(chuàng)建對(duì)象,然后使用這個(gè)對(duì)象對(duì)類進(jìn)行操作,這個(gè)就屬于正射~

(3)而反射則是一開(kāi)始并不知道要初始化的是什么類,無(wú)法使用new來(lái)實(shí)例化創(chuàng)建對(duì)象,主要是通過(guò)JDK提供的反射API來(lái)實(shí)現(xiàn),在運(yùn)行時(shí)才知道要操作的是什么類,并且可以獲取到類的完整構(gòu)造以及調(diào)用對(duì)應(yīng)的方法,這就是反射~

??1.2 反射例子

代碼如下:

package com.justin.java.lang;

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

/**
 * @program: Jdk1.8 Test
 * @description: 正射、反射簡(jiǎn)單調(diào)用示例
 * @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{
        //一、正射調(diào)用過(guò)程
        Student student = new Student();
        student.setId(1);
        System.out.println("正射調(diào)用過(guò)程Student id:" + student.getId());

        //二、反射調(diào)用過(guò)程
        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("正射調(diào)用過(guò)程Student id:" + getIdMethod.invoke(studentObj));
    }
}

輸出結(jié)果:

正射調(diào)用過(guò)程Student id:1
反射調(diào)用過(guò)程Student id:2

上述例子反射的調(diào)用過(guò)程,可以看到獲取一個(gè)類的反射對(duì)象,主要過(guò)程為:

  • 獲取類的Class實(shí)例對(duì)象
  • 根據(jù)Class實(shí)例對(duì)象獲取Constructor對(duì)象
  • 再根據(jù)Constructor對(duì)象的newInstance方法獲取到類的反射對(duì)象

獲取到類的反射對(duì)象后,就可以對(duì)類進(jìn)行操作了~ 例如,上述示例中對(duì)類的方法進(jìn)行調(diào)用過(guò)程為:

  • 根據(jù)Class實(shí)例對(duì)象獲取到類的Method對(duì)象
  • 再根據(jù)Method對(duì)象的invoke方法調(diào)用到具體類的方法

前面一點(diǎn)也提到了獲取到類的Class實(shí)例對(duì)象,上面示例反向調(diào)用過(guò)程中我們是通過(guò)Class.forName("類的全局定名")這種方式來(lái)獲取到類的Class實(shí)例對(duì)象,除了這種,常用的還有其他兩種,往下講解~

??二、Java反射機(jī)制中獲取Class的三種方式及區(qū)別?

??2.1 Class的幾種獲取方式

(1)獲取類的java.lang.Class實(shí)例對(duì)象,常見(jiàn)的三種方式分別為:

通過(guò)MyClass.class獲取,這里的MyClass指具體類~~通過(guò)Class.forName("類的全局定名")獲取,全局定名為包名+類名通過(guò)new MyClass().getClass()獲取,這里的MyClass指具體類~

(2)通過(guò)MyClass.class獲取,JVM會(huì)使用ClassLoader類加載器將類加載到內(nèi)存中,但并不會(huì)做任何類的初始化工作,返回java.lang.Class對(duì)象

(3)通過(guò)Class.forName("類的全局定名")獲取,同樣,類會(huì)被JVM加載到內(nèi)存中,并且會(huì)進(jìn)行類的靜態(tài)初始化工作,返回java.lang.Class對(duì)象

(4)通過(guò)new MyClass().getClass()獲取,這種方式使用了new進(jìn)行實(shí)例化操作,因此靜態(tài)初始化和非靜態(tài)初始化工作都會(huì)進(jìn)行,getClass方法屬于頂級(jí)Object類中的方法,任何子類對(duì)象都可以調(diào)用,哪個(gè)子類調(diào)用,就返回那個(gè)子類的java.lang.Class對(duì)象

PS: 這3種方式,最終在JVM堆區(qū)對(duì)應(yīng)類的java.lang.Class對(duì)象都屬于同一個(gè),也就是內(nèi)存地址相同,進(jìn)行==雙等號(hào)比較結(jié)果為true,原因是JVM類加載過(guò)程中使用的是同一個(gè)ClassLoader類加載器加載某個(gè)類,不論加載多少次,生成到堆區(qū)的java.lang.Class對(duì)象始終只有一個(gè),除非自定義類加載器,破壞JVM的雙親委派機(jī)制,使得同一個(gè)類被不同類加載器加載,JVM才會(huì)把它當(dāng)做兩個(gè)不同的java.lang.Class對(duì)象

??2.2 代碼演示幾種方式的區(qū)別

創(chuàng)建一個(gè)實(shí)體類,分別在實(shí)體類中創(chuàng)建類的靜態(tài)代碼塊、動(dòng)態(tài)代碼塊、有參構(gòu)造方法、無(wú)參構(gòu)造方法,方便測(cè)試幾種方式的區(qū)別及內(nèi)存地址是否相同~

(1)實(shí)體類:

public class MyClass {
    private static final String staticStr = "Hi";
    private static int staticInt = 2021;
    private String id;

    static {
        System.out.println("靜態(tài)代碼塊:staticStr=" + staticStr + ",staticInt=" + staticInt);
    }

    {
        System.out.println("動(dòng)態(tài)代碼塊~");
    }

    public MyClass() {
        System.out.println("無(wú)參構(gòu)造方法~");
    }

    public MyClass(String id) {
        System.out.println("有參構(gòu)造方法~");
        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)單元測(cè)試類:
通過(guò)@Test注解對(duì)三種方式分別進(jìn)行單元測(cè)試,再對(duì)這三種方式的組合進(jìn)行單元測(cè)試~

package com.justin.java.lang;

import org.junit.Test;

/**
 * @program: Jdk1.8Test
 * @description: Java反射機(jī)制中獲取類的Class實(shí)例對(duì)象的常見(jiàn)三種方式及區(qū)別對(duì)比
 * @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("四、三種方式內(nèi)存地址比較=========");
        Class<?> class1 = MyClass.class;
        Class class2 = Class.forName("com.justin.java.lang.MyClass");
        Class class3 = new MyClass().getClass();
        System.out.println("比較結(jié)果=========");
        System.out.println("MyClass.class和Class.forName內(nèi)存地址比較是否相同:" + (class1 == class2));
        System.out.println("MyClass.class和new MyClass().getClass內(nèi)存地址比較是否相同:" + (class1 == class3));
        System.out.println("Class.forName和new MyClass().getClass內(nèi)存地址比較是否相同:" + (class2 == class3));
    }
}

逐個(gè)執(zhí)行單元,得出測(cè)試結(jié)果為:

* test1()方法
一、MyClass.class方式=========

*  test2()方法
二、Class.forName方式=========
靜態(tài)代碼塊:staticStr=Hi,staticInt=2021

*  test3()方法
三、new MyClass().getClass方式=========
靜態(tài)代碼塊:staticStr=Hi,staticInt=2021
動(dòng)態(tài)代碼塊~
無(wú)參構(gòu)造方法~

*  test12()方法
一、MyClass.class方式=========
二、Class.forName方式=========
靜態(tài)代碼塊:staticStr=Hi,staticInt=2021

*  test13()方法
一、MyClass.class方式=========
三、new MyClass().getClass方式=========
靜態(tài)代碼塊:staticStr=Hi,staticInt=2021
動(dòng)態(tài)代碼塊~
無(wú)參構(gòu)造方法~

*  test23()方法
二、Class.forName方式=========
靜態(tài)代碼塊:staticStr=Hi,staticInt=2021
三、new MyClass().getClass方式=========
動(dòng)態(tài)代碼塊~
無(wú)參構(gòu)造方法~

*  test()方法
四、三種方式內(nèi)存地址比較=========
靜態(tài)代碼塊:staticStr=Hi,staticInt=2021
動(dòng)態(tài)代碼塊~
無(wú)參構(gòu)造方法~
比較結(jié)果=========
MyClass.class和Class.forName內(nèi)存地址比較是否相同:true
MyClass.class和new MyClass().getClass內(nèi)存地址比較是否相同:true
Class.forName和new MyClass().getClass內(nèi)存地址比較是否相同:true

通過(guò)test1、test2、test3的測(cè)試結(jié)果驗(yàn)證了2.1 三種方式及區(qū)別中黃色標(biāo)記部分的區(qū)別說(shuō)明,即:

MyClass.class不會(huì)做任何類的初始化工作Class.forName會(huì)進(jìn)行類的靜態(tài)初始化工作new MyClass().getClass靜態(tài)初始化和非靜態(tài)初始化工作都會(huì)進(jìn)行使用這三種方式任意一種最終在JVM加載到內(nèi)存中都會(huì)是內(nèi)存地址相同的

而test23組合得到的測(cè)試結(jié)果,說(shuō)明靜態(tài)代碼塊只會(huì)被加載一次~

講了這么多,除了知道基本原理和基本使用之外,更重要的還是要知道它的一些比較實(shí)際的應(yīng)用場(chǎng)景,往下介紹~

??三、Java反射機(jī)制的應(yīng)用場(chǎng)景有哪些?

??3.1 應(yīng)用場(chǎng)景

  • 工廠模式中的簡(jiǎn)單工廠模式優(yōu)化
  • 代理模式中的動(dòng)態(tài)代理方式實(shí)現(xiàn)
  • Java JDBC數(shù)據(jù)庫(kù)操作

??3.2 簡(jiǎn)單工廠模式優(yōu)化

??3.2.1 什么是簡(jiǎn)單工廠模式?

Java中主要有23種設(shè)計(jì)模式,其中工廠模式就是其中一種,而簡(jiǎn)單工廠模式,顧名思義,也是屬于工廠模式中的一種,只不過(guò)比較簡(jiǎn)單。簡(jiǎn)單工廠模式也可以叫做靜態(tài)方法模式(因?yàn)楣S類一般都是在內(nèi)部定義了一個(gè)靜態(tài)方法)。
從現(xiàn)實(shí)生活角度來(lái)理解的話,工廠是專門負(fù)責(zé)生產(chǎn)產(chǎn)品的,同樣在設(shè)計(jì)模式中,簡(jiǎn)單工廠模式我們可以理解為專門負(fù)責(zé)生產(chǎn)對(duì)象的一個(gè)類,稱為“工廠類”。

??3.2.2 簡(jiǎn)單工廠模式有什么用?

簡(jiǎn)單工廠模式通過(guò)創(chuàng)建一個(gè)對(duì)應(yīng)的工廠類,將類實(shí)例化的操作與使用對(duì)象的操作進(jìn)行分開(kāi),讓使用者不用知道具體參數(shù)就可以實(shí)例化出所需要的具體產(chǎn)品類,從而避免了在客戶端代碼中顯式指定,實(shí)現(xiàn)了解耦。即使用者可直接消費(fèi)產(chǎn)品而不需要知道其生產(chǎn)的細(xì)節(jié)~

??3.2.3 如何實(shí)現(xiàn)簡(jiǎn)單工程模式?

實(shí)現(xiàn)簡(jiǎn)單工程模式的核心是創(chuàng)建一個(gè)工廠類,并且在內(nèi)部定義了一個(gè)靜態(tài)方法,傳入不同的參數(shù)標(biāo)識(shí)通過(guò)switch進(jìn)行分組,通過(guò)new實(shí)例化創(chuàng)建不同的子類對(duì)象返回~

實(shí)現(xiàn)例子:

步驟1:創(chuàng)建抽象產(chǎn)品類

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

步驟2:創(chuàng)建具體產(chǎn)品類:

public class ProductA implements Product {
    @Override
    public void show() {
        System.out.println("生產(chǎn)了產(chǎn)品A");
    }
}
public class ProductB implements Product {
    @Override
    public void show() {
        System.out.println("生產(chǎn)了產(chǎn)品B");
    }
}

public class ProductC implements Product {
    @Override
    public void show() {
        System.out.println("生產(chǎn)了產(chǎn)品C");
    }
}

步驟3:創(chuàng)建簡(jiǎn)單工廠類

public class SimpleFactory {
    /**
     * 實(shí)現(xiàn)簡(jiǎn)單工廠模式
     * @param pName 產(chǎn)品標(biāo)識(shí)
     * @return 返回具體的產(chǎn)品
     */
    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:調(diào)用簡(jiǎn)單工廠類

public class SimpleFactoryTest {
    public static void main(String[] args) {
        try {
            SimpleFactory.createProduct("A").show();
        } catch (NullPointerException e) {
            System.out.println("沒(méi)有A這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }
        try {
            SimpleFactory.createProduct("B").show();
        } catch (NullPointerException e) {
            System.out.println("沒(méi)有B這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }
        try {
            SimpleFactory.createProduct("C").show();
        } catch (NullPointerException e) {
            System.out.println("沒(méi)有C這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }
        try {
            SimpleFactory.createProduct("D").show();
        } catch (NullPointerException e) {
            System.out.println("沒(méi)有D這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }
    }
}

??3.2.4 簡(jiǎn)單工廠模式優(yōu)化

(1)簡(jiǎn)單工廠模式弊端

  • 操作成本高:每增加一個(gè)接口的子類,必須修改工廠類的邏輯
  • 系統(tǒng)復(fù)雜性提高:每增加一個(gè)接口的子類,都必須向工廠類添加邏輯

這兩點(diǎn)弊端從前面的例子SimpleFactory工廠類的實(shí)現(xiàn),可以看出簡(jiǎn)單工廠模式中對(duì)工廠類SimpleFactory的維護(hù)成本有點(diǎn)大,因?yàn)閷?shí)際中可能會(huì)很頻繁的去更新具體產(chǎn)品類,每一次變更都需要去修改工廠類,此時(shí)就可以利用Java反射機(jī)制對(duì)簡(jiǎn)單工廠模式進(jìn)行優(yōu)化~

(2)簡(jiǎn)單工廠模式的優(yōu)化思路

采用Java反射機(jī)制,通過(guò)傳入子類全局定名(包名+類名) 動(dòng)態(tài)的創(chuàng)建不同的子類對(duì)象實(shí)例,從而使得在不增加產(chǎn)品接口子類和修改工廠類的邏輯的情況下還能實(shí)現(xiàn)了工廠類對(duì)子類實(shí)例對(duì)象的統(tǒng)一創(chuàng)建~

(3)簡(jiǎn)單工廠模式的優(yōu)化步驟

步驟1:創(chuàng)建工廠類

采用Java反射機(jī)制對(duì)工廠類進(jìn)行優(yōu)化,主要是將className即子類全局定名(包名+類名)作為入?yún)?,通過(guò)Class.forName方式獲取類的java.lang.Class實(shí)例對(duì)象,再通過(guò)Class實(shí)例對(duì)象的getInstance方法獲取到具體子類的實(shí)例對(duì)象~

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:調(diào)用工廠類

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("沒(méi)有A這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }

        try {
            Product productB = Factory.getInstance("com.justin.java.lang.ProductB");
            productB.show();
        } catch (NullPointerException e) {
            System.out.println("沒(méi)有B這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }

        try {
            Product productC = Factory.getInstance("com.justin.java.lang.ProductC");
            productC.show();
        } catch (NullPointerException e) {
            System.out.println("沒(méi)有C這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }

        try {
            Product productD = Factory.getInstance("com.justin.java.lang.ProductD");
            productD.show();
        } catch (Exception e) {
            System.out.println("沒(méi)有D這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }
    }
}

優(yōu)化結(jié)果:

使用Java反射機(jī)制優(yōu)化簡(jiǎn)單工廠模式后,可以看到,不論具體產(chǎn)品類更新多頻繁,都不需要再修改工廠類,從而解決了普通簡(jiǎn)單工廠模式操作成本高和系統(tǒng)復(fù)雜性高的問(wèn)題~

??3.2.5 簡(jiǎn)單工廠模式再次優(yōu)化

(1)再次優(yōu)化背景

簡(jiǎn)單工廠模式的工廠類采用Java反射機(jī)制進(jìn)行優(yōu)化后,此時(shí)的仍然存在這樣一個(gè)問(wèn)題,子類的全局定名(包名+類名)是寫(xiě)死的,但是實(shí)際上開(kāi)發(fā)者在寫(xiě)代碼時(shí)是很難提前預(yù)知所有的子類的全局定名(包名+類名)的,因此需要進(jìn)行二次優(yōu)化~

(2)再次優(yōu)化實(shí)現(xiàn)思路

通過(guò)配置文件方式,統(tǒng)一定義類名對(duì)應(yīng)全局定名(包名+類名),將配置文件存放到資源目錄下,程序運(yùn)行時(shí)通過(guò)ClassLoader類加載器動(dòng)態(tài)獲取到配置文件中定義的子類的全局定名~

(3)再次優(yōu)化實(shí)現(xiàn)步驟

再次優(yōu)化步驟1:相關(guān)優(yōu)化與第一次優(yōu)化保持不變~

再次優(yōu)化步驟2:配置類名對(duì)應(yīng)全局定名(包名+類名)

創(chuàng)建屬性配置文件Product.properties

//產(chǎn)品抽象類Product相關(guān)子類的全局定名(包名+類名)定義
ProductA = com.justin.java.lang.ProductA
ProductB = com.justin.java.lang.ProductB
ProductC = com.justin.java.lang.ProductC

注意:將Product.properties需要存放在src/main/resources資源目錄下,若資源目錄不存在則需要手動(dòng)創(chuàng)建~

再次優(yōu)化步驟3:修改調(diào)用工廠類

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("沒(méi)有A這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }

        try {
            className = prop.getProperty("ProductB");
            Product productA = Factory.getInstance(className);
            productA.show();
        } catch (NullPointerException e) {
            System.out.println("沒(méi)有B這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }

        try {
            className = prop.getProperty("ProductC");
            Product productA = Factory.getInstance(className);
            productA.show();
        } catch (NullPointerException e) {
            System.out.println("沒(méi)有C這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }
    }
}

運(yùn)行結(jié)果:

生產(chǎn)了產(chǎn)品A
生產(chǎn)了產(chǎn)品B
生產(chǎn)了產(chǎn)品C

??3.3 代理模式中的動(dòng)態(tài)代理實(shí)現(xiàn)

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("沒(méi)有A這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }

        try {
            className = prop.getProperty("ProductB");
            Product productA = Factory.getInstance(className);
            productA.show();
        } catch (NullPointerException e) {
            System.out.println("沒(méi)有B這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }

        try {
            className = prop.getProperty("ProductC");
            Product productA = Factory.getInstance(className);
            productA.show();
        } catch (NullPointerException e) {
            System.out.println("沒(méi)有C這款產(chǎn)品,無(wú)法生產(chǎn)~");
        }
    }
}

??3.3.1 什么是代理模式?

代理(Proxy)模式是一種設(shè)計(jì)模式,通過(guò)代理對(duì)象來(lái)訪問(wèn)目標(biāo)對(duì)象,還可以在不修改目標(biāo)對(duì)象的情況下,對(duì)代理對(duì)象進(jìn)行拓展,增強(qiáng)目標(biāo)對(duì)象的功能~

什么?還是不太理解?

更通俗一點(diǎn)的說(shuō)代理模式,就是想做某件事(買火車票),自己能買(直接去火車站買),卻委托別人去買(沒(méi)空還是代理點(diǎn)買吧),還可以讓別人幫自己做其他事(訂好酒店)~

代理模式又分為靜態(tài)代理、動(dòng)態(tài)代理,往下介紹~

??3.3.2 什么是靜態(tài)代理?

(1)靜態(tài)代理屬于代理模式的一種代理方式,需要代理對(duì)象和目標(biāo)對(duì)象實(shí)現(xiàn)相同的接口
(2)靜態(tài)代理的代理類是由程序員編寫(xiě)源碼,編譯后即可獲取到代理類的class字節(jié)碼文件,也就是在程序運(yùn)行前就已經(jīng)得到實(shí)際的代理類class字節(jié)碼文件了

??3.3.2 什么是動(dòng)態(tài)代理?

動(dòng)態(tài)代理

(1)動(dòng)態(tài)代理也屬于代理模式的一種代理方式,不過(guò)只需要目標(biāo)對(duì)象實(shí)現(xiàn)接口,代理對(duì)象不需要實(shí)現(xiàn)接口~
(2)動(dòng)態(tài)代理的代理類編譯后是沒(méi)有class字節(jié)碼文件的,而是在運(yùn)行時(shí)利用Java反射機(jī)制動(dòng)態(tài)的生成代理類的class字節(jié)碼文件~

動(dòng)態(tài)代理最常用的是JDK原生動(dòng)態(tài)代理和cglib動(dòng)態(tài)代理,往下介紹~

JDK 原生動(dòng)態(tài)代理

JDK 原生動(dòng)態(tài)代理,主要利用了JDK API
java.lang.reflect.Proxyjava.lang.relfect.InnvocationHandler 這兩個(gè)類來(lái)實(shí)現(xiàn)~

通過(guò)java.lang.reflect.Proxy代理類的newProxyInstance方法,傳遞3個(gè)參數(shù),分別是:
目標(biāo)對(duì)象的加載器 通過(guò)MyClass.getClass().getClassLoader方式獲取
目標(biāo)對(duì)象的實(shí)現(xiàn)接口類型 通過(guò)Object.getClass().getInterfaces()方式獲取
InnvocationHandler事件處理器 通過(guò)new取

例子:

用戶接口類IUserDao

public interface IUserDao {
    //添加數(shù)據(jù)
    public void insert();
}

目標(biāo)對(duì)象類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("添加數(shù)據(jù)");
    }
}

動(dòng)態(tài)代理類UserProxy

/**
 * @program: Jdk1.8Test
 * @description: 動(dòng)態(tài)代理類
 * @author: JustinQin
 * @create: 2021/8/23 23:31
 * @version: v1.0.0
 **/
public class UserProxy {
    private Object target; //目標(biāo)對(duì)象

    public UserProxy(Object target) {
        this.target = target;
    }

    /**
     * 利用JDK API獲取到代理對(duì)象
     * @return
     */
    public Object getProxyInstance() {
        //目標(biāo)對(duì)象的加載器
        ClassLoader loader = target.getClass().getClassLoader();

        //目標(biāo)對(duì)象的實(shí)現(xiàn)接口類型
        Class<?>[] interfaces = target.getClass().getInterfaces();

        //InnvocationHandler事件處理器實(shí)例對(duì)象
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("添加數(shù)據(jù)前:手動(dòng)開(kāi)啟事務(wù)");
                // 執(zhí)行目標(biāo)對(duì)象方法
                Object value = method.invoke(target, args);
                System.out.println("添加數(shù)據(jù)后:手動(dòng)提交事務(wù)");
                return null;
            }
        };
        //傳入3個(gè)參數(shù),創(chuàng)建代理類的實(shí)例對(duì)象,并返回
        return Proxy.newProxyInstance(loader, interfaces,h);
    }
}


動(dòng)態(tài)代理單元測(cè)試類

/**
 * @program: 動(dòng)態(tài)代理單元測(cè)試類
 * @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("目標(biāo)對(duì)象信息:" + target.getClass());
        //獲取代理類實(shí)例對(duì)象
        IUserDao proxy = (IUserDao) new UserProxy(target).getProxyInstance();
        System.out.println("代理對(duì)象信息:" + proxy.getClass());
        //執(zhí)行代理方法
        proxy.insert();
    }
}

單元測(cè)試執(zhí)行結(jié)果

目標(biāo)對(duì)象信息:class com.justin.java.reflect.UserDao
代理對(duì)象信息:class com.sun.proxy.$Proxy2
添加數(shù)據(jù)前:手動(dòng)開(kāi)啟事務(wù)
添加數(shù)據(jù)
添加數(shù)據(jù)后:手動(dòng)提交事務(wù)

cglib動(dòng)態(tài)代理

cglib (Code Generation Library )是一個(gè)第三方代碼生成類庫(kù),運(yùn)行時(shí)在內(nèi)存中動(dòng)態(tài)生成一個(gè)子類對(duì)象從而實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象功能的擴(kuò)展。

Spring AOP結(jié)合了cglib動(dòng)態(tài)代理和JDK原生動(dòng)態(tài)代理來(lái)實(shí)現(xiàn),這里不過(guò)多介紹,有興趣小伙伴可以查閱資料學(xué)習(xí)下~

??3.3.3 動(dòng)態(tài)代理中如何利用Java反射機(jī)制?

JDK原生動(dòng)態(tài)代理中,獲取代理示例對(duì)象過(guò)程中,獲取目標(biāo)對(duì)象的類加載器,通過(guò)target.getClass().getClassLoader(獲取到目標(biāo)對(duì)象的類加載器,target.getClass()方式獲取目標(biāo)對(duì)象的Class實(shí)例對(duì)象使用的就是Java反射機(jī)制來(lái)實(shí)現(xiàn)的~

??3.4 Java JDBC數(shù)據(jù)庫(kù)操作實(shí)現(xiàn)

??3.4.1 利用反射加載JDBC驅(qū)動(dòng)

相信很多小伙伴都知道Java JDBC連接數(shù)據(jù)庫(kù)主要分為七大步驟,其中第一步加載JDBC驅(qū)動(dòng),利用Java反射機(jī)制通過(guò)傳入不同的驅(qū)動(dòng)名稱,加載不同數(shù)據(jù)庫(kù)的驅(qū)動(dòng)~

Class.forName("com.mysql.jdbc.Driver"); //加載MySQL驅(qū)動(dòng)
Class.forName("oracle.jdbc.driver.OracleDriver"); //加載Oracle驅(qū)動(dòng)

鏈接:Mysql驅(qū)動(dòng)架包mysql-connector-java-5.1.30.jar

鏈接:Oracle驅(qū)動(dòng)架包ojdbc14-10.2.0.4.0.jar

??3.4.2 Java JDBC連接示例

創(chuàng)建測(cè)試庫(kù)表及數(shù)據(jù)

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驅(qū)動(dòng)
        Class.forName("com.mysql.jdbc.Driver");
        //2.獲取數(shù)據(jù)庫(kù)的連接(Connection)對(duì)象
        Connection connection = DriverManager.getConnection(
                "jdbc:mysql://localhost/test", //mysql連接url,test表示你要連接的數(shù)據(jù)庫(kù)名
                "root", //數(shù)據(jù)庫(kù)用戶名
                "abc@123456"); //密碼
        //3.獲取數(shù)據(jù)庫(kù)的操作(PrepareStatement)對(duì)象
        PreparedStatement prepareStatement = connection.prepareStatement("select * from TEST.user where id = ?");
        //4.設(shè)置傳入?yún)?shù)
        prepareStatement.setInt(1, 1);
        //5.上傳sql語(yǔ)句到服務(wù)器執(zhí)行(excute),并返回結(jié)果集(ResultSet)
        ResultSet result = prepareStatement.executeQuery();
        //6.處理返回的ResultSet結(jié)果集
        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.釋放相關(guān)資源:Connection對(duì)象、PrepareStatement對(duì)象、ResultSet對(duì)象。
        connection.close();
        prepareStatement.close();
        result.close();
    }

執(zhí)行結(jié)果:

1,張一,男,21

Java Oracle JDBC連接七大步驟~

public class JdbcOracleTest {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //1.加載JDBC驅(qū)動(dòng)
        Class.forName("oracle.jdbc.driver.OracleDriver");
        //2.獲取數(shù)據(jù)庫(kù)的連接(Connection)對(duì)象
        Connection connection = DriverManager.getConnection(
                "jdbc:oracle:thin:@127.0.0.1:1521:orcl",	//oracle連接url
                "root", //數(shù)據(jù)庫(kù)用戶名
                "abc@123456"); //密碼
        //3.獲取數(shù)據(jù)庫(kù)的操作(PrepareStatement)對(duì)象
        PreparedStatement prepareStatement = connection.prepareStatement("select * from TEST.user where id = ?");
        //4.設(shè)置傳入?yún)?shù)
        prepareStatement.setInt(1, 1);
        //5.上傳sql語(yǔ)句到服務(wù)器執(zhí)行(excute),并返回結(jié)果集(ResultSet)
        ResultSet result = prepareStatement.executeQuery();
        //6.處理返回的ResultSet結(jié)果集
        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.釋放相關(guān)資源:Connection對(duì)象、PrepareStatement對(duì)象、ResultSet對(duì)象。
        connection.close();
        prepareStatement.close();
        result.close();
    }
}

PS:上面通過(guò)Java JDBC連接數(shù)據(jù)庫(kù)并進(jìn)行操作,這里的連接是單一連接,直接通過(guò)DriverManager.getConnection這種Java原生的數(shù)據(jù)庫(kù)連接方式建立的連接,現(xiàn)在實(shí)際的Java Spring項(xiàng)目當(dāng)中,都是通過(guò)配置mybatis的數(shù)據(jù)庫(kù)連接池來(lái)實(shí)現(xiàn)的,不過(guò)原理都是一樣的,加載驅(qū)動(dòng)也是利用了Java反射機(jī)制指定不同的驅(qū)動(dòng)名稱,實(shí)現(xiàn)不同數(shù)據(jù)庫(kù)驅(qū)動(dòng)的加載~

數(shù)據(jù)庫(kù)連接池配置spring-mybatis.xml

	<!-- 基于tomcat jdbc連接池的數(shù)據(jù)源  -->
    <bean id="dataSource" class="com.justin.datasource.TomcatDataSource" init-method="createPool">
		<!-- 基于dbcp連接池的數(shù)據(jù)源
		<bean id="dataSource" class="com.justin.datasource.DbcpDataSource" destroy-method="close"> -->
		<!-- 基于阿里druid連接池的數(shù)據(jù)源
        <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}" />
		<!-- 連接池最大數(shù)量 -->
		<property name="maxActive" value="${app-data-source.maxActive}" />
		<!-- 連接池最大空閑 -->
		<property name="maxIdle" value="${app-data-source.maxIdle}" />
		<!-- 連接池最小空閑 -->
		<property name="minIdle" value="${app-data-source.minIdle}" />
		<!-- 獲取連接最大等待時(shí)間 -->
		<property name="maxWait" value="${app-data-source.maxWait}" />
	</bean>

數(shù)據(jù)庫(kù)配置信息jdbc.propertis

#數(shù)據(jù)庫(kù)連接驅(qū)動(dòng)
app-data-source.driverClassName=com.mysql.jdbc.Driver
#數(shù)據(jù)庫(kù)連接url
app-data-source.url=jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=UTF-8
#數(shù)據(jù)庫(kù)用戶
app-data-source.username=root
#數(shù)據(jù)庫(kù)用戶密碼(加密)
app-data-source.password=abc@123456
#連接池初始化大小
app-data-source.initialSize=10
#連接池最大數(shù)量
app-data-source.maxActive=50
#連接池最大空閑
app-data-source.maxIdle=20
#連接池最小空閑
app-data-source.minIdle=5
#獲取連接最大等待時(shí)間
app-data-source.maxWait=30000

面試總結(jié)

一、Java反射機(jī)制是什么?

1、Java反射機(jī)制(Java Reflection)是Java語(yǔ)言中一種動(dòng)態(tài)(運(yùn)行時(shí))訪問(wèn)、檢測(cè) & 修改它本身的能力,主要作用是動(dòng)態(tài)(運(yùn)行時(shí))獲取類的完整結(jié)構(gòu)信息 & 調(diào)用對(duì)象的方法~

更簡(jiǎn)單點(diǎn)的說(shuō)就是Java程序在運(yùn)行時(shí)(動(dòng)態(tài))通過(guò)創(chuàng)建一個(gè)類的反射對(duì)象,再對(duì)類進(jìn)行相關(guān)操作,比如:

  • 獲取該對(duì)象的成員變量 & 賦值
  • 調(diào)用該對(duì)象的方法(含構(gòu)造方法,有參/無(wú)參)
  • 判斷該對(duì)象所屬的類

2、更通俗點(diǎn)的說(shuō),我們使用某個(gè)類,都會(huì)知道這個(gè)類,以及要用它來(lái)做什么,可以直接通過(guò)new實(shí)例化創(chuàng)建對(duì)象,然后使用這個(gè)對(duì)象對(duì)類進(jìn)行操作,這個(gè)就屬于正射~

3、而反射則是一開(kāi)始并不知道要初始化的是什么類,無(wú)法使用new來(lái)實(shí)例化創(chuàng)建對(duì)象,主要是通過(guò)JDK提供的反射API來(lái)實(shí)現(xiàn),在運(yùn)行時(shí)才知道要操作的是什么類,并且可以獲取到類的完整構(gòu)造以及調(diào)用對(duì)應(yīng)的方法,這就是反射~

二、Java反射機(jī)制中獲取Class的三種方式及區(qū)別?

1、獲取類的java.lang.Class實(shí)例對(duì)象,常見(jiàn)的三種方式分別為:

  • 通過(guò)MyClass.class獲取
  • 通過(guò)Class.forName("類的全局定名")獲取
  • 通過(guò)new MyClass().getClass()獲取

2、通過(guò)MyClass.class獲取,JVM會(huì)使用ClassLoader類加載器將類加載到內(nèi)存中,但并不會(huì)做任何類的初始化工作,返回java.lang.Class對(duì)象

3、通過(guò)Class.forName("類的全局定名")獲取,同樣,類會(huì)被JVM加載到內(nèi)存中,并且會(huì)進(jìn)行類的靜態(tài)初始化工作,返回java.lang.Class對(duì)象

4、通過(guò)new MyClass().getClass()獲取,這種方式使用了new進(jìn)行實(shí)例化操作,因此== 靜態(tài)初始化和非靜態(tài)初始化工作都會(huì)進(jìn)行 == ,getClass方法屬于頂級(jí)Object類中的方法,任何子類對(duì)象都可以調(diào)用,哪個(gè)子類調(diào)用,就返回那個(gè)子類的java.lang.Class對(duì)象

5、這3種方式,最終在JVM堆區(qū)對(duì)應(yīng)類的java.lang.Class對(duì)象都屬于同一個(gè),也就是內(nèi)存地址相同,進(jìn)行==雙等號(hào)比較結(jié)果為true,原因是JVM類加載過(guò)程中使用的是同一個(gè)ClassLoader類加載器加載某個(gè)類,不論加載多少次,生成到堆區(qū)的java.lang.Class對(duì)象始終只有一個(gè),除非自定義類加載器,破壞JVM的雙親委派機(jī)制,使得同一個(gè)類被不同類加載器加載,JVM才會(huì)把它當(dāng)做兩個(gè)不同的java.lang.Class對(duì)象

三、Java反射機(jī)制的應(yīng)用場(chǎng)景有哪些?

  • 工廠模式中的簡(jiǎn)單工廠模式優(yōu)化
  • 代理模式中的動(dòng)態(tài)代理方式實(shí)現(xiàn)
  • Java JDBC數(shù)據(jù)庫(kù)操作

總結(jié)

到此這篇關(guān)于Java反射機(jī)制原理、Class獲取方式以及應(yīng)用場(chǎng)景的文章就介紹到這了,更多相關(guān)Java反射原理及Class獲取內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java中ArrayList的使用詳細(xì)介紹

    Java中ArrayList的使用詳細(xì)介紹

    這篇文章主要介紹了Java中ArrayList的使用,本文給大家詳細(xì)講述該相關(guān)的知識(shí)點(diǎn),并且會(huì)通過(guò)大量的案例加以說(shuō)明,需要的朋友可以參考一下
    2022-04-04
  • Java中如何獲取mysql連接的3種方法總結(jié)

    Java中如何獲取mysql連接的3種方法總結(jié)

    最近工作中需要用到mysql連接,發(fā)現(xiàn)實(shí)現(xiàn)的方法不止一個(gè),所以就來(lái)總結(jié)下,下面這篇文章主要給大家介紹了關(guān)于Java中如何獲取mysql連接的3種方法,需要的朋友可以參考借鑒,感興趣的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-08-08
  • Java開(kāi)發(fā)中synchronized的定義及用法詳解

    Java開(kāi)發(fā)中synchronized的定義及用法詳解

    這篇文章主要介紹了Java開(kāi)發(fā)中synchronized的定義及用法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Java靜態(tài)方法不能調(diào)用非靜態(tài)成員的原因分析

    Java靜態(tài)方法不能調(diào)用非靜態(tài)成員的原因分析

    在Java中,靜態(tài)方法是屬于類的方法,而不是屬于對(duì)象的方法,它可以通過(guò)類名直接調(diào)用,無(wú)需創(chuàng)建對(duì)象實(shí)例,非靜態(tài)成員指的是類的實(shí)例變量和實(shí)例方法,它們需要通過(guò)對(duì)象實(shí)例才能訪問(wèn)和調(diào)用,本文小編將和大家一起探討Java靜態(tài)方法為什么不能調(diào)用非靜態(tài)成員
    2023-10-10
  • Java中遞歸、循環(huán)的優(yōu)劣分析

    Java中遞歸、循環(huán)的優(yōu)劣分析

    這篇文章主要給大家介紹了關(guān)于Java中遞歸、循環(huán)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • SpringMvc3+extjs4實(shí)現(xiàn)上傳與下載功能

    SpringMvc3+extjs4實(shí)現(xiàn)上傳與下載功能

    這篇文章主要為大家詳細(xì)介紹了SpringMvc3+extjs4實(shí)現(xiàn)上傳與下載功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • java 算法之快速排序?qū)崿F(xiàn)代碼

    java 算法之快速排序?qū)崿F(xiàn)代碼

    這篇文章主要介紹了java 算法之快速排序?qū)崿F(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • Java編程實(shí)現(xiàn)遍歷兩個(gè)MAC地址之間所有MAC的方法

    Java編程實(shí)現(xiàn)遍歷兩個(gè)MAC地址之間所有MAC的方法

    這篇文章主要介紹了Java編程實(shí)現(xiàn)遍歷兩個(gè)MAC地址之間所有MAC的方法,涉及Java針對(duì)MAC的遍歷獲取與字符串轉(zhuǎn)換相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-11-11
  • 詳解jvm對(duì)象的創(chuàng)建和分配

    詳解jvm對(duì)象的創(chuàng)建和分配

    這篇文章主要介紹了jvm對(duì)象的創(chuàng)建和分配的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下
    2021-03-03
  • 在Java SE上使用Headless模式的超級(jí)指南

    在Java SE上使用Headless模式的超級(jí)指南

    這篇文章主要介紹了在Java SE上使用Headless模式的超級(jí)指南,文中介紹了Headless模式實(shí)際使用的各種技巧,極力推薦!需要的朋友可以參考下
    2015-07-07

最新評(píng)論