Java反射機制原理、Class獲取方式以及應(yīng)用場景詳解
??學(xué)習(xí)背景
學(xué)習(xí)Java的小伙伴,可能聽過Java反射機制,但是熟悉又有點陌生,本文主要是通過思考面試中經(jīng)常被問到的幾個Java反射機制的問題,再通過理論知識結(jié)合代碼實例及應(yīng)用場景進行講解,加深自己對Java反射機制的認知和理解,也希望能幫助到有需要的小伙伴~

??一、Java反射機制是什么?
??1.1 反射原理
(1)Java反射機制(Java Reflection)是Java語言中一種動態(tài)(運行時)訪問、檢測 & 修改它本身的能力,主要作用是動態(tài)(運行時)獲取類的完整結(jié)構(gòu)信息 & 調(diào)用對象的方法~
更簡單點的說就是Java程序在運行時(動態(tài))通過創(chuàng)建一個類的反射對象,再對類進行相關(guān)操作,比如:
獲取該對象的成員變量 & 賦值調(diào)用該對象的方法(含構(gòu)造方法,有參/無參)判斷該對象所屬的類
PS:不過說實話,直接看比較官方的定義還是有點難理解,再來更加通俗點的說吧~
(2)一般情況下,我們使用某個類,都會知道這個類,以及要用它來做什么,可以直接通過new實例化創(chuàng)建對象,然后使用這個對象對類進行操作,這個就屬于正射~
(3)而反射則是一開始并不知道要初始化的是什么類,無法使用new來實例化創(chuàng)建對象,主要是通過JDK提供的反射API來實現(xiàn),在運行時才知道要操作的是什么類,并且可以獲取到類的完整構(gòu)造以及調(diào)用對應(yīng)的方法,這就是反射~
??1.2 反射例子
代碼如下:
package com.justin.java.lang;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* @program: Jdk1.8 Test
* @description: 正射、反射簡單調(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)用過程
Student student = new Student();
student.setId(1);
System.out.println("正射調(diào)用過程Student id:" + student.getId());
//二、反射調(diào)用過程
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)用過程Student id:" + getIdMethod.invoke(studentObj));
}
}
輸出結(jié)果:
正射調(diào)用過程Student id:1
反射調(diào)用過程Student id:2
上述例子反射的調(diào)用過程,可以看到獲取一個類的反射對象,主要過程為:
- 獲取類的Class實例對象
- 根據(jù)Class實例對象獲取Constructor對象
- 再根據(jù)Constructor對象的newInstance方法獲取到類的反射對象
獲取到類的反射對象后,就可以對類進行操作了~ 例如,上述示例中對類的方法進行調(diào)用過程為:
- 根據(jù)Class實例對象獲取到類的Method對象
- 再根據(jù)Method對象的invoke方法調(diào)用到具體類的方法
前面一點也提到了獲取到類的Class實例對象,上面示例反向調(diào)用過程中我們是通過Class.forName("類的全局定名")這種方式來獲取到類的Class實例對象,除了這種,常用的還有其他兩種,往下講解~
??二、Java反射機制中獲取Class的三種方式及區(qū)別?

