精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

學會反射后,我被錄取了!

開發 后端
反射是一個非常重要的知識點,在學習Spring 框架時,Bean的初始化用到了反射,在破壞單例模式時也用到了反射,在獲取標注的注解時也會用到反射······

 [[338210]]

本文系小菠蘿的投稿

反射是一個非常重要的知識點,在學習Spring 框架時,Bean的初始化用到了反射,在破壞單例模式時也用到了反射,在獲取標注的注解時也會用到反射······

當然了,反射在日常開發中,我們沒碰到過多少,至少我沒怎么用過。但面試是造火箭現場,可愛的面試官們又怎會輕易地放過我們呢?反射是開源框架中的一個重要設計理念,在源碼分析中少不了它的身影,所以,今天我會盡量用淺顯易懂的語言,讓你去理解下面這幾點:

(1)反射的思想以及它的作用: 概念篇

(2)反射的基本使用及應用場景: 應用篇

(3)使用反射能給我們編碼時帶來的優勢以及存在的缺陷: 分析篇

反射的思想及作用

有反必有正,就像世間的陰和陽,計算機的0和1一樣。天道有輪回,蒼天...(凈會在這瞎bibi)

在學習反射之前,先來了解正射是什么。我們平常用的最多的 new 方式實例化對象的方式就是一種正射的體現。假如我需要實例化一個HashMap,代碼就會是這樣子。

  1. Map<IntegerInteger> map = new HashMap<>(); 
  2. map.put(1, 1); 

某一天發現,該段程序不適合用 HashMap 存儲鍵值對,更傾向于用LinkedHashMap存儲。重新編寫代碼后變成下面這個樣子。

  1. Map<IntegerInteger> map = new LinkedHashMap<>(); 
  2. map.put(1, 1); 

假如又有一天,發現數據還是適合用 HashMap來存儲,難道又要重新修改源碼嗎?

“發現問題了嗎?我們每次改變一種需求,都要去重新修改源碼,然后對代碼進行編譯,打包,再到 JVM 上重啟項目。這么些步驟下來,效率非常低。

 

