JavaWeb三層架構中Service和Dao層物件單例化可行性
宣告:以下個人觀點,僅作參考;
閱讀正文的前提知識:
一. 單例模式:
單例概念(百度): 單例模式,是一種常用的軟體設計模式。在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中,應用該模式的類一個類只有一個例項。即一個類只有一個物件例項。
Java中的單例模式: 從專案開始到結束, 某一Java類僅產生一個例項物件; java中的例項一般通過new呼叫構造建立( 工廠模式除外 ), 為了達到單例的目的, 需要將類的構造方法私有化(private), 僅提供public方法獲得例項, 並在該方法中完成單例邏輯;
完整的單例模式示例如下:
餓漢式(類載入時,即第一次用到該類時;):
//餓漢式:建立物件例項的時候直接初始化 空間換時間
public class SingletonOne {
//1、建立類中私有構造
private SingletonOne(){ }
//2、建立該型別的私有靜態例項
private static SingletonOne instance=new SingletonOne();
//3、建立公有靜態方法返回靜態例項物件
public static SingletonOne getInstance(){
return instance;
}
}
懶漢式
//懶漢式:類內例項物件建立時並不直接初始化,直到第一次呼叫get方法時,才完成初始化操作 //時間換空間 public class SingletonTwo { //1、建立私有構造方法 private SingletonTwo(){} //2、建立靜態的該類例項物件 private static SingletonTwo instance=null; //3、建立開放的靜態方法提供例項物件 public static SingletonTwo getInstance(){ if (instance == null) { synchronized (SingletonTwo.class) { if (instance == null) { instance = new SingletonTwo(); } } } return instance; } }
懶漢式和餓漢式差別在於: 實際建立物件的時機不同, 基於目前Web專案中一般時間(效能)要求大於空間要求, 故: JavaWeb中個人推薦使用餓漢式, 其特點: 利用空間換時間, 專案載入完成後執行響應快, 且直接利用類載入的時機遮蔽了執行緒安全問題, 使得類的定義相對簡單;
|→→→→→→→→→→→→時間線→→→→→→→→→→→→→→→→→→→→
| 餓漢式: 類載入 建立例項 工廠方法被呼叫 返回例項給呼叫者
| 懶漢式: 類載入 工廠方法被呼叫 建立例項 返回例項給呼叫者
|→→→→→→→→→→→→時間線→→→→→→→→→→→→→→→→→→→→
單例模式在應用層面的特點:
1. 單例的成員變數: 多執行緒操作時, 該成員變數唯一, 故可以作為全域性變數的配置 (但是需要注意的是, 多執行緒修改時的同步問題, 可以使用同步鎖方式避免) ;
2. 單例的方法: 多執行緒操作時, 可以共享方法, 但是個各個執行緒又有自己單獨的方法區域性屬性變數, 故可以實現單個例項方法提供全域性分別使用的目的, 從而不必每個執行緒自行例項化物件再呼叫, 即節約時間又節約空間;
二.Java類的載入時機:
1. 類載入器分類:
i. 啟動類載入器(Bootstrap ClassLoader): 載入Java核心庫, 屬於JVM一部分;
ii. 擴充套件類載入器(Extendsion ClassLoader): 載入java擴充套件庫;
iii. 應用程式類載入器(Application ClassLoader): 載入使用者自定義類;
iiii.自定義類載入器: 本人不瞭解, 先不講;
2. 因為講的都是使用者自定義類,所以細講 iii 的應用程式類載入器:
i. 一般情況下自定義類的載入時機:
a. new物件時;
b. 呼叫類的靜態資源(靜態成員變數 / 靜態方法)時;
c. 子類載入時;
d. 反射操作時;
e. 帶主方法的類;
ii. 在使用類前的適當時機, 預先主動進行類載入:
類載入的本質是將類的位元組碼檔案放入記憶體中, 當我們需要獲取某一個類的位元組碼物件時, 自然就驅動虛擬機器去載入該類:
a. Class.forName(類的全限定名);
b. this.getClass().getClassLoader().loadClass();
另外:在類中定義一個無關的靜態成員變數, 需要載入類時, 訪問一次該變數即可 (個人感覺此方法優點耍滑頭的意思0.0......) ;
-----------------------------------------------------前提知識到此結束-------------------------------------------------------------
正文部分:
三.JavaWeb三層架構中Service和Dao層物件單例化的必要性
JavaWeb的三層架構中, 我們需要預先啟動伺服器, 伺服器啟動完畢開始接受使用者訪問. 而使用者訪問時, 需要儘可能的快速響應.
其次JavaWeb中, Servlet預設以 單例多執行緒模式執行,即一個Servlet類僅例項化一個物件;
故:
首先 假設: Service和Dao層每次使用時均需要建立物件, 即: 每次客戶端請求Servlet訪問, Servlet中每個方法各自建立新的Sercice層物件, 該Sercice層物件的每個方法再各自建立新的Dao層物件. 因為每收到一個請求Servlet都會建立新的執行緒去處理, 故當前有多少個方法被呼叫, 就會相應建立多少個Service層和Dao層物件, 嚴重浪費CPU和記憶體資源, 同時降低伺服器響應速度;
然後 我們針對單個Servlet執行緒進行優化: 使得每個Servlet的執行緒中僅產生一個Service層和一個Dao層物件:
要實現該目的, 我們只需保證每個執行緒中Web層和Service層的方法共用一個Service層和Dao層物件即可, 此時僅需要將每個方法的各自例項化Service和Dao層物件提取為成員物件, 即可實現;
然後 我們針對單個Servlet物件進一步優化, 使得單個Servlet方法執行期間僅產生一個Service層和一個Dao層物件:
回顧Java整體機制, 不難找出最簡單有效的方法: 即將Service層和Dao層物件的定義變為static, 因為一個類的靜態變數僅此一份, 即可實現;
最後 實際專案中不可能只存在一個Servlet, 即使有BaseServlet幫助我們分模組分功能合併Servlet, 但大型專案功能和模組數量也不可小覷, 故需要再進一步優化, 使得所有Servlet的所有執行緒在訪問同一Service層和Dao層物件時, 使用同一個物件:
>補充話題背景知識部分:
或許, 此時部分讀者心中有疑問, 這樣的話, 還能正確地進行層間引數的傳遞嗎?
答案是肯定的, 原因就是Servlet的單例多執行緒模式, 而多執行緒呼叫同一方法時, 區域性變數是每個執行緒一份的 (因為每個執行緒都有單獨的記憶體空間) . 而層間引數傳遞完全可由區域性變數+方法引數實現;
繼續最後的優化之前簡單介紹一下設計模式中的 "工廠模式":
先講個小案例, 說明工廠模式的應用場景:
Java中萬物皆物件思想, 相比大家都已經很熟悉, 生活中一盤菜是物件, 使用的手機也是一部物件.
菜我們可以自己做, 因為其簡單, 我們知道使用什麼原料, 且更改原料也很簡單. 但是手機我們卻沒辦法自己製造, 故需要從網上購買, 電商售賣的手機最終由手機廠家來生產.
而且一般我們並不關係手機的具體原材料, 僅僅關心其功能是否完善好用. 所以,手機完全可以使用不同廠家和規格的原料和不同品牌的元件, 甚至: 同一款手機,也可以使用不同的原料和配件來實現完全相同的功能.
另外,生活中另一個問題, 我們並不是每天都自己做飯, 尤其是快節奏的今天, 此時食堂/飯館/外賣的意義就出現了, 將大眾需求進行統一處理, 降低時間和資源成本, 此處的食堂/飯館/外賣也可以看做一種工廠.
Java中, 我們在呼叫某些類的方法時, 內部實現太複雜, 呼叫者初始化起來比較困難, 僅需要功能, 或者說並不關心類的內部具體怎麼實現. 故需要類自己去處理, 並進行自我初始化, 並提供一個靜態的共有方法getInstance()返回自身的例項物件, 這就是Java中"工廠模式"達到的的目的之一: 高內聚;
另外, 在Java中, 我們Service層並不想去關心Dao層具體由哪個類實現, 以及如何實現, 就如我們並不關心手機內部天線的生產廠家一樣, 故此時我們只需要按手機型號選擇即可, 具體實現交給廠家; 這裡就引出了Java的面向介面程式設計, 我們例項化Service層和Dao層物件時, 使用該類物件的介面接收, 好比規定手機型號, 具體例項的建立交給工廠類去做(工廠類載入配置檔案....不再分講). 這裡就是Java中"工廠模式"達到的目的之二: 低耦合, 此時工廠類就是解耦的中間人;
第三, 在Java中, 我們有許多工作在每個類中都要進行相同流程的處理, 此時我們就需要流程化工作的抽取, 一般我們定義為工具類, 但在需要時也可以作為 "工廠模式" 的附加功能;
補充話題背景知識部分結束<
此處繼續進行擴充套件"多執行緒方法區域性變數作用域"和"工廠模式"之前的優化工作:
我們想要 "使得所有Servlet的所有執行緒在訪問同一Service層和Dao層物件時, 使用同一個物件" , 就需要用到單例模式. 即在Service層和Dao層所有類均採用單例模式, 這樣就初步實現了本階段優化的目的;
但是:
i. 目前多各類存在高度相同的業務程式碼, 故需要進行抽取;
ii. 考慮公司資料量陡增, 優化各層實現邏輯等,我們需要儘可能地進行層與層間解耦;
因此:
僅考慮解耦,則只需要結合"面向介面程式設計"和"工廠模式"即可,除了在呼叫層採用介面接收之外, 工廠類定義如下:
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.ResourceBundle;
public class BeanFactory2 {
public static Object getinstance(String className){//工廠方法,返回給定類名的例項化物件
ResourceBundle bundle = ResourceBundle.getBundle("beans");//建立ResourceBundle資源載入物件
String classLongName = bundle.getString(className);
try {
return Class.forName(classLongName).getConstructor().newInstance();//例項化物件
} catch (Exception e) {
return null;//若報錯返回null
}
}
}
同時考慮解耦和單例模式: 我們抽取成工具類, 而該工具類專門用來例項化單例並返回, 同時考慮上述的解耦思想, 此時把工具類的處理邏輯作為工廠類的附加功能即可, 具體實現如下:
import java.util.Enumeration;
import java.util.HashMap;
import java.util.ResourceBundle;
public class BeanFactory {
private static HashMap<String, Object> objectMap;//定義Map集合,存放類名和實體類Key-Value關係
//餓漢模式
//類載入時例項化出所有單例物件的集合
static{
ResourceBundle bundle = ResourceBundle.getBundle("beans");//建立ResourceBundle資源載入物件
Enumeration<String> keys = bundle.getKeys();//獲取配置檔案中的所有Key(類名)值列舉
//建立類Map集合,類名作為鍵,類例項物件作為值
objectMap = new HashMap<>();//例項化Map集合
//遍歷列舉物件, 獲取所有key值,根據key值獲取物件類的全限定名,並例項化物件存放到Map中
while(keys.hasMoreElements()){
String classNameStr = keys.nextElement();//獲取單個key
String classLongName = bundle.getString(classNameStr);//獲取對應全限定名
try {
Object object = Class.forName(classLongName).getConstructor().newInstance();//例項化物件
objectMap.put(classNameStr,object);//放入Map集合中
} catch (Exception e) {
objectMap.put(classNameStr,null);//若報錯放入null
}
}
}
public static Object getinstance(String className){//工廠方法,返回給定類名的例項化物件
return objectMap.get(className);
}
}
請注意: 此時因為列舉是無序的, 在使用單例模式工廠時,除非將dao層類資訊單獨配置beans屬性檔案,在service層類物件之前全部載入完畢(或使用其它方法達到類似目的), 否則不要在service層物件中定義全域性dao層物件, 此時應該每個方法分別使用getInstance方法獲取單例物件的地址來使用;
總結:
1. 以上單例模式工廠類, 在博主的測試專案中執行正常
2. 為了更進一步的提高專案載入的完整度, 可以利用文中方法在專案載入時, 也就是在ServletContextListener監聽器中直接使用反射預先載入單例模式工廠類, 從而優化使用者的體驗;
以上就是博主對"JavaWeb三層架構中Service和Dao層物件單例化的必要性"的論述, 即實現此處的單例化是個很有優勢的選擇, 同時附帶介紹了層間解耦思想,希望對大家有所幫助.
寫部落格唯一的目的: 本著對讀者負責的態度,要求自己以及其嚴謹的態度學習研究某一方面知識.
轉載請標明出處: 划船一哥 :https://blog.csdn.net/weixin_42711325/article/details/83340431