??2.1 Class的幾種獲取方式
(1)獲取類的java.lang.Class實例對象,常見的三種方式分別為:
通過MyClass.class獲取,這里的MyClass指具體類~~通過Class.forName("類的全局定名")獲取,全局定名為包名+類名通過new MyClass().getClass()獲取,這里的MyClass指具體類~
(2)通過MyClass.class獲取,JVM會使用ClassLoader類加載器將類加載到內(nèi)存中,但并不會做任何類的初始化工作,返回java.lang.Class對象
(3)通過Class.forName("類的全局定名")獲取,同樣,類會被JVM加載到內(nèi)存中,并且會進行類的靜態(tài)初始化工作,返回java.lang.Class對象
(4)通過new MyClass().getClass()獲取,這種方式使用了new進行實例化操作,因此靜態(tài)初始化和非靜態(tài)初始化工作都會進行,getClass方法屬于頂級Object類中的方法,任何子類對象都可以調(diào)用,哪個子類調(diào)用,就返回那個子類的java.lang.Class對象
PS: 這3種方式,最終在JVM堆區(qū)對應(yīng)類的
java.lang.Class對象都屬于同一個,也就是內(nèi)存地址相同,進行==雙等號比較結(jié)果為true,原因是JVM類加載過程中使用的是同一個ClassLoader類加載器加載某個類,不論加載多少次,生成到堆區(qū)的java.lang.Class對象始終只有一個,除非自定義類加載器,破壞JVM的雙親委派機制,使得同一個類被不同類加載器加載,JVM才會把它當做兩個不同的java.lang.Class對象
??2.2 代碼演示幾種方式的區(qū)別
創(chuàng)建一個實體類,分別在實體類中創(chuàng)建類的靜態(tài)代碼塊、動態(tài)代碼塊、有參構(gòu)造方法、無參構(gòu)造方法,方便測試幾種方式的區(qū)別及內(nèi)存地址是否相同~
(1)實體類:
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("動態(tài)代碼塊~");
}
public MyClass() {
System.out.println("無參構(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)單元測試類:
通過@Test注解對三種方式分別進行單元測試,再對這三種方式的組合進行單元測試~
package com.justin.java.lang;
import org.junit.Test;
/**
* @program: Jdk1.8Test
* @description: Java反射機制中獲取類的Class實例對象的常見三種方式及區(qū)別對比
* @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));
}
}
逐個執(zhí)行單元,得出測試結(jié)果為:
* test1()方法
一、MyClass.class方式=========* test2()方法
二、Class.forName方式=========
靜態(tài)代碼塊:staticStr=Hi,staticInt=2021* test3()方法
三、new MyClass().getClass方式=========
靜態(tài)代碼塊:staticStr=Hi,staticInt=2021
動態(tài)代碼塊~
無參構(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
動態(tài)代碼塊~
無參構(gòu)造方法~* test23()方法
二、Class.forName方式=========
靜態(tài)代碼塊:staticStr=Hi,staticInt=2021
三、new MyClass().getClass方式=========
動態(tài)代碼塊~
無參構(gòu)造方法~* test()方法
四、三種方式內(nèi)存地址比較=========
靜態(tài)代碼塊:staticStr=Hi,staticInt=2021
動態(tài)代碼塊~
無參構(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
通過test1、test2、test3的測試結(jié)果驗證了2.1 三種方式及區(qū)別中黃色標記部分的區(qū)別說明,即:
MyClass.class不會做任何類的初始化工作Class.forName會進行類的靜態(tài)初始化工作new MyClass().getClass靜態(tài)初始化和非靜態(tài)初始化工作都會進行使用這三種方式任意一種最終在JVM加載到內(nèi)存中都會是內(nèi)存地址相同的
而test23組合得到的測試結(jié)果,說明靜態(tài)代碼塊只會被加載一次~
講了這么多,除了知道基本原理和基本使用之外,更重要的還是要知道它的一些比較實際的應(yīng)用場景,往下介紹~
??三、Java反射機制的應(yīng)用場景有哪些?

??3.1 應(yīng)用場景
- 工廠模式中的簡單工廠模式優(yōu)化
- 代理模式中的動態(tài)代理方式實現(xiàn)
- Java JDBC數(shù)據(jù)庫操作
??3.2 簡單工廠模式優(yōu)化
??3.2.1 什么是簡單工廠模式?
Java中主要有23種設(shè)計模式,其中工廠模式就是其中一種,而簡單工廠模式,顧名思義,也是屬于工廠模式中的一種,只不過比較簡單。簡單工廠模式也可以叫做靜態(tài)方法模式(因為工廠類一般都是在內(nèi)部定義了一個靜態(tài)方法)。
從現(xiàn)實生活角度來理解的話,工廠是專門負責生產(chǎn)產(chǎn)品的,同樣在設(shè)計模式中,簡單工廠模式我們可以理解為專門負責生產(chǎn)對象的一個類,稱為“工廠類”。
??3.2.2 簡單工廠模式有什么用?
簡單工廠模式通過創(chuàng)建一個對應(yīng)的工廠類,將類實例化的操作與使用對象的操作進行分開,讓使用者不用知道具體參數(shù)就可以實例化出所需要的具體產(chǎn)品類,從而避免了在客戶端代碼中顯式指定,實現(xiàn)了解耦。即使用者可直接消費產(chǎn)品而不需要知道其生產(chǎn)的細節(jié)~
??3.2.3 如何實現(xiàn)簡單工程模式?
實現(xiàn)簡單工程模式的核心是創(chuàng)建一個工廠類,并且在內(nèi)部定義了一個靜態(tài)方法,傳入不同的參數(shù)標識通過switch進行分組,通過new實例化創(chuàng)建不同的子類對象返回~
實現(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)建簡單工廠類
public class SimpleFactory {
/**
* 實現(xiàn)簡單工廠模式
* @param pName 產(chǎn)品標識
* @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)用簡單工廠類
public class SimpleFactoryTest {
public static void main(String[] args) {
try {
SimpleFactory.createProduct("A").show();
} catch (NullPointerException e) {
System.out.println("沒有A這款產(chǎn)品,無法生產(chǎn)~");
}
try {
SimpleFactory.createProduct("B").show();
} catch (NullPointerException e) {
System.out.println("沒有B這款產(chǎn)品,無法生產(chǎn)~");
}
try {
SimpleFactory.createProduct("C").show();
} catch (NullPointerException e) {
System.out.println("沒有C這款產(chǎn)品,無法生產(chǎn)~");
}
try {
SimpleFactory.createProduct("D").show();
} catch (NullPointerException e) {
System.out.println("沒有D這款產(chǎn)品,無法生產(chǎn)~");
}
}
}
??3.2.4 簡單工廠模式優(yōu)化
(1)簡單工廠模式弊端
- 操作成本高:每增加一個接口的子類,必須修改工廠類的邏輯
- 系統(tǒng)復(fù)雜性提高:每增加一個接口的子類,都必須向工廠類添加邏輯
這兩點弊端從前面的例子SimpleFactory工廠類的實現(xiàn),可以看出簡單工廠模式中對工廠類SimpleFactory的維護成本有點大,因為實際中可能會很頻繁的去更新具體產(chǎn)品類,每一次變更都需要去修改工廠類,此時就可以利用Java反射機制對簡單工廠模式進行優(yōu)化~
(2)簡單工廠模式的優(yōu)化思路
采用Java反射機制,通過傳入子類全局定名(包名+類名) 動態(tài)的創(chuàng)建不同的子類對象實例,從而使得在不增加產(chǎn)品接口子類和修改工廠類的邏輯的情況下還能實現(xiàn)了工廠類對子類實例對象的統(tǒng)一創(chuàng)建~
(3)簡單工廠模式的優(yōu)化步驟
步驟1:創(chuàng)建工廠類
采用Java反射機制對工廠類進行優(yōu)化,主要是將className即子類全局定名(包名+類名)作為入?yún)ⅲㄟ^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:調(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("沒有A這款產(chǎn)品,無法生產(chǎn)~");
}
try {
Product productB = Factory.getInstance("com.justin.java.lang.ProductB");
productB.show();
} catch (NullPointerException e) {
System.out.println("沒有B這款產(chǎn)品,無法生產(chǎn)~");
}
try {
Product productC = Factory.getInstance("com.justin.java.lang.ProductC");
productC.show();
} catch (NullPointerException e) {
System.out.println("沒有C這款產(chǎn)品,無法生產(chǎn)~");
}
try {
Product productD = Factory.getInstance("com.justin.java.lang.ProductD");
productD.show();
} catch (Exception e) {
System.out.println("沒有D這款產(chǎn)品,無法生產(chǎn)~");
}
}
}
優(yōu)化結(jié)果:
使用Java反射機制優(yōu)化簡單工廠模式后,可以看到,不論具體產(chǎn)品類更新多頻繁,都不需要再修改工廠類,從而解決了普通簡單工廠模式操作成本高和系統(tǒng)復(fù)雜性高的問題~
??3.2.5 簡單工廠模式再次優(yōu)化
(1)再次優(yōu)化背景
簡單工廠模式的工廠類采用Java反射機制進行優(yōu)化后,此時的仍然存在這樣一個問題,子類的全局定名(包名+類名)是寫死的,但是實際上開發(fā)者在寫代碼時是很難提前預(yù)知所有的子類的全局定名(包名+類名)的,因此需要進行二次優(yōu)化~
(2)再次優(yōu)化實現(xiàn)思路
通過配置文件方式,統(tǒng)一定義類名對應(yīng)全局定名(包名+類名),將配置文件存放到資源目錄下,程序運行時通過ClassLoader類加載器動態(tài)獲取到配置文件中定義的子類的全局定名~
(3)再次優(yōu)化實現(xiàn)步驟
再次優(yōu)化步驟1:相關(guān)優(yōu)化與第一次優(yōu)化保持不變~
再次優(yōu)化步驟2:配置類名對應(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資源目錄下,若資源目錄不存在則需要手動創(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("沒有A這款產(chǎn)品,無法生產(chǎn)~");
}
try {
className = prop.getProperty("ProductB");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("沒有B這款產(chǎn)品,無法生產(chǎn)~");
}
try {
className = prop.getProperty("ProductC");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("沒有C這款產(chǎn)品,無法生產(chǎn)~");
}
}
}
運行結(jié)果:
生產(chǎn)了產(chǎn)品A
生產(chǎn)了產(chǎn)品B
生產(chǎn)了產(chǎn)品C
??3.3 代理模式中的動態(tài)代理實現(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("沒有A這款產(chǎn)品,無法生產(chǎn)~");
}
try {
className = prop.getProperty("ProductB");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("沒有B這款產(chǎn)品,無法生產(chǎn)~");
}
try {
className = prop.getProperty("ProductC");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("沒有C這款產(chǎn)品,無法生產(chǎn)~");
}
}
}
??3.3.1 什么是代理模式?
代理(Proxy)模式是一種設(shè)計模式,通過代理對象來訪問目標對象,還可以在不修改目標對象的情況下,對代理對象進行拓展,增強目標對象的功能~
什么?還是不太理解?
更通俗一點的說代理模式,就是想做某件事(買火車票),自己能買(直接去火車站買),卻委托別人去買(沒空還是代理點買吧),還可以讓別人幫自己做其他事(訂好酒店)~

代理模式又分為靜態(tài)代理、動態(tài)代理,往下介紹~
??3.3.2 什么是靜態(tài)代理?
(1)靜態(tài)代理屬于代理模式的一種代理方式,需要代理對象和目標對象實現(xiàn)相同的接口
(2)靜態(tài)代理的代理類是由程序員編寫源碼,編譯后即可獲取到代理類的class字節(jié)碼文件,也就是在程序運行前就已經(jīng)得到實際的代理類class字節(jié)碼文件了
??3.3.2 什么是動態(tài)代理?
動態(tài)代理
(1)動態(tài)代理也屬于代理模式的一種代理方式,不過只需要目標對象實現(xiàn)接口,代理對象不需要實現(xiàn)接口~
(2)動態(tài)代理的代理類編譯后是沒有class字節(jié)碼文件的,而是在運行時利用Java反射機制動態(tài)的生成代理類的class字節(jié)碼文件~
動態(tài)代理最常用的是JDK原生動態(tài)代理和cglib動態(tài)代理,往下介紹~
JDK 原生動態(tài)代理
JDK 原生動態(tài)代理,主要利用了JDK API的java.lang.reflect.Proxy和java.lang.relfect.InnvocationHandler 這兩個類來實現(xiàn)~
通過java.lang.reflect.Proxy代理類的newProxyInstance方法,傳遞3個參數(shù),分別是:
目標對象的加載器 通過MyClass.getClass().getClassLoader方式獲取
目標對象的實現(xiàn)接口類型 通過Object.getClass().getInterfaces()方式獲取
InnvocationHandler事件處理器 通過new取
例子:
用戶接口類IUserDao
public interface IUserDao {
//添加數(shù)據(jù)
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("添加數(shù)據(jù)");
}
}
動態(tài)代理類UserProxy
/**
* @program: Jdk1.8Test
* @description: 動態(tài)代理類
* @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();
//目標對象的實現(xiàn)接口類型
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("添加數(shù)據(jù)前:手動開啟事務(wù)");
// 執(zhí)行目標對象方法
Object value = method.invoke(target, args);
System.out.println("添加數(shù)據(jù)后:手動提交事務(wù)");
return null;
}
};
//傳入3個參數(shù),創(chuàng)建代理類的實例對象,并返回
return Proxy.newProxyInstance(loader, interfaces,h);
}
}
動態(tài)代理單元測試類
/**
* @program: 動態(tài)代理單元測試類
* @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());
//執(zhí)行代理方法
proxy.insert();
}
}
單元測試執(zhí)行結(jié)果
目標對象信息:class com.justin.java.reflect.UserDao
代理對象信息:class com.sun.proxy.$Proxy2
添加數(shù)據(jù)前:手動開啟事務(wù)
添加數(shù)據(jù)
添加數(shù)據(jù)后:手動提交事務(wù)
cglib動態(tài)代理
cglib (Code Generation Library )是一個第三方代碼生成類庫,運行時在內(nèi)存中動態(tài)生成一個子類對象從而實現(xiàn)對目標對象功能的擴展。
Spring AOP結(jié)合了cglib動態(tài)代理和JDK原生動態(tài)代理來實現(xiàn),這里不過多介紹,有興趣小伙伴可以查閱資料學(xué)習(xí)下~
??3.3.3 動態(tài)代理中如何利用Java反射機制?
JDK原生動態(tài)代理中,獲取代理示例對象過程中,獲取目標對象的類加載器,通過
target.getClass().getClassLoader(獲取到目標對象的類加載器,target.getClass()方式獲取目標對象的Class實例對象使用的就是Java反射機制來實現(xiàn)的~
??3.4 Java JDBC數(shù)據(jù)庫操作實現(xiàn)
??3.4.1 利用反射加載JDBC驅(qū)動
相信很多小伙伴都知道Java JDBC連接數(shù)據(jù)庫主要分為七大步驟,其中第一步加載JDBC驅(qū)動,利用Java反射機制通過傳入不同的驅(qū)動名稱,加載不同數(shù)據(jù)庫的驅(qū)動~
Class.forName("com.mysql.jdbc.Driver"); //加載MySQL驅(qū)動
Class.forName("oracle.jdbc.driver.OracleDriver"); //加載Oracle驅(qū)動
鏈接:Mysql驅(qū)動架包mysql-connector-java-5.1.30.jar
鏈接:Oracle驅(qū)動架包ojdbc14-10.2.0.4.0.jar
??3.4.2 Java JDBC連接示例
創(chuàng)建測試庫表及數(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ū)動
Class.forName("com.mysql.jdbc.Driver");
//2.獲取數(shù)據(jù)庫的連接(Connection)對象
Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost/test", //mysql連接url,test表示你要連接的數(shù)據(jù)庫名
"root", //數(shù)據(jù)庫用戶名
"abc@123456"); //密碼
//3.獲取數(shù)據(jù)庫的操作(PrepareStatement)對象
PreparedStatement prepareStatement = connection.prepareStatement("select * from TEST.user where id = ?");
//4.設(shè)置傳入?yún)?shù)
prepareStatement.setInt(1, 1);
//5.上傳sql語句到服務(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對象、PrepareStatement對象、ResultSet對象。
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ū)動
Class.forName("oracle.jdbc.driver.OracleDriver");
//2.獲取數(shù)據(jù)庫的連接(Connection)對象
Connection connection = DriverManager.getConnection(
"jdbc:oracle:thin:@127.0.0.1:1521:orcl", //oracle連接url
"root", //數(shù)據(jù)庫用戶名
"abc@123456"); //密碼
//3.獲取數(shù)據(jù)庫的操作(PrepareStatement)對象
PreparedStatement prepareStatement = connection.prepareStatement("select * from TEST.user where id = ?");
//4.設(shè)置傳入?yún)?shù)
prepareStatement.setInt(1, 1);
//5.上傳sql語句到服務(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對象、PrepareStatement對象、ResultSet對象。
connection.close();
prepareStatement.close();
result.close();
}
}
PS:上面通過Java JDBC連接數(shù)據(jù)庫并進行操作,這里的連接是單一連接,直接通過DriverManager.getConnection這種Java原生的數(shù)據(jù)庫連接方式建立的連接,現(xiàn)在實際的Java Spring項目當中,都是通過配置mybatis的數(shù)據(jù)庫連接池來實現(xiàn)的,不過原理都是一樣的,加載驅(qū)動也是利用了Java反射機制指定不同的驅(qū)動名稱,實現(xiàn)不同數(shù)據(jù)庫驅(qū)動的加載~
數(shù)據(jù)庫連接池配置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}" />
<!-- 獲取連接最大等待時間 -->
<property name="maxWait" value="${app-data-source.maxWait}" />
</bean>
數(shù)據(jù)庫配置信息jdbc.propertis
#數(shù)據(jù)庫連接驅(qū)動 app-data-source.driverClassName=com.mysql.jdbc.Driver #數(shù)據(jù)庫連接url app-data-source.url=jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=UTF-8 #數(shù)據(jù)庫用戶 app-data-source.username=root #數(shù)據(jù)庫用戶密碼(加密) 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 #獲取連接最大等待時間 app-data-source.maxWait=30000
面試總結(jié)
一、Java反射機制是什么?
1、Java反射機制(Java Reflection)是Java語言中一種動態(tài)(運行時)訪問、檢測 & 修改它本身的能力,主要作用是動態(tài)(運行時)獲取類的完整結(jié)構(gòu)信息 & 調(diào)用對象的方法~
更簡單點的說就是Java程序在運行時(動態(tài))通過創(chuàng)建一個類的反射對象,再對類進行相關(guān)操作,比如:
- 獲取該對象的成員變量 & 賦值
- 調(diào)用該對象的方法(含構(gòu)造方法,有參/無參)
- 判斷該對象所屬的類
2、更通俗點的說,我們使用某個類,都會知道這個類,以及要用它來做什么,可以直接通過new實例化創(chuàng)建對象,然后使用這個對象對類進行操作,這個就屬于正射~
3、而反射則是一開始并不知道要初始化的是什么類,無法使用new來實例化創(chuàng)建對象,主要是通過JDK提供的反射API來實現(xiàn),在運行時才知道要操作的是什么類,并且可以獲取到類的完整構(gòu)造以及調(diào)用對應(yīng)的方法,這就是反射~
二、Java反射機制中獲取Class的三種方式及區(qū)別?
1、獲取類的java.lang.Class實例對象,常見的三種方式分別為:
- 通過MyClass.class獲取
- 通過Class.forName("類的全局定名")獲取
- 通過new MyClass().getClass()獲取
2、通過MyClass.class獲取,JVM會使用ClassLoader類加載器將類加載到內(nèi)存中,但并不會做任何類的初始化工作,返回java.lang.Class對象
3、通過Class.forName("類的全局定名")獲取,同樣,類會被JVM加載到內(nèi)存中,并且會進行類的靜態(tài)初始化工作,返回java.lang.Class對象
4、通過new MyClass().getClass()獲取,這種方式使用了new進行實例化操作,因此== 靜態(tài)初始化和非靜態(tài)初始化工作都會進行 == ,getClass方法屬于頂級Object類中的方法,任何子類對象都可以調(diào)用,哪個子類調(diào)用,就返回那個子類的java.lang.Class對象
5、這3種方式,最終在JVM堆區(qū)對應(yīng)類的java.lang.Class對象都屬于同一個,也就是內(nèi)存地址相同,進行==雙等號比較結(jié)果為true,原因是JVM類加載過程中使用的是同一個ClassLoader類加載器加載某個類,不論加載多少次,生成到堆區(qū)的java.lang.Class對象始終只有一個,除非自定義類加載器,破壞JVM的雙親委派機制,使得同一個類被不同類加載器加載,JVM才會把它當做兩個不同的java.lang.Class對象
三、Java反射機制的應(yīng)用場景有哪些?
- 工廠模式中的簡單工廠模式優(yōu)化
- 代理模式中的動態(tài)代理方式實現(xiàn)
- Java JDBC數(shù)據(jù)庫操作
總結(jié)
到此這篇關(guān)于Java反射機制原理、Class獲取方式以及應(yīng)用場景的文章就介紹到這了,更多相關(guān)Java反射原理及Class獲取內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java開發(fā)中synchronized的定義及用法詳解
這篇文章主要介紹了Java開發(fā)中synchronized的定義及用法詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Java靜態(tài)方法不能調(diào)用非靜態(tài)成員的原因分析
在Java中,靜態(tài)方法是屬于類的方法,而不是屬于對象的方法,它可以通過類名直接調(diào)用,無需創(chuàng)建對象實例,非靜態(tài)成員指的是類的實例變量和實例方法,它們需要通過對象實例才能訪問和調(diào)用,本文小編將和大家一起探討Java靜態(tài)方法為什么不能調(diào)用非靜態(tài)成員2023-10-10
SpringMvc3+extjs4實現(xiàn)上傳與下載功能
這篇文章主要為大家詳細介紹了SpringMvc3+extjs4實現(xiàn)上傳與下載功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06
Java編程實現(xiàn)遍歷兩個MAC地址之間所有MAC的方法
這篇文章主要介紹了Java編程實現(xiàn)遍歷兩個MAC地址之間所有MAC的方法,涉及Java針對MAC的遍歷獲取與字符串轉(zhuǎn)換相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11