對于這種需求頻繁變更但變更不大的場景,頻繁地更改源碼肯定是一種不允許的操作,我們可以使用一個開關,判斷什么時候使用哪一種數據結構。

  1. public Map<IntegerInteger> getMap(String param) { 
  2.     Map<IntegerInteger> map = null
  3.     if (param.equals("HashMap")) { 
  4.         map = new HashMap<>(); 
  5.     } else if (param.equals("LinkedHashMap")) { 
  6.         map = new LinkedHashMap<>(); 
  7.     } else if (param.equals("WeakHashMap")) { 
  8.         map = new WeakHashMap<>(); 
  9.     } 
  10.     return map; 

通過傳入參數param決定使用哪一種數據結構,可以在項目運行時,通過動態傳入參數決定使用哪一個數據結構。

如果某一天還想用TreeMap,還是避免不了修改源碼,重新編譯執行的弊端。這個時候,反射就派上用場了。

在代碼運行之前,我們不確定將來會使用哪一種數據結構,只有在程序運行時才決定使用哪一個數據類,而反射可以在程序運行過程中動態獲取類信息和調用類方法。通過反射構造類實例,代碼會演變成下面這樣。

  1. public Map<IntegerInteger> getMap(String className) { 
  2.     Class clazz = Class.forName(className); 
  3.     Consructor con = clazz.getConstructor(); 
  4.     return (Map<IntegerInteger>) con.newInstance(); 

無論使用什么 Map,只要實現了Map接口,就可以使用全類名路徑傳入到方法中,獲得對應的 Map 實例。例如java.util.HashMap / java.util.LinkedHashMap····如果要創建其它類例如WeakHashMap,我也不需要修改上面這段源碼。

我們來回顧一下如何從 new 一個對象引出使用反射的。

  • 在不使用反射時,構造對象使用 new 方式實現,這種方式在編譯期就可以把對象的類型確定下來。
  • 如果需求發生變更,需要構造另一個對象,則需要修改源碼,非常不優雅,所以我們通過使用開關,在程序運行時判斷需要構造哪一個對象,在運行時可以變更開關來實例化不同的數據結構。
  • 如果還有其它擴展的類有可能被使用,就會創建出非常多的分支,且在編碼時不知道有什么其他的類被使用到,假如日后Map接口下多了一個集合類是xxxHashMap,還得創建分支,此時引出了反射:可以在運行時才確定使用哪一個數據類,在切換類時,無需重新修改源碼、編譯程序。

第一章總結:

  • 反射的思想:在程序運行過程中確定和解析數據類的類型。
  • 反射的作用:對于在編譯期無法確定使用哪個數據類的場景,通過反射可以在程序運行時構造出不同的數據類實例。

反射的基本使用

Java 反射的主要組成部分有4個:

  • Class:任何運行在內存中的所有類都是該 Class 類的實例對象,每個 Class 類對象內部都包含了本來的所有信息。記著一句話,通過反射干任何事,先找 Class 準沒錯!
  • Field:描述一個類的屬性,內部包含了該屬性的所有信息,例如數據類型,屬性名,訪問修飾符······
  • Constructor:描述一個類的構造方法,內部包含了構造方法的所有信息,例如參數類型,參數名字,訪問修飾符······
  • Method:描述一個類的所有方法(包括抽象方法),內部包含了該方法的所有信息,與Constructor類似,不同之處是 Method 擁有返回值類型信息,因為構造方法是沒有返回值的。

我總結了一張腦圖,放在了下面,如果用到了反射,離不開這核心的4個類,只有去了解它們內部提供了哪些信息,有什么作用,運用它們的時候才能易如反掌。

 


 

 

我們在學習反射的基本使用時,我會用一個SmallPineapple類作為模板進行說明,首先我們先來熟悉這個類的基本組成:屬性,構造函數和方法

  1. public class SmallPineapple { 
  2.     public String name
  3.     public int age; 
  4.     private double weight; // 體重只有自己知道 
  5.      
  6.     public SmallPineapple() {} 
  7.      
  8.     public SmallPineapple(String nameint age) { 
  9.         this.name = name
  10.         this.age = age; 
  11.     } 
  12.     public void getInfo() { 
  13.         System.out.print("["name + " 的年齡是:" + age + "]"); 
  14.     } 

反射中的用法有非常非常多,常見的功能有以下這幾個:

  • 在運行時獲取一個類的 Class 對象
  • 在運行時構造一個類的實例化對象
  • 在運行時獲取一個類的所有信息:變量、方法、構造器、注解

獲取類的 Class 對象

在 Java 中,每一個類都會有專屬于自己的 Class 對象,當我們編寫完.java文件后,使用javac編譯后,就會產生一個字節碼文件.class,在字節碼文件中包含類的所有信息,如屬性,構造方法,方法······當字節碼文件被裝載進虛擬機執行時,會在內存中生成 Class 對象,它包含了該類內部的所有信息,在程序運行時可以獲取這些信息。

獲取 Class 對象的方法有3種:

  • 類名.class:這種獲取方式只有在編譯前已經聲明了該類的類型才能獲取到 Class 對象
  1. Class clazz = SmallPineapple.class; 
  • 實例.getClass():通過實例化對象獲取該實例的 Class 對象
  1. SmallPineapple sp = new SmallPineapple(); 
  2. Class clazz = sp.getClass(); 
  • Class.forName(className):通過類的全限定名獲取該類的 Class 對象
  1. Class clazz = Class.forName("com.bean.smallpineapple"); 

拿到 Class對象就可以對它為所欲為了:剝開它的皮(獲取類信息)、指揮它做事(調用它的方法),看透它的一切(獲取屬性),總之它就沒有隱私了。

不過在程序中,每個類的 Class 對象只有一個,也就是說你只有這一個奴隸。我們用上面三種方式測試,通過三種方式打印各個 Class 對象都是相同的。

  1. Class clazz1 = Class.forName("com.bean.SmallPineapple"); 
  2. Class clazz2 = SmallPineapple.class; 
  3. SmallPineapple instance = new SmallPineapple(); 
  4. Class clazz3 = instance.getClass(); 
  5. System.out.println("Class.forName() == SmallPineapple.class:" + (clazz1 == clazz2)); 
  6. System.out.println("Class.forName() == instance.getClass():" + (clazz1 == clazz3)); 
  7. System.out.println("instance.getClass() == SmallPineapple.class:" + (clazz2 == clazz3)); 

 

“內存中只有一個 Class 對象的原因要牽扯到 JVM 類加載機制的雙親委派模型,它保證了程序運行時,加載類時每個類在內存中僅會產生一個Class對象。在這里我不打算詳細展開說明,可以簡單地理解為 JVM 幫我們保證了一個類在內存中至多存在一個 Class 對象。

構造類的實例化對象

通過反射構造一個類的實例方式有2種:

  • Class 對象調用newInstance()方法
  1. Class clazz = Class.forName("com.bean.SmallPineapple"); 
  2. SmallPineapple smallPineapple = (SmallPineapple) clazz.newInstance(); 
  3. smallPineapple.getInfo(); 
  4. // [null 的年齡是:0] 

即使 SmallPineapple 已經顯式定義了構造方法,通過 newInstance() 創建的實例中,所有屬性值都是對應類型的初始值,因為 newInstance() 構造實例會調用默認無參構造器。

  • Constructor 構造器調用newInstance()方法
  1. Class clazz = Class.forName("com.bean.SmallPineapple"); 
  2. Constructor constructor = clazz.getConstructor(String.class, int.class); 
  3. constructor.setAccessible(true); 
  4. SmallPineapple smallPineapple2 = (SmallPineapple) constructor.newInstance("小菠蘿", 21); 
  5. smallPineapple2.getInfo(); 
  6. // [小菠蘿 的年齡是:21] 

通過 getConstructor(Object... paramTypes) 方法指定獲取指定參數類型的 Constructor, Constructor 調用 newInstance(Object... paramValues) 時傳入構造方法參數的值,同樣可以構造一個實例,且內部屬性已經被賦值。

通過Class對象調用 newInstance() 會走默認無參構造方法,如果想通過顯式構造方法構造實例,需要提前從Class中調用getConstructor()方法獲取對應的構造器,通過構造器去實例化對象。

“這些 API 是在開發當中最常遇到的,當然還有非常多重載的方法,本文由于篇幅原因,且如果每個方法都一一講解,我們也記不住,所以用到的時候去類里面查找就已經足夠了。

獲取一個類的所有信息

Class 對象中包含了該類的所有信息,在編譯期我們能看到的信息就是該類的變量、方法、構造器,在運行時最常被獲取的也是這些信息。

 

獲取類中的變量(Field)

  • Field[] getFields():獲取類中所有被public修飾的所有變量
  • Field getField(String name):根據變量名獲取類中的一個變量,該變量必須被public修飾
  • Field[] getDeclaredFields():獲取類中所有的變量,但無法獲取繼承下來的變量
  • Field getDeclaredField(String name):根據姓名獲取類中的某個變量,無法獲取繼承下來的變量

獲取類中的方法(Method)

  • Method[] getMethods():獲取類中被public修飾的所有方法
  • Method getMethod(String name, Class... paramTypes):根據名字和參數類型獲取對應方法,該方法必須被public修飾
  • Method[] getDeclaredMethods():獲取所有方法,但無法獲取繼承下來的方法
  • Method getDeclaredMethod(String name, Class... paramTypes):根據名字和參數類型獲取對應方法,無法獲取繼承下來的方法

獲取類的構造器(Constructor)

  • Constuctor[] getConstructors():獲取類中所有被public修飾的構造器
  • Constructor getConstructor(Class... paramTypes):根據參數類型獲取類中某個構造器,該構造器必須被public修飾
  • Constructor[] getDeclaredConstructors():獲取類中所有構造器
  • Constructor getDeclaredConstructor(class... paramTypes):根據參數類型獲取對應的構造器

每種功能內部以 Declared 細分為2類:

“有Declared修飾的方法:可以獲取該類內部包含的所有變量、方法和構造器,但是無法獲取繼承下來的信息無Declared修飾的方法:可以獲取該類中public修飾的變量、方法和構造器,可獲取繼承下來的信息

如果想獲取類中**所有的(包括繼承)**變量、方法和構造器,則需要同時調用getXXXs()和getDeclaredXXXs()兩個方法,用Set集合存儲它們獲得的變量、構造器和方法,以防兩個方法獲取到相同的東西。

例如:要獲取SmallPineapple獲取類中所有的變量,代碼應該是下面這樣寫。

  1. Class clazz = Class.forName("com.bean.SmallPineapple"); 
  2. // 獲取 public 屬性,包括繼承 
  3. Field[] fields1 = clazz.getFields(); 
  4. // 獲取所有屬性,不包括繼承 
  5. Field[] fields2 = clazz.getDeclaredFields(); 
  6. // 將所有屬性匯總到 set 
  7. Set<Field> allFields = new HashSet<>(); 
  8. allFields.addAll(Arrays.asList(fields1)); 
  9. allFields.addAll(Arrays.asList(fields2)); 

“不知道你有沒有發現一件有趣的事情,如果父類的屬性用protected修飾,利用反射是無法獲取到的。protected 修飾符的作用范圍:只允許同一個包下或者子類訪問,可以繼承到子類。getFields() 只能獲取到本類的public屬性的變量值;getDeclaredFields() 只能獲取到本類的所有屬性,不包括繼承的;無論如何都獲取不到父類的 protected 屬性修飾的變量,但是它的的確確存在于子類中。

獲取注解

獲取注解單獨擰了出來,因為它并不是專屬于 Class 對象的一種信息,每個變量,方法和構造器都可以被注解修飾,所以在反射中,Field,Constructor 和 Method 類對象都可以調用下面這些方法獲取標注在它們之上的注解。

  • Annotation[] getAnnotations():獲取該對象上的所有注解
  • Annotation getAnnotation(Class annotaionClass):傳入注解類型,獲取該對象上的特定一個注解
  • Annotation[] getDeclaredAnnotations():獲取該對象上的顯式標注的所有注解,無法獲取繼承下來的注解
  • Annotation getDeclaredAnnotation(Class annotationClass):根據注解類型,獲取該對象上的特定一個注解,無法獲取繼承下來的注解

只有注解的@Retension標注為RUNTIME時,才能夠通過反射獲取到該注解,@Retension 有3種保存策略:

  • SOURCE:只在**源文件(.java)**中保存,即該注解只會保留在源文件中,編譯時編譯器會忽略該注解,例如 @Override 注解
  • CLASS:保存在字節碼文件(.class)中,注解會隨著編譯跟隨字節碼文件中,但是運行時不會對該注解進行解析
  • RUNTIME:一直保存到運行時,用得最多的一種保存策略,在運行時可以獲取到該注解的所有信息

像下面這個例子,SmallPineapple 類繼承了抽象類Pineapple,getInfo()方法上標識有 @Override 注解,且在子類中標注了@Transient注解,在運行時獲取子類重寫方法上的所有注解,只能獲取到@Transient的信息。

  1. public abstract class Pineapple { 
  2.     public abstract void getInfo(); 
  3. public class SmallPineapple extends Pineapple { 
  4.     @Transient 
  5.     @Override 
  6.     public void getInfo() { 
  7.         System.out.print("小菠蘿的身高和年齡是:" + height + "cm ; " + age + "歲"); 
  8.     } 

啟動類Bootstrap獲取 SmallPineapple 類中的 getInfo() 方法上的注解信息:

  1. public class Bootstrap { 
  2.     /** 
  3.      * 根據運行時傳入的全類名路徑判斷具體的類對象 
  4.      * @param path 類的全類名路徑 
  5.      */ 
  6.     public static void execute(String path) throws Exception { 
  7.         Class obj = Class.forName(path); 
  8.         Method method = obj.getMethod("getInfo"); 
  9.         Annotation[] annotations = method.getAnnotations(); 
  10.         for (Annotation annotation : annotations) { 
  11.             System.out.println(annotation.toString()); 
  12.         } 
  13.     } 
  14.     public static void main(String[] args) throws Exception { 
  15.         execute("com.pineapple.SmallPineapple"); 
  16.     } 
  17. // @java.beans.Transient(value=true

通過反射調用方法

通過反射獲取到某個 Method 類對象后,可以通過調用invoke方法執行。

  • invoke(Oject obj, Object... args):參數``1指定調用該方法的**對象**,參數2`是方法的參數列表值。

如果調用的方法是靜態方法,參數1只需要傳入null,因為靜態方法不與某個對象有關,只與某個類有關。

可以像下面這種做法,通過反射實例化一個對象,然后獲取Method方法對象,調用invoke()指定SmallPineapple的getInfo()方法。

  1. Class clazz = Class.forName("com.bean.SmallPineapple"); 
  2. Constructor constructor = clazz.getConstructor(String.class, int.class); 
  3. constructor.setAccessible(true); 
  4. SmallPineapple sp = (SmallPineapple) constructor.newInstance("小菠蘿", 21); 
  5. Method method = clazz.getMethod("getInfo"); 
  6. if (method != null) { 
  7.     method.invoke(sp, null); 
  8. // [小菠蘿的年齡是:21] 

反射的應用場景

反射常見的應用場景這里介紹3個:

  • Spring 實例化對象:當程序啟動時,Spring 會讀取配置文件applicationContext.xml并解析出里面所有的標簽實例化到IOC容器中。
  • 反射 + 工廠模式:通過反射消除工廠中的多個分支,如果需要生產新的類,無需關注工廠類,工廠類可以應對各種新增的類,反射可以使得程序更加健壯。
  • JDBC連接數據庫:使用JDBC連接數據庫時,指定連接數據庫的驅動類時用到反射加載驅動類

Spring 的 IOC 容器

在 Spring 中,經常會編寫一個上下文配置文件applicationContext.xml,里面就是關于bean的配置,程序啟動時會讀取該 xml 文件,解析出所有的 標簽,并實例化對象放入IOC容器中。

  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <beans xmlns="http://www.springframework.org/schema/beans" 
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4.        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
  5.     <bean id="smallpineapple" class="com.bean.SmallPineapple"
  6.         <constructor-arg type="java.lang.String" value="小菠蘿"/> 
  7.         <constructor-arg type="int" value="21"/> 
  8.     </bean> 
  9. </beans> 

在定義好上面的文件后,通過ClassPathXmlApplicationContext加載該配置文件,程序啟動時,Spring 會將該配置文件中的所有bean都實例化,放入 IOC 容器中,IOC 容器本質上就是一個工廠,通過該工廠傳入 標簽的id屬性獲取到對應的實例。

  1. public class Main { 
  2.     public static void main(String[] args) { 
  3.         ApplicationContext ac = 
  4.                 new ClassPathXmlApplicationContext("applicationContext.xml"); 
  5.         SmallPineapple smallPineapple = (SmallPineapple) ac.getBean("smallpineapple"); 
  6.         smallPineapple.getInfo(); // [小菠蘿的年齡是:21] 
  7.     } 

Spring 在實例化對象的過程經過簡化之后,可以理解為反射實例化對象的步驟:

  • 獲取Class對象的構造器
  • 通過構造器調用 newInstance() 實例化對象

當然 Spring 在實例化對象時,做了非常多額外的操作,才能夠讓現在的開發足夠的便捷且穩定。

“在之后的文章中會專門寫一篇文章講解如何利用反射實現一個簡易版的IOC容器,IOC容器原理很簡單,只要掌握了反射的思想,了解反射的常用 API 就可以實現,我可以提供一個簡單的思路:利用 HashMap 存儲所有實例,key 代表 標簽的 id,value 存儲對應的實例,這對應了 Spring IOC容器管理的對象默認是單例的。

反射 + 抽象工廠模式

傳統的工廠模式,如果需要生產新的子類,需要修改工廠類,在工廠類中增加新的分支;

  1. public class MapFactory { 
  2.     public Map<Object, object> produceMap(String name) { 
  3.         if ("HashMap".equals(name)) { 
  4.             return new HashMap<>(); 
  5.         } else if ("TreeMap".equals(name)) { 
  6.             return new TreeMap<>(); 
  7.         } // ··· 
  8.     } 

利用反射和工廠模式相結合,在產生新的子類時,工廠類不用修改任何東西,可以專注于子類的實現,當子類確定下來時,工廠也就可以生產該子類了。

反射 + 抽象工廠的核心思想是:

  • 在運行時通過參數傳入不同子類的全限定名獲取到不同的 Class 對象,調用 newInstance() 方法返回不同的子類。細心的讀者會發現提到了子類這個概念,所以反射 + 抽象工廠模式,一般會用于有繼承或者接口實現關系。

例如,在運行時才確定使用哪一種 Map 結構,我們可以利用反射傳入某個具體 Map 的全限定名,實例化一個特定的子類。

  1. public class MapFactory { 
  2.     /** 
  3.      * @param className 類的全限定名 
  4.      */ 
  5.     public Map<Object, Object> produceMap(String className) { 
  6.         Class clazz = Class.forName(className); 
  7.         Map<Object, Object> map = clazz.newInstance(); 
  8.         return map; 
  9.     } 

className 可以指定為 java.util.HashMap,或者 java.util.TreeMap 等等,根據業務場景來定。

JDBC 加載數據庫驅動類

在導入第三方庫時,JVM不會主動去加載外部導入的類,而是等到真正使用時,才去加載需要的類,正是如此,我們可以在獲取數據庫連接時傳入驅動類的全限定名,交給 JVM 加載該類。

  1. public class DBConnectionUtil { 
  2.     /** 指定數據庫的驅動類 */ 
  3.     private static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver"
  4.      
  5.     public static Connection getConnection() { 
  6.         Connection conn = null
  7.         // 加載驅動類 
  8.         Class.forName(DRIVER_CLASS_NAME); 
  9.         // 獲取數據庫連接對象 
  10.         conn = DriverManager.getConnection("jdbc:mysql://···""root""root"); 
  11.         return conn; 
  12.     } 

在我們開發 SpringBoot 項目時,會經常遇到這個類,但是可能習慣成自然了,就沒多大在乎,我在這里給你們看看常見的application.yml中的數據庫配置,我想你應該會恍然大悟吧。

 

這里的 driver-class-name,和我們一開始加載的類是不是覺得很相似,這是因為MySQL版本不同引起的驅動類不同,這體現使用反射的好處:不需要修改源碼,僅加載配置文件就可以完成驅動類的替換。

“在之后的文章中會專門寫一篇文章詳細地介紹反射的應用場景,實現簡單的IOC容器以及通過反射實現工廠模式的好處。在這里,你只需要掌握反射的基本用法和它的思想,了解它的主要使用場景。

反射的優勢及缺陷

反射的優點:

  • 增加程序的靈活性:面對需求變更時,可以靈活地實例化不同對象

但是,有得必有失,一項技術不可能只有優點沒有缺點,反射也有兩個比較隱晦的缺點:

  • 破壞類的封裝性:可以強制訪問 private 修飾的信息
  • 性能損耗:反射相比直接實例化對象、調用方法、訪問變量,中間需要非常多的檢查步驟和解析步驟,JVM無法對它們優化。

增加程序的靈活性

這里不再用 SmallPineapple 舉例了,我們來看一個更加貼近開發的例子:

利用反射連接數據庫,涉及到數據庫的數據源。在 SpringBoot 中一切約定大于配置,想要定制配置時,使用application.properties配置文件指定數據源

角色1 - Java的設計者:我們設計好DataSource接口,你們其它數據庫廠商想要開發者用你們的數據源監控數據庫,就得實現我的這個接口!

角色2 - 數據庫廠商:

  • MySQL 數據庫廠商:我們提供了 com.mysql.cj.jdbc.MysqlDataSource 數據源,開發者可以使用它連接 MySQL。
  • 阿里巴巴廠商:我們提供了 com.alibaba.druid.pool.DruidDataSource 數據源,我這個數據源更牛逼,具有頁面監控,慢SQL日志記錄等功能,開發者快來用它監控 MySQL吧!
  • SQLServer 廠商:我們提供了 com.microsoft.sqlserver.jdbc.SQLServerDataSource數據源,如果你想實用SQL Server 作為數據庫,那就使用我們的這個數據源連接吧

角色3 - 開發者:我們可以用配置文件指定使用DruidDataSource數據源

  1. spring.datasource.type=com.alibaba.druid.pool.DruidDataSource 

需求變更:某一天,老板來跟我們說,Druid 數據源不太符合我們現在的項目了,我們使用MysqlDataSource 吧,然后程序猿就會修改配置文件,重新加載配置文件,并重啟項目,完成數據源的切換。

  1. spring.datasource.type=com.mysql.cj.jdbc.MysqlDataSource 

在改變連接數據庫的數據源時,只需要改變配置文件即可,無需改變任何代碼,原因是:

  • Spring Boot 底層封裝好了連接數據庫的數據源配置,利用反射,適配各個數據源。

下面來簡略的進行源碼分析。我們用ctrl+左鍵點擊spring.datasource.type進入 DataSourceProperties 類中,發現使用setType() 將全類名轉化為 Class 對象注入到type成員變量當中。在連接并監控數據庫時,就會使用指定的數據源操作。

  1. private Class<? extends DataSource> type; 
  2.  
  3. public void setType(Class<? extends DataSource> type) { 
  4.     this.type = type; 

Class對象指定了泛型上界DataSource,我們去看一下各大數據源的類圖結構。

 

上圖展示了一部分數據源,當然不止這些,但是我們可以看到,無論指定使用哪一種數據源,我們都只需要與配置文件打交道,而無需更改源碼,這就是反射的靈活性!

破壞類的封裝性

很明顯的一個特點,反射可以獲取類中被private修飾的變量、方法和構造器,這違反了面向對象的封裝特性,因為被 private 修飾意味著不想對外暴露,只允許本類訪問,而setAccessable(true)可以無視訪問修飾符的限制,外界可以強制訪問。

還記得單例模式一文嗎?里面講到反射破壞餓漢式和懶漢式單例模式,所以之后用了枚舉避免被反射KO。

回到最初的起點,SmallPineapple 里有一個 weight 屬性被 private 修飾符修飾,目的在于自己的體重并不想給外界知道。

  1. public class SmallPineapple { 
  2.     public String name
  3.     public int age; 
  4.     private double weight; // 體重只有自己知道 
  5.      
  6.     public SmallPineapple(String nameint age, double weight) { 
  7.         this.name = name
  8.         this.age = age; 
  9.         this.weight = weight; 
  10.     } 
  11.      

雖然 weight 屬性理論上只有自己知道,但是如果經過反射,這個類就像在裸奔一樣,在反射面前變得一覽無遺。

  1. SmallPineapple sp = new SmallPineapple("小菠蘿", 21, "54.5"); 
  2. Clazz clazz = Class.forName(sp.getClass()); 
  3. Field weight = clazz.getDeclaredField("weight"); 
  4. weight.setAccessable(true); 
  5. System.out.println("窺覷到小菠蘿的體重是:" + weight.get(sp)); 
  6. // 窺覷到小菠蘿的體重是:54.5 kg 

性能損耗

在直接 new 對象并調用對象方法和訪問屬性時,編譯器會在編譯期提前檢查可訪問性,如果嘗試進行不正確的訪問,IDE會提前提示錯誤,例如參數傳遞類型不匹配,非法訪問 private 屬性和方法。

“而在利用反射操作對象時,編譯器無法提前得知對象的類型,訪問是否合法,參數傳遞類型是否匹配。只有在程序運行時調用反射的代碼時才會從頭開始檢查、調用、返回結果,JVM也無法對反射的代碼進行優化。

雖然反射具有性能損耗的特點,但是我們不能一概而論,產生了使用反射就會性能下降的思想,反射的慢,需要同時調用上100W次才可能體現出來,在幾次、幾十次的調用,并不能體現反射的性能低下。所以不要一味地戴有色眼鏡看反射,在單次調用反射的過程中,性能損耗可以忽略不計。如果程序的性能要求很高,那么盡量不要使用反射。

反射基礎篇文末總結

  • 反射的思想:反射就像是一面鏡子一樣,在運行時才看到自己是誰,可獲取到自己的信息,甚至實例化對象。
  • 反射的作用:在運行時才確定實例化對象,使程序更加健壯,面對需求變更時,可以最大程度地做到不修改程序源碼應對不同的場景,實例化不同類型的對象。
  • 反射的應用場景常見的有3個:Spring的 IOC 容器,反射+工廠模式 使工廠類更穩定,JDBC連接數據庫時加載驅動類
  • 反射的3個特點:增加程序的靈活性、破壞類的封裝性以及性能損耗

本文轉載自微信公眾號「Java建設者」,可以通過以下二維碼關注。轉載本文請聯系Java建設者公眾號。

 

責任編輯:武曉燕 來源: Java建設者
相關推薦

2020-01-18 11:13:08

CPU程序存儲

2021-03-10 08:47:46

反射Java對象

2009-05-11 11:30:26

面試官程序員求職

2020-09-21 06:50:29

勒索軟件攻擊網絡安全

2020-03-30 08:00:38

Nginx徹底搞懂

2024-01-18 09:38:00

Java注解JDK5

2013-06-20 11:11:00

程序員經理

2019-08-22 09:07:11

大數據數據科學專業

2022-08-29 08:05:44

Go類型JSON

2024-11-08 08:56:01

2019-08-28 16:22:30

Python數據微信

2019-07-01 09:31:04

拉黑復活檢測器

2010-07-13 14:07:59

2024-10-09 07:40:43

2020-12-18 08:28:13

Redis數據數據庫

2024-01-15 09:15:52

parallel語句函數

2019-06-18 11:09:54

2024-05-10 08:15:32

go語言反射機制

2022-04-12 13:12:23

AI信任新技術

2021-06-27 17:15:25

SolarWinds黑客微軟
點贊
收藏

51CTO技術棧公眾號

欧美一级视频在线观看| 亚洲国产精品成人精品| 樱空桃在线播放| 亚洲免费黄色片| 久久夜色精品| 久久久国产影院| 国产精品无码电影| 天天综合91| 狠狠躁夜夜躁久久躁别揉| 亚洲v国产v在线观看| 亚洲欧美激情国产综合久久久| 亚洲一区二区网站| 久久久999精品| 久久丫精品国产亚洲av不卡| 亚洲欧洲二区| 色婷婷亚洲婷婷| 欧美黄色免费网址| jizz日韩| 成人sese在线| 成人av在线亚洲| 久久夜色精品国产噜噜亚洲av| 亚洲老妇激情| 中文国产成人精品| 亚洲av永久无码精品| 日本国产亚洲| 91久久免费观看| 国产在线播放观看| 91高清在线观看视频| 久久久久久99精品| 国产专区一区二区| 国产黄色高清视频| 久久激五月天综合精品| 日产精品99久久久久久| 亚州国产精品视频| 国内精品嫩模av私拍在线观看| 精品国产一区二区三区久久久 | 国产69精品久久久久777| 国产精品成人一区二区三区吃奶| 日韩人妻无码一区二区三区99| 重囗味另类老妇506070| 视频一区视频二区国产精品| 久久久久亚洲av无码a片| 日韩激情啪啪| 亚洲高清久久网| 日本久久久久久久久久| 日本一区影院| 欧美zozozo| 国模大尺度视频| 成人噜噜噜噜| 欧美一区二区视频在线观看2020| 久久人人爽av| 婷婷激情成人| 51久久夜色精品国产麻豆| 日韩不卡一二三| 日韩免费在线电影| 91精品婷婷国产综合久久性色 | 欧美黄网在线观看| 污污片在线免费视频| 亚洲天堂中文字幕| 日本一二三区视频在线| 女子免费在线观看视频www| 一区二区三区中文字幕| 免费看日b视频| 成人免费高清观看| 欧美日韩中文字幕在线视频| 女性隐私黄www网站视频| 色老太综合网| 欧美中文字幕久久| 亚洲综合婷婷久久| 成人免费91| 精品国产1区二区| 亚洲色偷偷色噜噜狠狠99网| 日韩av三区| 亚洲人成毛片在线播放| 91香蕉国产视频| 亚洲国产老妈| 97精品伊人久久久大香线蕉| 欧美在线观看不卡| 麻豆视频观看网址久久| 亚洲a∨日韩av高清在线观看| 国产白浆在线观看| 91蝌蚪porny九色| 视频在线精品一区| av文字幕在线观看| 精品国产电影一区| 欧美精品性生活| 一区二区在线视频观看| 日韩av网址在线观看| 蜜桃传媒一区二区亚洲| 欧美在线91| 欧美亚洲视频在线看网址| 91好色先生tv| 97精品超碰一区二区三区| 亚洲在线观看一区| 白浆视频在线观看| 欧美精品一卡两卡| 免费a在线观看播放| av中字幕久久| 欧美成人精品三级在线观看| 国产美女激情视频| 国产麻豆视频一区| 久久久神马电影| 国产一二区在线| 大桥未久av一区二区三区| 日本中文字幕影院| 亚洲肉体裸体xxxx137| 精品少妇v888av| 国产精品久久久久久久久夜色| 国产一区二区三区免费播放| 欧美1o一11sex性hdhd| 欧美wwww| 欧美男女性生活在线直播观看| 国产熟女高潮一区二区三区 | 久久精品成人欧美大片| 天堂а√在线中文在线新版| 国产成人无遮挡在线视频| 日韩片电影在线免费观看| 99色在线观看| 日韩视频免费观看高清完整版在线观看| 能免费看av的网站| 影音先锋亚洲电影| 91色p视频在线| 高清毛片在线看| 色综合天天综合色综合av| 免费观看污网站| 一本一道久久a久久精品蜜桃| 国产91在线播放九色快色| 色欲av永久无码精品无码蜜桃| 亚洲免费观看高清完整版在线观看 | 精品久久精品久久| 手机在线免费av| 91精品欧美综合在线观看最新| 人与嘼交av免费| 国产精品一卡| 久久精品中文字幕一区二区三区 | 成人欧美精品一区二区| 亚洲精品网址| 91色视频在线导航| 国产网站在线免费观看| 欧美丰满美乳xxx高潮www| jizz日本在线播放| 蜜臀av在线播放一区二区三区| 欧美12av| 主播大秀视频在线观看一区二区| 亚洲欧美中文日韩v在线观看| 国产情侣自拍av| www.日本不卡| 99热在线这里只有精品| 欧美亚洲国产日韩| 欧美与黑人午夜性猛交久久久| 深爱激情五月婷婷| 欧美特级www| 91l九色lporny| 美腿丝袜亚洲一区| 一区二区免费电影| 亚洲成a人片777777久久| 久久精品国产视频| 草草视频在线播放| 亚洲第一av色| 欧美图片一区二区| 日韩极品在线观看| 日韩欧美在线观看强乱免费| 日韩五码电影| 欧美激情亚洲另类| 瑟瑟在线观看| 欧美亚洲国产一卡| 国产人妻精品一区二区三区不卡| 国产成人在线看| 国产一级大片免费看| 久久久久久毛片免费看| 青青久久av北条麻妃黑人| 成人精品一区| 欧美一区二区三区不卡| 国产精品999久久久| 国产亚洲精久久久久久| 亚洲18在线看污www麻豆| 欧美.www| 欧美男人的天堂| 国内精品视频| 国模吧一区二区三区| 黄色的视频在线免费观看| 5858s免费视频成人| 色播视频在线播放| 国产精品麻豆一区二区| 亚洲成a人无码| 日本伊人色综合网| 国产一二三区在线播放| 99久久精品免费看国产免费软件| 国产一区二区色| 超碰在线无需免费| 亚洲精品久久久久中文字幕二区| 无码人妻av免费一区二区三区| 国产精品高潮呻吟| aaaaaav| 久久99精品久久久| 国产一级爱c视频| 久久精品国产www456c0m| 国产欧美一区二区视频| 91p九色成人| 午夜精品久久久久久久99热| 麻豆系列在线观看| 亚洲美女视频网站| 亚洲精品久久久久久久久久| 欧美性高清videossexo| 日本一区二区欧美| 亚洲色图欧美偷拍| 成年人网站免费看| 国产激情偷乱视频一区二区三区| 无码内射中文字幕岛国片| 午夜精品剧场| 亚洲午夜在线观看| 妖精一区二区三区精品视频| www国产亚洲精品| va天堂va亚洲va影视| 国产成人a亚洲精品| www.综合网.com| 久久影院在线观看| 91福利在线视频| 亚洲欧美第一页| 少妇精品高潮欲妇又嫩中文字幕 | 欧美在线亚洲在线| 国精一区二区三区| 欧美成人午夜剧场免费观看| 日本三级视频在线观看| 一区二区三区视频免费在线观看 | 国产亚洲一区二区在线观看| 毛茸茸free性熟hd| www.日韩大片| 国产av一区二区三区传媒| 国内久久精品视频| 一个色综合久久| 卡一卡二国产精品 | 久久久国内精品| 91高清一区| 国产精品波多野结衣| 欧美综合视频| 中国成人亚色综合网站| 久久亚洲国产| 一区二区视频国产| 久久亚洲在线| 成人在线观看www| 综合久久综合| 国产在线视频在线| 国产精品jizz在线观看美国| 国产激情在线看| 国内精品久久久久久久97牛牛| 欧美国产综合在线| 精品9999| 成人在线激情网| 首页国产欧美日韩丝袜| 国产日韩欧美久久| 精品无人码麻豆乱码1区2区| 欧美成人手机在线视频| 国产在线精品不卡| 国产一精品一aⅴ一免费| 成人短视频下载| 在线免费观看黄色小视频| 久久亚洲精华国产精华液| 欧美成人国产精品一区二区| 国产精品午夜电影| 亚洲波多野结衣| 亚洲成人午夜影院| 久久国产乱子伦精品| 欧美私人免费视频| 99热这里只有精品99| 亚洲风情亚aⅴ在线发布| 深夜福利在线看| 中文字幕国内精品| caopeng在线| 97视频免费在线观看| 日韩成人亚洲| 97碰碰视频| 久久中文资源| 婷婷亚洲婷婷综合色香五月| 日韩成人激情| 热99这里只有精品| 日韩av不卡一区二区| 在线观看视频你懂得| 成人一区二区三区视频在线观看| 成人手机在线免费视频| 国产精品水嫩水嫩| 国产在线视频第一页| 欧美性高清videossexo| 亚洲国产精品18久久久久久| 亚洲欧美日韩视频一区| 国产精品刘玥久久一区| 欧美在线观看网站| 国产免费av国片精品草莓男男| 久久精品人人做人人爽电影| 图片小说视频色综合| 成人小视频在线看| 国产一区在线观看麻豆| 99久久久无码国产精品性| 亚洲免费av网站| 日韩黄色片网站| 欧美mv和日韩mv的网站| 国产福利免费在线观看| 韩国视频理论视频久久| 久久亚洲人体| 欧美亚洲国产免费| 国产综合自拍| 女同激情久久av久久| 久久一日本道色综合| 免费无码毛片一区二区app| 欧美亚洲日本国产| 涩涩视频在线观看免费| 欧美激情网站在线观看| 国产精品麻豆成人av电影艾秋| 久久国产手机看片| 国内精品久久久久久久影视蜜臀 | 不卡av电影在线播放| 波兰性xxxxx极品hd| 色欧美片视频在线观看在线视频| 亚洲精品一区二区三区新线路| 久久精品2019中文字幕| 激情开心成人网| 国产在线一区二区三区欧美 | 午夜精品蜜臀一区二区三区免费| 日韩午夜电影免费看| 日本一区二区三区视频在线播放| 91久久夜色精品国产九色| 红桃视频一区二区三区免费| 国产精品网曝门| 自拍偷拍18p| 亚洲精品一区二区在线| 蜜桃视频在线观看播放| 国产精品久久久久久久小唯西川| 亚洲精品成人| 欧美性受xxxx黒人xyx性爽| 国产精品乱人伦一区二区| 这里只有久久精品视频| 一区二区三欧美| 婷婷六月国产精品久久不卡| 久久伦理网站| 香蕉成人久久| 日韩人妻一区二区三区| 欧美性极品少妇精品网站| 五月婷中文字幕| 69久久夜色精品国产69| 人人精品亚洲| 国产精品丝袜久久久久久消防器材| 不卡免费追剧大全电视剧网站| 精品无码人妻一区二区三| 日韩女同互慰一区二区| 波多野结衣中文在线| 国产精品sss| 伊人成人在线| aaaaaav| 欧美视频中文一区二区三区在线观看| 国产精品一区在线看| 国产精品女主播| 99久久99久久精品国产片果冰| 91aaa精品| 亚洲大片精品永久免费| 日韩porn| 国产精品丝袜视频| 亚洲精品99| 亚洲高清无码久久| 欧美性猛交xxxx| 91caoporm在线视频| 91色在线观看| 99亚洲伊人久久精品影院红桃| 国产男男chinese网站| 欧美午夜宅男影院| fc2ppv国产精品久久| 精品麻豆av| 日本怡春院一区二区| 青青青在线免费观看| 亚洲电影第1页| 97成人超碰| www插插插无码免费视频网站| 91丝袜国产在线播放| 在线观看毛片视频| 欧美日韩国产二区| 国产99亚洲| 亚洲一级片av| 精品福利在线视频| 在线免费观看黄色| 91久久伊人青青碰碰婷婷| 欧美专区18| 91嫩草|国产丨精品入口| 亚洲电影在线看| 亚洲日本中文| 久久国产亚洲精品无码| 最新不卡av在线| 午夜福利视频一区二区| 国产精品丝袜高跟| 亚洲黄色视屏| 亚洲欧美另类日本| 亚洲精品中文字幕女同| 91麻豆精品国产综合久久久 | 97在线视频国产| 久久综合成人| 久久人人爽人人爽人人片| 欧美电影一区二区三区| 色戒汤唯在线| 大片在线观看网站免费收看| 国产视频一区二区在线观看| 性色av蜜臀av| 国产在线不卡精品|