??前言:本文章為瑞_系列專欄之《Java開發(fā)手冊》的工程結(jié)構(gòu)篇,主要介紹應(yīng)用分層、二方庫依賴、服務(wù)器。由于博主是從阿里的《Java開發(fā)手冊》學(xué)習(xí)到Java的編程規(guī)約,所以本系列專欄主要以這本書進(jìn)行講解和拓展,有需要的小伙伴可以點擊鏈接下載。本文僅供大家交流、學(xué)習(xí)及研究使用,禁止用于商業(yè)用途,違者必究!
本系列第一篇鏈接:(一)編程規(guī)約
本系列第二篇鏈接:(二)異常日志
本系列第三篇鏈接:(三)單元測試
本系列第四篇鏈接:(四)安全規(guī)約
本系列第五篇鏈接:(五)MySQL數(shù)據(jù)庫
本系列第六篇鏈接:(六)工程結(jié)構(gòu)
本系列第七篇鏈接:(七)設(shè)計規(guī)約
工程結(jié)構(gòu)的意義
??工程結(jié)構(gòu)是整個軟件開發(fā)過程中非常重要的一環(huán),它定義了各個模塊之間的層次關(guān)系和依賴關(guān)系,使得開發(fā)人員能夠更加清晰地理解和管理復(fù)雜的軟件系統(tǒng)。它通過合理的層次劃分、模塊化開發(fā)、減少冗余、提高可擴(kuò)展性、便于測試和維護(hù)以及促進(jìn)團(tuán)隊協(xié)作等方面,工程結(jié)構(gòu)有助于提高軟件開發(fā)的效率和質(zhì)量,降低維護(hù)成本和風(fēng)險。
??工程結(jié)構(gòu)就像一座城市的規(guī)劃,合理的規(guī)劃可以讓城市更加有序、美觀和宜居。同樣,合理的工程結(jié)構(gòu)可以讓軟件系統(tǒng)更加有序、可維護(hù)和可擴(kuò)展,可以為開發(fā)人員提供了一種組織和構(gòu)建復(fù)雜軟件系統(tǒng)的有效方法。當(dāng)你開始認(rèn)知到所有的代碼不能全部寫在main方法里的時候,就是理解工程結(jié)構(gòu)意義的開始。
??工程結(jié)構(gòu)主要的作用和意義如下:
- 層次劃分:工程結(jié)構(gòu)將整個軟件系統(tǒng)劃分為不同的層次,每個層次都有明確的職責(zé)和功能。層次之間的劃分使得開發(fā)人員能夠更好地理解系統(tǒng)的整體結(jié)構(gòu),并更好地組織代碼和模塊。
- 模塊化開發(fā):工程結(jié)構(gòu)使得開發(fā)人員可以將復(fù)雜的軟件系統(tǒng)劃分為多個模塊,每個模塊負(fù)責(zé)特定的功能或業(yè)務(wù)領(lǐng)域。這種模塊化開發(fā)方式可以提高代碼的可維護(hù)性和可重用性,降低系統(tǒng)的耦合度,使得開發(fā)更加高效和可靠。
- 減少冗余:工程結(jié)構(gòu)通過合理的層次劃分和模塊化開發(fā),可以避免代碼的冗余和重復(fù)。每個層次和模塊只關(guān)注自己的業(yè)務(wù)邏輯和功能,避免了代碼的交叉和耦合,減少了不必要的復(fù)雜度。
- 提高可擴(kuò)展性:工程結(jié)構(gòu)使得軟件系統(tǒng)更加易于擴(kuò)展。當(dāng)需要增加新的功能或業(yè)務(wù)時,只需要在相應(yīng)的層次上添加新的模塊或組件,而不會對其他部分造成影響。這種可擴(kuò)展性使得軟件系統(tǒng)能夠更好地適應(yīng)變化和未來的發(fā)展。
- 便于測試和維護(hù):工程結(jié)構(gòu)使得測試和維護(hù)變得更加容易。每個層次和模塊都有明確的接口和功能,可以單獨進(jìn)行測試和調(diào)試。同時,層次之間的依賴關(guān)系使得代碼更加易于理解和維護(hù),降低了維護(hù)成本和難度。
- 促進(jìn)團(tuán)隊協(xié)作:工程結(jié)構(gòu)有助于促進(jìn)團(tuán)隊協(xié)作和分工。不同開發(fā)人員可以負(fù)責(zé)不同的層次和模塊,共同完成復(fù)雜的軟件系統(tǒng)。合理的分工可以提高開發(fā)效率和代碼質(zhì)量,使得團(tuán)隊協(xié)作更加順暢和高效。
(一) 應(yīng)用分層
- 【推薦】圖中默認(rèn)上層依賴于下層,箭頭關(guān)系表示可直接依賴,如:開放接口層可以依賴于Web 層,也可以直接依賴于 Service 層,依此類推:
- 開放接口層:可直接封裝 Service 方法暴露成 RPC 接口;通過 Web 封裝成 http 接口;網(wǎng)關(guān)控制層等。
- 終端顯示層:各個端的模板渲染并執(zhí)行顯示的層。當(dāng)前主要是 velocity 渲染,JS 渲染,JSP 渲染,移 動端展示等。
- Web 層:主要是對訪問控制進(jìn)行轉(zhuǎn)發(fā),各類基本參數(shù)校驗,或者不復(fù)用的業(yè)務(wù)簡單處理等。
- Service 層:相對具體的業(yè)務(wù)邏輯服務(wù)層。
- Manager 層:通用業(yè)務(wù)處理層,它有如下特征:
??1) 對第三方平臺封裝的層,預(yù)處理返回結(jié)果及轉(zhuǎn)化異常信息。
??2) 對 Service 層通用能力的下沉,如緩存方案、中間件通用處理。
??3) 與 DAO 層交互,對多個 DAO 的組合復(fù)用。 - DAO 層:數(shù)據(jù)訪問層,與底層 MySQL、Oracle、Hbase、OB 等進(jìn)行數(shù)據(jù)交互。
- 外部接口或第三方平臺:包括其它部門 RPC 開放接口,基礎(chǔ)平臺,其它公司的 HTTP 接口。
瑞:本條需要長時間的經(jīng)驗累積才能理解,如果是初學(xué)者的話請先記住本條即可,后續(xù)慢慢形成自己的理解。
博主對應(yīng)用分層的理解是:要根據(jù)自己的業(yè)務(wù)結(jié)構(gòu)去進(jìn)行分層設(shè)計,如常見的三層模型:用戶表示層、業(yè)務(wù)邏輯層、數(shù)據(jù)層。根據(jù)自身的業(yè)務(wù)面向接口編程進(jìn)行N層架構(gòu)設(shè)計
,每一層都各司其職互不干擾,各層之間應(yīng)當(dāng)盡量解耦。
博主對DAO數(shù)據(jù)訪問層的理解:
??DAO層只進(jìn)行數(shù)據(jù)訪問,它只負(fù)責(zé)將SQL傳給底層數(shù)據(jù)庫進(jìn)行數(shù)據(jù)交互,而不進(jìn)行其上層業(yè)務(wù)邏輯層的工作,無論業(yè)務(wù)邏輯層傳遞什么參數(shù)進(jìn)來,都應(yīng)傳遞至數(shù)據(jù)庫進(jìn)行執(zhí)行操作而不進(jìn)行業(yè)務(wù)判斷校驗處理。所以類似數(shù)據(jù)庫連接對象不應(yīng)當(dāng)向上層即業(yè)務(wù)邏輯層傳遞,這不僅違反了分層設(shè)計的意義也污染了接口。
??所以按照博主的理解,對于Java后端程序控制事務(wù)的回滾,只能做到數(shù)據(jù)行級別的控制,因為數(shù)據(jù)庫訪問對象不應(yīng)當(dāng)在service層傳遞和操作,無法做到service調(diào)用多個dao層方法去回滾,畢竟要用到同一個連接對象才可以回滾事務(wù)。
??要用到同一個連接對象,這在實際開發(fā)中是不切和實際的。既然dao層不去傳遞數(shù)據(jù)庫連接對象,那需要多個數(shù)據(jù)庫操作的業(yè)務(wù)的事務(wù)如何處理呢?想想場景,比如支付寶充值話費(支付寶扣款,話費增加),阿里和電信的接口不可能去傳遞數(shù)據(jù)庫連接對象,支持寶話費充值業(yè)務(wù)需要調(diào)用電信提供的接口,但電信的接口會要求提供數(shù)據(jù)庫連接對象嗎,這是絕對不可能的?? 對吧??如果說,支付寶扣款成功了,但是話費充值失?。ū热缯脭嗑W(wǎng)演練而發(fā)生異常等情況),那么電信的service層就會去判斷,只要確保支付寶轉(zhuǎn)的錢到賬了,那應(yīng)該是自己電信的業(yè)務(wù)出問題了,它們可能會先墊付這筆錢,再service生成核對的消息給人工審核,然后巴拉巴拉總之電信會通過自己的異常流程進(jìn)行處理解決,是不可能電信去回滾支付寶的扣款的,想一想是不是這個邏輯。所以在實際業(yè)務(wù)中,數(shù)據(jù)庫連接對象是無法傳遞的,因為數(shù)據(jù)庫連接對象早就被close了,一旦被close,那這輩子都取不到該連接對象了,更不要說傳遞它進(jìn)行回滾操作。所以我認(rèn)為即使在service層處理了事務(wù)意義也不大(除非就是個單體項目)更多的應(yīng)當(dāng)靠service層的邏輯校驗保護(hù)業(yè)務(wù)的正常的或異常的執(zhí)行流程,而dao層只需要管好自己與數(shù)據(jù)庫的交互,即保證dao中單個方法的回滾。
(這是博主自身的經(jīng)驗分享,可能和您認(rèn)知的相悖,但博主依然認(rèn)為應(yīng)當(dāng)通過分析,形成自己的理解,而不是別人說什么就是什么。在程序設(shè)計上沒有對錯,只有適合和更適合,若有問題請在評論區(qū)中指正,非常感謝 )本條雖為推薦,但在實際開發(fā)中非常重要,請務(wù)必多花時間理解應(yīng)用分層的意義及作用
- 【參考】(分層異常處理規(guī)約)在 DAO 層,產(chǎn)生的異常類型有很多,無法用細(xì)粒度的異常進(jìn)行 catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印日志,因為日志在 Manager/Service 層一定需要捕獲并打印到日志文件中去,如果同臺服務(wù)器再打日志,浪費性能和存儲。在 Service 層出現(xiàn)異常時,必須記錄出錯日志到磁盤,盡可能帶上參數(shù)信息,相當(dāng)于保護(hù)案發(fā)現(xiàn)場。Manager 層與 Service 同機(jī)部署,日志方式與 DAO 層處理一致,如果是單獨部署,則采用與 Service 一致的處理方式。Web 層絕不應(yīng)該繼續(xù)往上拋異常,因為已經(jīng)處于頂層,如果意識到這個異常將導(dǎo)致頁面無法正常渲染,那么就應(yīng)該直接跳轉(zhuǎn)到友好錯誤頁面,盡量加上友好的錯誤提示信息。開放接口層要將異常處理成錯誤碼和錯誤信息方式返回。
瑞:本條中提到了Web 層絕不應(yīng)該繼續(xù)往上拋異常,因為已經(jīng)處于頂層,所以在Web層(常見是Controller),一定要對catch中的error_message進(jìn)行轉(zhuǎn)義處理后返回給前端。本條需結(jié)合本系列第二篇【異常日志】中的(二) 異常處理第4條:【強(qiáng)制】捕獲異常是為了處理它,不要捕獲了卻什么都不處理而拋棄之,如果不想處理它,請將該異常拋給它的調(diào)用者。最外層的業(yè)務(wù)使用者,必須處理異常,將其轉(zhuǎn)化為用戶可以理解的內(nèi)容,以及【異常日志】中的(一) 錯誤碼第7條、第8條。錯誤碼不能直接輸出給用戶作為提示信息使用、錯誤碼之外的業(yè)務(wù)獨特信息由 error_message 來承載,而不是讓錯誤碼本身涵蓋過多具體業(yè)務(wù)屬性。
-
【參考】分層領(lǐng)域模型規(guī)約:
? DO(Data Object):此對象與數(shù)據(jù)庫表結(jié)構(gòu)一一對應(yīng),通過 DAO 層向上傳輸數(shù)據(jù)源對象。
? DTO(Data Transfer Object):數(shù)據(jù)傳輸對象,Service 或 Manager 向外傳輸?shù)膶ο蟆?br> ? BO(Business Object):業(yè)務(wù)對象,可以由 Service 層輸出的封裝業(yè)務(wù)邏輯的對象。
? Query:數(shù)據(jù)查詢對象,各層接收上層的查詢請求。注意超過 2 個參數(shù)的查詢封裝,禁止使用 Map 類來傳輸。
? VO(View Object):顯示層對象,通常是 Web 向模板渲染引擎層傳輸?shù)膶ο蟆?/li>
瑞:一定要分清楚 [ 數(shù)據(jù)對象類 ] 和 [ 業(yè)務(wù)對象類 ] 的職責(zé)區(qū)別。數(shù)據(jù)對象類只負(fù)責(zé)數(shù)據(jù)的存儲和傳遞,數(shù)據(jù)對象類內(nèi)部不應(yīng)該存在任何的業(yè)務(wù)邏輯的處理,通常情況下只有 setter/getter/toString ,如DTO數(shù)據(jù)傳輸對象。而業(yè)務(wù)對象類才是負(fù)責(zé)處理業(yè)務(wù)邏輯,不應(yīng)該去維護(hù)非業(yè)務(wù)的成員屬性,如Service層接口的具體實現(xiàn)類。
下圖為博主的經(jīng)驗對應(yīng)用分層的理解,可能有誤,僅供學(xué)習(xí)參考(若需搬運下圖請標(biāo)明出處)
瑞:
1??PO/DO(實體類):根據(jù)阿里巴巴的開發(fā)手冊中的定義,DO可以認(rèn)為等同于PO也就是Entity實體類,與數(shù)據(jù)庫表結(jié)構(gòu)一一對應(yīng),用來增刪改查
2??BO(業(yè)務(wù)對象類):BO是PO的需求結(jié)果組合,同一類別的業(yè)務(wù)就會有一個BO與之對應(yīng),除了get,set方法以外,還有業(yè)務(wù)邏輯的方法。如購物業(yè)務(wù),需要從商品DO、用戶余額DO等獲取需要的數(shù)據(jù)
3??:DTO(數(shù)據(jù)傳輸對象類):DTO和BO、VO的區(qū)別主要是就是字段的刪減,DTO在BO的基礎(chǔ)上,獲取需要的數(shù)據(jù)向上層傳遞。而VO是在DTO的基礎(chǔ)上,刪減一些數(shù)據(jù)向前端展示
4??VO(展示對象類):VO就是展示給前端用的數(shù)據(jù),一般情況下是從DTO中獲取要展示的信息。如博主信息簡略展示,會從博主信息DTO中獲取到名稱、成就展示,而DTO中的頭像等數(shù)據(jù)不需要在簡略展示的前端界面中出現(xiàn)
5??所以在一些簡單的業(yè)務(wù)中(基礎(chǔ)CRUD)可以把DTO和BO合并為DTO,VO也可以使用DTO代替,即PO(實體類)->DTO(Response)->前端
(二) 二方庫依賴
-
【強(qiáng)制】定義 GAV 遵從以下規(guī)則:
1) GroupID 格式:com.{公司/BU }.業(yè)務(wù)線 [.子業(yè)務(wù)線],最多 4 級。
說明:{公司/BU} 例如:alibaba/taobao/tmall/aliexpress 等 BU 一級;子業(yè)務(wù)線可選。
正例:com.taobao.jstorm 或 com.alibaba.dubbo.register
2) ArtifactID 格式:產(chǎn)品線名-模塊名。語義不重復(fù)不遺漏,先到中央倉庫去查證一下。
正例:dubbo-client / fastjson-api / jstorm-tool
3) Version:詳細(xì)規(guī)定參考下方。 -
【強(qiáng)制】二方庫版本號命名方式:主版本號.次版本號.修訂號
1)主版本號:產(chǎn)品方向改變,或者大規(guī)模 API 不兼容,或者架構(gòu)不兼容升級。
2) 次版本號:保持相對兼容性,增加主要功能特性,影響范圍極小的 API 不兼容修改。
3) 修訂號:保持完全兼容性,修復(fù) BUG、新增次要功能特性等。
說明:注意起始版本號必須為:1.0.0,而不是 0.0.1。
反例:倉庫內(nèi)某二方庫版本號從 1.0.0.0 開始,一直默默“升級”成 1.0.0.64,完全失去版本的語義信息。
瑞:在Maven中使用<version>,如下所示:
<version>1.0.0</version>
-
【強(qiáng)制】線上應(yīng)用不要依賴 SNAPSHOT 版本(安全包除外);正式發(fā)布的類庫必須先去中央倉庫進(jìn)行查證,使 RELEASE 版本號有延續(xù)性,且版本號不允許覆蓋升級。
說明:不依賴 SNAPSHOT 版本是保證應(yīng)用發(fā)布的冪等性。另外,也可以加快編譯時的打包構(gòu)建。 -
【強(qiáng)制】二方庫的新增或升級,保持除功能點之外的其它 jar 包仲裁結(jié)果不變。如果有改變,必須明確評估和驗證。
說明:在升級時,進(jìn)行 dependency:resolve 前后信息比對,如果仲裁結(jié)果完全不一致,那么通過dependency:tree 命令,找出差異點,進(jìn)行<exclude>排除 jar 包。 -
【強(qiáng)制】二方庫里可以定義枚舉類型,參數(shù)可以使用枚舉類型,但是接口返回值不允許使用枚舉類型或者包含枚舉類型的 POJO 對象。
-
【強(qiáng)制】依賴于一個二方庫群時,必須定義一個統(tǒng)一的版本變量,避免版本號不一致。
說明:依賴 springframework-core,-context,-beans,它們都是同一個版本,可以定義一個變量來保存版本:${spring.version},定義依賴的時候,引用該版本。
瑞:在Maven中如下所示:
<!-- 屬性配置 -->
<properties>
<spring.version>版本</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
-
【強(qiáng)制】禁止在子項目的 pom 依賴中出現(xiàn)相同的 GroupId,相同的 ArtifactId,但是不同的Version。
說明:在本地調(diào)試時會使用各子項目指定的版本號,但是合并成一個 war,只能有一個版本號出現(xiàn)在最后的lib 目錄中。曾經(jīng)出現(xiàn)過線下調(diào)試是正確的,發(fā)布到線上卻出故障的先例。 -
【推薦】底層基礎(chǔ)技術(shù)框架、核心數(shù)據(jù)管理平臺、或近硬件端系統(tǒng)謹(jǐn)慎引入第三方實現(xiàn)。
-
【推薦】所有 pom 文件中的依賴聲明放在<dependencies>語句塊中,所有版本仲裁放在<dependencyManagement>語句塊中。
說明:<dependencyManagement>里只是聲明版本,并不實現(xiàn)引入,因此子項目需要顯式的聲明依賴,version 和 scope 都讀取自父 pom。而<dependencies>所有聲明在主 pom 的<dependencies>里的依賴都會自動引入,并默認(rèn)被所有的子項目繼承。
瑞:后續(xù)博主會專門寫關(guān)于maven的使用博客,先預(yù)留位置
-
【推薦】二方庫不要有配置項,最低限度不要再增加配置項。
-
【推薦】不要使用不穩(wěn)定的工具包或者 Utils 類。
說明:不穩(wěn)定指的是提供方無法做到向下兼容,在編譯階段正常,但在運行時產(chǎn)生異常,因此,盡量使用業(yè)界穩(wěn)定的二方工具包。 -
【參考】為避免應(yīng)用二方庫的依賴沖突問題,二方庫發(fā)布者應(yīng)當(dāng)遵循以下原則:
1)精簡可控原則。移除一切不必要的 API 和依賴,只包含 Service API、必要的領(lǐng)域模型對象、Utils 類、
常量、枚舉等。如果依賴其它二方庫,盡量是 provided 引入,讓二方庫使用者去依賴具體版本號;無 log
具體實現(xiàn),只依賴日志框架。
2)穩(wěn)定可追溯原則。每個版本的變化應(yīng)該被記錄,二方庫由誰維護(hù),源碼在哪里,都需要能方便查到。除
非用戶主動升級版本,否則公共二方庫的行為不應(yīng)該發(fā)生變化。
(三) 服務(wù)器
-
【推薦】高并發(fā)服務(wù)器建議調(diào)小 TCP 協(xié)議的 time_wait 超時時間。
說明:操作系統(tǒng)默認(rèn) 240 秒后,才會關(guān)閉處于 time_wait 狀態(tài)的連接,在高并發(fā)訪問下,服務(wù)器端會因為處于 time_wait 的連接數(shù)太多,可能無法建立新的連接,所以需要在服務(wù)器上調(diào)小此等待值。
正例:在 linux 服務(wù)器上請通過變更/etc/sysctl.conf 文件去修改該缺省值(秒):
??net.ipv4.tcp_fin_timeout = 30 -
【推薦】調(diào)大服務(wù)器所支持的最大文件句柄數(shù)(File Descriptor,簡寫為 fd)。
說明:主流操作系統(tǒng)的設(shè)計是將 TCP/UDP 連接采用與文件一樣的方式去管理,即一個連接對應(yīng)于一個 fd。主流的linux服務(wù)器默認(rèn)所支持最大fd數(shù)量為1024,當(dāng)并發(fā)連接數(shù)很大時很容易因為fd不足而出現(xiàn)“opentoo many files”錯誤,導(dǎo)致新的連接無法建立。建議將 linux 服務(wù)器所支持的最大句柄數(shù)調(diào)高數(shù)倍(與服務(wù)器的內(nèi)存數(shù)量相關(guān))。 -
【推薦】給 JVM 環(huán)境參數(shù)設(shè)置-XX:+HeapDumpOnOutOfMemoryError 參數(shù),讓 JVM 碰到 OOM場景時輸出 dump 信息。
說明:OOM 的發(fā)生是有概率的,甚至相隔數(shù)月才出現(xiàn)一例,出錯時的堆內(nèi)信息對解決問題非常有幫助。
瑞:jvm虛擬機(jī)參數(shù)模版如下
設(shè)置 | 說明 |
---|---|
-Xms1g | 初始堆內(nèi)存大小 |
-Xmx1g | 最大堆內(nèi)存大小 |
-Xss256k | 虛擬機(jī)棧內(nèi)存大小 |
-XX:MaxMetaspaceSize=512m | 最大元空間大小 |
-XX:+DisableExplicitGC | 讓System.gc()方法失效 |
-XX:+HeapDumpOnOutOfMemoryError | 在OutOfMemoryError的時候保存堆內(nèi)存快照 |
-XX:HeapDumpPath=/opt/logs/my-service.hprof | 將堆內(nèi)存快照保存到文件中 |
-XX:+PrintGCDetails | 打印垃圾回收的詳細(xì)信息 |
-XX:+PrintGCDateStamps | 打印垃圾回收發(fā)生的時間 |
-Xloggc:文件路徑 | 將垃圾回收信息保存保存到文件中 |
- 【推薦】在線上生產(chǎn)環(huán)境,JVM 的 Xms 和 Xmx 設(shè)置一樣大小的內(nèi)存容量,避免在 GC 后調(diào)整堆大小帶來的壓力。
瑞:建議將-Xms設(shè)置的和-Xmx一樣大,有以下幾點好處:
1??運行時性能更好,堆的擴(kuò)容是需要向操作系統(tǒng)申請內(nèi)存的,這樣會導(dǎo)致程序性能短期下降。
2??可用性問題,如果在擴(kuò)容時其他程序正在使用大量內(nèi)存,很容易因為操作系統(tǒng)內(nèi)存不足分配失敗。
3??啟動速度更快,Oracle官方文檔的原話:如果初始堆太小,Java 應(yīng)用程序啟動會變得很慢,因為 JVM 被迫頻繁執(zhí)行垃圾收集,直到堆增長到更合理的大小。文章來源:http://www.zghlxwxcb.cn/news/detail-803404.html
- 【參考】服務(wù)器內(nèi)部重定向必須使用 forward;外部重定向地址必須使用 URL Broker 生成,否則因線上采用 HTTPS 協(xié)議而導(dǎo)致瀏覽器提示“不安全“。此外,還會帶來 URL 維護(hù)不一致的問題。
??如果覺得這篇文章對您有所幫助的話,請動動小手點波關(guān)注??,你的點贊??收藏??轉(zhuǎn)發(fā)??評論??都是對博主最好的支持~文章來源地址http://www.zghlxwxcb.cn/news/detail-803404.html
到了這里,關(guān)于瑞_Java開發(fā)手冊_(六)工程結(jié)構(gòu)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!