這部分主要是開源Java EE框架方面的內(nèi)容,包括Hibernate晓勇、MyBatis堂飞、Spring、Spring MVC等绑咱,由于Struts 2已經(jīng)是明日黃花绰筛,在這里就不討論Struts 2的面試題,如果需要了解相關(guān)內(nèi)容描融,可以參考我的另一篇文章《Java面試題集(86-115)》铝噩。此外,這篇文章還對企業(yè)應用架構(gòu)窿克、大型網(wǎng)站架構(gòu)和應用服務(wù)器優(yōu)化等內(nèi)容進行了簡單的探討骏庸,這些內(nèi)容相信對面試會很有幫助。
126年叮、什么是ORM具被?
答:對象關(guān)系映射(Object-Relational Mapping,簡稱ORM)是一種為了解決程序的面向?qū)ο竽P团c數(shù)據(jù)庫的關(guān)系模型互不匹配問題的技術(shù)只损;簡單的說一姿,ORM是通過使用描述對象和數(shù)據(jù)庫之間映射的元數(shù)據(jù)(在Java中可以用XML或者是注解),將程序中的對象自動持久化到關(guān)系數(shù)據(jù)庫中或者將關(guān)系數(shù)據(jù)庫表中的行轉(zhuǎn)換成Java對象跃惫,其本質(zhì)上就是將數(shù)據(jù)從一種形式轉(zhuǎn)換到另外一種形式叮叹。
127、持久層設(shè)計要考慮的問題有哪些爆存?你用過的持久層框架有哪些蛉顽?
答:所謂"持久"就是將數(shù)據(jù)保存到可掉電式存儲設(shè)備中以便今后使用,簡單的說终蒂,就是將內(nèi)存中的數(shù)據(jù)保存到關(guān)系型數(shù)據(jù)庫蜂林、文件系統(tǒng)、消息隊列等提供持久化支持的設(shè)備中拇泣。持久層就是系統(tǒng)中專注于實現(xiàn)數(shù)據(jù)持久化的相對獨立的層面噪叙。
持久層設(shè)計的目標包括:
- 數(shù)據(jù)存儲邏輯的分離,提供抽象化的數(shù)據(jù)訪問接口霉翔。
- 數(shù)據(jù)訪問底層實現(xiàn)的分離睁蕾,可以在不修改代碼的情況下切換底層實現(xiàn)。
- 資源管理和調(diào)度的分離,在數(shù)據(jù)訪問層實現(xiàn)統(tǒng)一的資源調(diào)度(如緩存機制)子眶。
- 數(shù)據(jù)抽象瀑凝,提供更面向?qū)ο蟮臄?shù)據(jù)操作。
持久層框架有:
-Guzz
-jOOQ
128臭杰、Hibernate中SessionFactory是線程安全的嗎粤咪?Session是線程安全的嗎(兩個線程能夠共享同一個Session嗎)?
答:SessionFactory對應Hibernate的一個數(shù)據(jù)存儲的概念渴杆,它是線程安全的寥枝,可以被多個線程并發(fā)訪問。SessionFactory一般只會在啟動的時候構(gòu)建磁奖。對于應用程序囊拜,最好將SessionFactory通過單例模式進行封裝以便于訪問。Session是一個輕量級非線程安全的對象(線程間不能共享session)比搭,它表示與數(shù)據(jù)庫進行交互的一個工作單元冠跷。Session是由SessionFactory創(chuàng)建的,在任務(wù)完成之后它會被關(guān)閉身诺。Session是持久層服務(wù)對外提供的主要接口蜜托。Session會延遲獲取數(shù)據(jù)庫連接(也就是在需要的時候才會獲取)戚长。為了避免創(chuàng)建太多的session盗冷,可以使用ThreadLocal將session和當前線程綁定在一起,這樣可以讓同一個線程獲得的總是同一個session同廉。Hibernate 3中SessionFactory的getCurrentSession()方法就可以做到。
129柑司、Hibernate中Session的load和get方法的區(qū)別是什么迫肖?
答:主要有以下三項區(qū)別:
① 如果沒有找到符合條件的記錄,get方法返回null攒驰,load方法拋出異常蟆湖。
② get方法直接返回實體類對象,load方法返回實體類對象的代理玻粪。
③ 在Hibernate 3之前隅津,get方法只在一級緩存中進行數(shù)據(jù)查找,如果沒有找到對應的數(shù)據(jù)則越過二級緩存劲室,直接發(fā)出SQL語句完成數(shù)據(jù)讀嚷兹浴;load方法則可以從二級緩存中獲取數(shù)據(jù)很洋;從Hibernate 3開始充蓝,get方法不再是對二級緩存只寫不讀,它也是可以訪問二級緩存的。
說明:對于load()方法Hibernate認為該數(shù)據(jù)在數(shù)據(jù)庫中一定存在可以放心的使用代理來實現(xiàn)延遲加載谓苟,如果沒有數(shù)據(jù)就拋出異常官脓,而通過get()方法獲取的數(shù)據(jù)可以不存在。
130涝焙、Session的save()卑笨、update()、merge()仑撞、lock()赤兴、saveOrUpdate()和persist()方法分別是做什么的?有什么區(qū)別派草?
答:Hibernate的對象有三種狀態(tài):瞬時態(tài)(transient)搀缠、持久態(tài)(persistent)和游離態(tài)(detached),如第135題中的圖所示近迁。瞬時態(tài)的實例可以通過調(diào)用save()艺普、persist()或者saveOrUpdate()方法變成持久態(tài);游離態(tài)的實例可以通過調(diào)用 update()鉴竭、saveOrUpdate()歧譬、lock()或者replicate()變成持久態(tài)。save()和persist()將會引發(fā)SQL的INSERT語句搏存,而update()或merge()會引發(fā)UPDATE語句瑰步。save()和update()的區(qū)別在于一個是將瞬時態(tài)對象變成持久態(tài),一個是將游離態(tài)對象變?yōu)槌志脩B(tài)璧眠。merge()方法可以完成save()和update()方法的功能缩焦,它的意圖是將新的狀態(tài)合并到已有的持久化對象上或創(chuàng)建新的持久化對象。對于persist()方法责静,按照官方文檔的說明:① persist()方法把一個瞬時態(tài)的實例持久化袁滥,但是并不保證標識符被立刻填入到持久化實例中,標識符的填入可能被推遲到flush的時間灾螃;② persist()方法保證當它在一個事務(wù)外部被調(diào)用的時候并不觸發(fā)一個INSERT語句题翻,當需要封裝一個長會話流程的時候,persist()方法是很有必要的腰鬼;③ save()方法不保證第②條嵌赠,它要返回標識符,所以它會立即執(zhí)行INSERT語句熄赡,不管是在事務(wù)內(nèi)部還是外部姜挺。至于lock()方法和update()方法的區(qū)別,update()方法是把一個已經(jīng)更改過的脫管狀態(tài)的對象變成持久狀態(tài)本谜;lock()方法是把一個沒有更改過的脫管狀態(tài)的對象變成持久狀態(tài)初家。
131、闡述Session加載實體對象的過程。
答:Session加載實體對象的步驟是:
① Session在調(diào)用數(shù)據(jù)庫查詢功能之前溜在,首先會在一級緩存中通過實體類型和主鍵進行查找陌知,如果一級緩存查找命中且數(shù)據(jù)狀態(tài)合法,則直接返回掖肋;
② 如果一級緩存沒有命中仆葡,接下來Session會在當前NonExists記錄(相當于一個查詢黑名單,如果出現(xiàn)重復的無效查詢可以迅速做出判斷志笼,從而提升性能)中進行查找沿盅,如果NonExists中存在同樣的查詢條件,則返回null纫溃;
③ 如果一級緩存查詢失敗則查詢二級緩存飞蛹,如果二級緩存命中則直接返回盼理;
④ 如果之前的查詢都未命中荷鼠,則發(fā)出SQL語句酌呆,如果查詢未發(fā)現(xiàn)對應記錄則將此次查詢添加到Session的NonExists中加以記錄,并返回null坊谁;
⑤ 根據(jù)映射配置和SQL語句得到ResultSet费彼,并創(chuàng)建對應的實體對象;
⑥ 將對象納入Session(一級緩存)的管理口芍;
⑦ 如果有對應的攔截器箍铲,則執(zhí)行攔截器的onLoad方法;
⑧ 如果開啟并設(shè)置了要使用二級緩存鬓椭,則將數(shù)據(jù)對象納入二級緩存颠猴;
⑨ 返回數(shù)據(jù)對象。
132小染、Query接口的list方法和iterate方法有什么區(qū)別芙粱?
答:
① list()方法無法利用一級緩存和二級緩存(對緩存只寫不讀),它只能在開啟查詢緩存的前提下使用查詢緩存氧映;iterate()方法可以充分利用緩存,如果目標數(shù)據(jù)只讀或者讀取頻繁脱货,使用iterate()方法可以減少性能開銷岛都。
② list()方法不會引起N+1查詢問題,而iterate()方法可能引起N+1查詢問題
說明:關(guān)于N+1查詢問題振峻,可以參考CSDN上的一篇文章《什么是N+1查詢》
133臼疫、Hibernate如何實現(xiàn)分頁查詢?
答:通過Hibernate實現(xiàn)分頁查詢扣孟,開發(fā)人員只需要提供HQL語句(調(diào)用Session的createQuery()方法)或查詢條件(調(diào)用Session的createCriteria()方法)烫堤、設(shè)置查詢起始行數(shù)(調(diào)用Query或Criteria接口的setFirstResult()方法)和最大查詢行數(shù)(調(diào)用Query或Criteria接口的setMaxResults()方法),并調(diào)用Query或Criteria接口的list()方法,Hibernate會自動生成分頁查詢的SQL語句鸽斟。
134拔创、鎖機制有什么用?簡述Hibernate的悲觀鎖和樂觀鎖機制富蓄。
答:有些業(yè)務(wù)邏輯在執(zhí)行過程中要求對數(shù)據(jù)進行排他性的訪問剩燥,于是需要通過一些機制保證在此過程中數(shù)據(jù)被鎖住不會被外界修改,這就是所謂的鎖機制立倍。
Hibernate支持悲觀鎖和樂觀鎖兩種鎖機制灭红。悲觀鎖,顧名思義悲觀的認為在數(shù)據(jù)處理過程中極有可能存在修改數(shù)據(jù)的并發(fā)事務(wù)(包括本系統(tǒng)的其他事務(wù)或來自外部系統(tǒng)的事務(wù))口注,于是將處理的數(shù)據(jù)設(shè)置為鎖定狀態(tài)变擒。悲觀鎖必須依賴數(shù)據(jù)庫本身的鎖機制才能真正保證數(shù)據(jù)訪問的排他性,關(guān)于數(shù)據(jù)庫的鎖機制和事務(wù)隔離級別在《Java面試題大全(上)》中已經(jīng)討論過了寝志。樂觀鎖娇斑,顧名思義,對并發(fā)事務(wù)持樂觀態(tài)度(認為對數(shù)據(jù)的并發(fā)操作不會經(jīng)常性的發(fā)生)澈段,通過更加寬松的鎖機制來解決由于悲觀鎖排他性的數(shù)據(jù)訪問對系統(tǒng)性能造成的嚴重影響悠菜。最常見的樂觀鎖是通過數(shù)據(jù)版本標識來實現(xiàn)的,讀取數(shù)據(jù)時獲得數(shù)據(jù)的版本號败富,更新數(shù)據(jù)時將此版本號加1悔醋,然后和數(shù)據(jù)庫表對應記錄的當前版本號進行比較,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫中此記錄的當前版本號則更新數(shù)據(jù)兽叮,否則認為是過期數(shù)據(jù)無法更新芬骄。Hibernate中通過Session的get()和load()方法從數(shù)據(jù)庫中加載對象時可以通過參數(shù)指定使用悲觀鎖;而樂觀鎖可以通過給實體類加整型的版本字段再通過XML或@Version注解進行配置鹦聪。
提示:使用樂觀鎖會增加了一個版本字段账阻,很明顯這需要額外的空間來存儲這個版本字段,浪費了空間泽本,但是樂觀鎖會讓系統(tǒng)具有更好的并發(fā)性淘太,這是對時間的節(jié)省。因此樂觀鎖也是典型的空間換時間的策略规丽。
135蒲牧、闡述實體對象的三種狀態(tài)以及轉(zhuǎn)換關(guān)系。
答:最新的Hibernate文檔中為Hibernate對象定義了四種狀態(tài)(原來是三種狀態(tài)赌莺,面試的時候基本上問的也是三種狀態(tài))冰抢,分別是:瞬時態(tài)(new, or transient)、持久態(tài)(managed, or persistent)艘狭、游狀態(tài)(detached)和移除態(tài)(removed挎扰,以前Hibernate文檔中定義的三種狀態(tài)中沒有移除態(tài))翠订,如下圖所示,就以前的Hibernate文檔中移除態(tài)被視為是瞬時態(tài)遵倦。
瞬時態(tài):當new一個實體對象后尽超,這個對象處于瞬時態(tài),即這個對象只是一個保存臨時數(shù)據(jù)的內(nèi)存區(qū)域骇吭,如果沒有變量引用這個對象橙弱,則會被JVM的垃圾回收機制回收。這個對象所保存的數(shù)據(jù)與數(shù)據(jù)庫沒有任何關(guān)系燥狰,除非通過Session的save()棘脐、saveOrUpdate()、persist()龙致、merge()方法把瞬時態(tài)對象與數(shù)據(jù)庫關(guān)聯(lián)蛀缝,并把數(shù)據(jù)插入或者更新到數(shù)據(jù)庫,這個對象才轉(zhuǎn)換為持久態(tài)對象目代。
持久態(tài):持久態(tài)對象的實例在數(shù)據(jù)庫中有對應的記錄屈梁,并擁有一個持久化標識(ID)。對持久態(tài)對象進行delete操作后榛了,數(shù)據(jù)庫中對應的記錄將被刪除在讶,那么持久態(tài)對象與數(shù)據(jù)庫記錄不再存在對應關(guān)系,持久態(tài)對象變成移除態(tài)(可以視為瞬時態(tài))霜大。持久態(tài)對象被修改變更后构哺,不會馬上同步到數(shù)據(jù)庫,直到數(shù)據(jù)庫事務(wù)提交战坤。
游離態(tài):當Session進行了close()曙强、clear()、evict()或flush()后途茫,實體對象從持久態(tài)變成游離態(tài)碟嘴,對象雖然擁有持久和與數(shù)據(jù)庫對應記錄一致的標識值,但是因為對象已經(jīng)從會話中清除掉囊卜,對象不在持久化管理之內(nèi)娜扇,所以處于游離態(tài)(也叫脫管態(tài))。游離態(tài)的對象與臨時狀態(tài)對象是十分相似的栅组,只是它還含有持久化標識袱衷。
提示:關(guān)于這個問題,在Hibernate的官方文檔中有更為詳細的解讀笑窜。
136、如何理解Hibernate的延遲加載機制登疗?在實際應用中排截,延遲加載與Session關(guān)閉的矛盾是如何處理的嫌蚤?
答:延遲加載就是并不是在讀取的時候就把數(shù)據(jù)加載進來,而是等到使用時再加載断傲。Hibernate使用了虛擬代理機制實現(xiàn)延遲加載脱吱,我們使用Session的load()方法加載數(shù)據(jù)或者一對多關(guān)聯(lián)映射在使用延遲加載的情況下從一的一方加載多的一方,得到的都是虛擬代理认罩,簡單的說返回給用戶的并不是實體本身箱蝠,而是實體對象的代理。代理對象在用戶調(diào)用getter方法時才會去數(shù)據(jù)庫加載數(shù)據(jù)垦垂。但加載數(shù)據(jù)就需要數(shù)據(jù)庫連接宦搬。而當我們把會話關(guān)閉時,數(shù)據(jù)庫連接就同時關(guān)閉了劫拗。
延遲加載與session關(guān)閉的矛盾一般可以這樣處理:
① 關(guān)閉延遲加載特性间校。這種方式操作起來比較簡單,因為Hibernate的延遲加載特性是可以通過映射文件或者注解進行配置的页慷,但這種解決方案存在明顯的缺陷憔足。首先,出現(xiàn)"no session or session was closed"通常說明系統(tǒng)中已經(jīng)存在主外鍵關(guān)聯(lián)酒繁,如果去掉延遲加載的話滓彰,每次查詢的開銷都會變得很大。
② 在session關(guān)閉之前先獲取需要查詢的數(shù)據(jù)州袒,可以使用工具方法Hibernate.isInitialized()判斷對象是否被加載揭绑,如果沒有被加載則可以使用Hibernate.initialize()方法加載對象。
③ 使用攔截器或過濾器延長Session的生命周期直到視圖獲得數(shù)據(jù)稳析。Spring整合Hibernate提供的OpenSessionInViewFilter和OpenSessionInViewInterceptor就是這種做法洗做。
137、舉一個多對多關(guān)聯(lián)的例子彰居,并說明如何實現(xiàn)多對多關(guān)聯(lián)映射诚纸。
答:例如:商品和訂單、學生和課程都是典型的多對多關(guān)系陈惰∑枧牵可以在實體類上通過@ManyToMany注解配置多對多關(guān)聯(lián)或者通過映射文件中的和標簽配置多對多關(guān)聯(lián),但是實際項目開發(fā)中抬闯,很多時候都是將多對多關(guān)聯(lián)映射轉(zhuǎn)換成兩個多對一關(guān)聯(lián)映射來實現(xiàn)的井辆。
138、談一下你對繼承映射的理解溶握。
答:繼承關(guān)系的映射策略有三種:
① 每個繼承結(jié)構(gòu)一張表(table per class hierarchy)杯缺,不管多少個子類都用一張表。
② 每個子類一張表(table per subclass)睡榆,公共信息放一張表萍肆,特有信息放單獨的表袍榆。
③ 每個具體類一張表(table per concrete class),有多少個子類就有多少張表塘揣。
第一種方式屬于單表策略包雀,其優(yōu)點在于查詢子類對象的時候無需表連接,查詢速度快亲铡,適合多態(tài)查詢才写;缺點是可能導致表很大。后兩種方式屬于多表策略奖蔓,其優(yōu)點在于數(shù)據(jù)存儲緊湊赞草,其缺點是需要進行連接查詢,不適合多態(tài)查詢锭硼。
139房资、簡述Hibernate常見優(yōu)化策略。
答:這個問題應當挑自己使用過的優(yōu)化策略回答檀头,常用的有:
① 制定合理的緩存策略(二級緩存轰异、查詢緩存)。
② 采用合理的Session管理機制暑始。
③ 盡量使用延遲加載特性搭独。
④ 設(shè)定合理的批處理參數(shù)。
⑤ 如果可以廊镜,選用UUID作為主鍵生成器牙肝。
⑥ 如果可以,選用基于版本號的樂觀鎖替代悲觀鎖嗤朴。
⑦ 在開發(fā)過程中, 開啟hibernate.show_sql選項查看生成的SQL配椭,從而了解底層的狀況;開發(fā)完成后關(guān)閉此選項雹姊。
⑧ 考慮數(shù)據(jù)庫本身的優(yōu)化股缸,合理的索引、恰當?shù)臄?shù)據(jù)分區(qū)策略等都會對持久層的性能帶來可觀的提升吱雏,但這些需要專業(yè)的DBA(數(shù)據(jù)庫管理員)提供支持敦姻。
140、談一談Hibernate的一級緩存歧杏、二級緩存和查詢緩存镰惦。
答:Hibernate的Session提供了一級緩存的功能,默認總是有效的犬绒,當應用程序保存持久化實體旺入、修改持久化實體時,Session并不會立即把這種改變提交到數(shù)據(jù)庫凯力,而是緩存在當前的Session中眨业,除非顯示調(diào)用了Session的flush()方法或通過close()方法關(guān)閉Session急膀。通過一級緩存,可以減少程序與數(shù)據(jù)庫的交互龄捡,從而提高數(shù)據(jù)庫訪問性能。
SessionFactory級別的二級緩存是全局性的慷暂,所有的Session可以共享這個二級緩存聘殖。不過二級緩存默認是關(guān)閉的,需要顯示開啟并指定需要使用哪種二級緩存實現(xiàn)類(可以使用第三方提供的實現(xiàn))行瑞。一旦開啟了二級緩存并設(shè)置了需要使用二級緩存的實體類奸腺,SessionFactory就會緩存訪問過的該實體類的每個對象,除非緩存的數(shù)據(jù)超出了指定的緩存空間血久。
一級緩存和二級緩存都是對整個實體進行緩存突照,不會緩存普通屬性,如果希望對普通屬性進行緩存氧吐,可以使用查詢緩存讹蘑。查詢緩存是將HQL或SQL語句以及它們的查詢結(jié)果作為鍵值對進行緩存,對于同樣的查詢可以直接從緩存中獲取數(shù)據(jù)筑舅。查詢緩存默認也是關(guān)閉的座慰,需要顯示開啟。
141翠拣、Hibernate中DetachedCriteria類是做什么的版仔?
答:DetachedCriteria和Criteria的用法基本上是一致的,但Criteria是由Session的createCriteria()方法創(chuàng)建的误墓,也就意味著離開創(chuàng)建它的Session蛮粮,Criteria就無法使用了。DetachedCriteria不需要Session就可以創(chuàng)建(使用DetachedCriteria.forClass()方法創(chuàng)建)谜慌,所以通常也稱其為離線的Criteria然想,在需要進行查詢操作的時候再和Session綁定(調(diào)用其getExecutableCriteria(Session)方法),這也就意味著一個DetachedCriteria可以在需要的時候和不同的Session進行綁定畦娄。
142又沾、@OneToMany注解的mappedBy屬性有什么作用?
答:@OneToMany用來配置一對多關(guān)聯(lián)映射熙卡,但通常情況下杖刷,一對多關(guān)聯(lián)映射都由多的一方來維護關(guān)聯(lián)關(guān)系,例如學生和班級驳癌,應該在學生類中添加班級屬性來維持學生和班級的關(guān)聯(lián)關(guān)系(在數(shù)據(jù)庫中是由學生表中的外鍵班級編號來維護學生表和班級表的多對一關(guān)系)滑燃,如果要使用雙向關(guān)聯(lián),在班級類中添加一個容器屬性來存放學生颓鲜,并使用@OneToMany注解進行映射表窘,此時mappedBy屬性就非常重要典予。如果使用XML進行配置,可以用標簽的inverse="true"設(shè)置來達到同樣的效果乐严。
143瘤袖、MyBatis中使用#和$書寫占位符有什么區(qū)別?
答:#將傳入的數(shù)據(jù)都當成一個字符串昂验,會對傳入的數(shù)據(jù)自動加上引號捂敌;$將傳入的數(shù)據(jù)直接顯示生成在SQL中。注意:使用$占位符可能會導致SQL注射攻擊既琴,能用#的地方就不要使用$占婉,寫order by子句的時候應該用$而不是#。
144甫恩、解釋一下MyBatis中命名空間(namespace)的作用逆济。
答:在大型項目中,可能存在大量的SQL語句磺箕,這時候為每個SQL語句起一個唯一的標識(ID)就變得并不容易了奖慌。為了解決這個問題,在MyBatis中滞磺,可以為每個映射文件起一個唯一的命名空間升薯,這樣定義在這個映射文件中的每個SQL語句就成了定義在這個命名空間中的一個ID。只要我們能夠保證每個命名空間中這個ID是唯一的击困,即使在不同映射文件中的語句ID相同涎劈,也不會再產(chǎn)生沖突了。
145阅茶、MyBatis中的動態(tài)SQL是什么意思蛛枚?
答:對于一些復雜的查詢,我們可能會指定多個查詢條件脸哀,但是這些條件可能存在也可能不存在蹦浦,例如在58同城上面找房子,我們可能會指定面積撞蜂、樓層和所在位置來查找房源盲镶,也可能會指定面積、價格蝌诡、戶型和所在位置來查找房源溉贿,此時就需要根據(jù)用戶指定的條件動態(tài)生成SQL語句。如果不使用持久層框架我們可能需要自己拼裝SQL語句浦旱,還好MyBatis提供了動態(tài)SQL的功能來解決這個問題宇色。MyBatis中用于實現(xiàn)動態(tài)SQL的元素主要有:
- if
- choose / when / otherwise
- trim
- where
- set
- foreach
下面是映射文件的片段。
select*fromt_blogwhere1=1andtitle =#{title}andcontent =#{content}andowner =#{owner}
當然也可以像下面這些書寫。
select*fromt_blogwhere1=1andtitle =#{title}andcontent =#{content}andowner ="owner1"
再看看下面這個例子宣蠕。
select*fromt_blogwhereidin#{item}
146例隆、什么是IoC和DI?DI是如何實現(xiàn)的抢蚀?
答:IoC叫控制反轉(zhuǎn)镀层,是Inversion of Control的縮寫,DI(Dependency Injection)叫依賴注入皿曲,是對IoC更簡單的詮釋鹿响。控制反轉(zhuǎn)是把傳統(tǒng)上由程序代碼直接操控的對象的調(diào)用權(quán)交給容器谷饿,通過容器來實現(xiàn)對象組件的裝配和管理。所謂的"控制反轉(zhuǎn)"就是對組件對象控制權(quán)的轉(zhuǎn)移妈倔,從程序代碼本身轉(zhuǎn)移到了外部容器博投,由容器來創(chuàng)建對象并管理對象之間的依賴關(guān)系。IoC體現(xiàn)了好萊塢原則 - "Don’t call me, we will call you"盯蝴。依賴注入的基本原則是應用組件不應該負責查找資源或者其他依賴的協(xié)作對象毅哗。配置對象的工作應該由容器負責,查找資源的邏輯應該從應用組件的代碼中抽取出來捧挺,交給容器來完成虑绵。DI是對IoC更準確的描述,即組件之間的依賴關(guān)系由容器在運行期決定闽烙,形象的來說翅睛,即由容器動態(tài)的將某種依賴關(guān)系注入到組件之中。
舉個例子:一個類A需要用到接口B中的方法黑竞,那么就需要為類A和接口B建立關(guān)聯(lián)或依賴關(guān)系捕发,最原始的方法是在類A中創(chuàng)建一個接口B的實現(xiàn)類C的實例,但這種方法需要開發(fā)人員自行維護二者的依賴關(guān)系很魂,也就是說當依賴關(guān)系發(fā)生變動的時候需要修改代碼并重新構(gòu)建整個系統(tǒng)扎酷。如果通過一個容器來管理這些對象以及對象的依賴關(guān)系,則只需要在類A中定義好用于關(guān)聯(lián)接口B的方法(構(gòu)造器或setter方法)遏匆,將類A和接口B的實現(xiàn)類C放入容器中法挨,通過對容器的配置來實現(xiàn)二者的關(guān)聯(lián)。
依賴注入可以通過setter方法注入(設(shè)值注入)幅聘、構(gòu)造器注入和接口注入三種方式來實現(xiàn)凡纳,Spring支持setter注入和構(gòu)造器注入,通常使用構(gòu)造器注入來注入必須的依賴關(guān)系喊暖,對于可選的依賴關(guān)系惫企,則setter注入是更好的選擇,setter注入需要類提供無參構(gòu)造器或者無參的靜態(tài)工廠方法來創(chuàng)建對象。
147狞尔、Spring中Bean的作用域有哪些丛版?
答:在Spring的早期版本中,僅有兩個作用域:singleton和prototype偏序,前者表示Bean以單例的方式存在页畦;后者表示每次從容器中調(diào)用Bean時,都會返回一個新的實例研儒,prototype通常翻譯為原型豫缨。
補充:設(shè)計模式中的創(chuàng)建型模式中也有一個原型模式,原型模式也是一個常用的模式端朵,例如做一個室內(nèi)設(shè)計軟件好芭,所有的素材都在工具箱中,而每次從工具箱中取出的都是素材對象的一個原型冲呢,可以通過對象克隆來實現(xiàn)原型模式舍败。
Spring 2.x中針對WebApplicationContext新增了3個作用域,分別是:request(每次HTTP請求都會創(chuàng)建一個新的Bean)敬拓、session(同一個HttpSession共享同一個Bean邻薯,不同的HttpSession使用不同的Bean)和globalSession(同一個全局Session共享一個Bean)。
說明:單例模式和原型模式都是重要的設(shè)計模式乘凸。一般情況下厕诡,無狀態(tài)或狀態(tài)不可變的類適合使用單例模式。在傳統(tǒng)開發(fā)中营勤,由于DAO持有Connection這個非線程安全對象因而沒有使用單例模式灵嫌;但在Spring環(huán)境下,所有DAO類對可以采用單例模式冀偶,因為Spring利用AOP和Java API中的ThreadLocal對非線程安全的對象進行了特殊處理质况。
ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路谭企。ThreadLocal,顧名思義是線程的一個本地化對象,當工作于多線程中的對象使用ThreadLocal維護變量時围段,ThreadLocal為每個使用該變量的線程分配一個獨立的變量副本粘驰,所以每一個線程都可以獨立的改變自己的副本坊罢,而不影響其他線程所對應的副本娩脾。從線程的角度看,這個變量就像是線程的本地變量量瓜。
ThreadLocal類非常簡單好用司恳,只有四個方法,能用上的也就是下面三個方法:
- void set(T value):設(shè)置當前線程的線程局部變量的值绍傲。
- T get():獲得當前線程所對應的線程局部變量的值扔傅。
- void remove():刪除當前線程中線程局部變量的值耍共。
ThreadLocal是如何做到為每一個線程維護一份獨立的變量副本的呢?在ThreadLocal類中有一個Map猎塞,鍵為線程對象试读,值是其線程對應的變量的副本,自己要模擬實現(xiàn)一個ThreadLocal類其實并不困難荠耽,代碼如下所示:
importjava.util.Collections;importjava.util.HashMap;importjava.util.Map;publicclassMyThreadLocal{privateMapmap=Collections.synchronizedMap(newHashMap());publicvoidset(T newValue){map.put(Thread.currentThread(), newValue);? ? }publicTget(){returnmap.get(Thread.currentThread());? ? }publicvoidremove(){map.remove(Thread.currentThread());? ? }}
148钩骇、解釋一下什么叫AOP(面向切面編程)?
答:AOP(Aspect-Oriented Programming)指一種程序設(shè)計范型铝量,該范型以一種稱為切面(aspect)的語言構(gòu)造為基礎(chǔ)倘屹,切面是一種新的模塊化機制,用來描述分散在對象慢叨、類或方法中的橫切關(guān)注點(crosscutting concern)纽匙。
149、你是如何理解"橫切關(guān)注"這個概念的拍谐?
答:"橫切關(guān)注"是會影響到整個應用程序的關(guān)注功能哄辣,它跟正常的業(yè)務(wù)邏輯是正交的,沒有必然的聯(lián)系赠尾,但是幾乎所有的業(yè)務(wù)邏輯都會涉及到這些關(guān)注功能。通常毅弧,事務(wù)气嫁、日志、安全性等關(guān)注就是應用中的橫切關(guān)注功能够坐。
150寸宵、你如何理解AOP中的連接點(Joinpoint)、切點(Pointcut)元咙、增強(Advice)梯影、引介(Introduction)、織入(Weaving)庶香、切面(Aspect)這些概念甲棍?
答:
a. 連接點(Joinpoint):程序執(zhí)行的某個特定位置(如:某個方法調(diào)用前、調(diào)用后赶掖,方法拋出異常后)感猛。一個類或一段程序代碼擁有一些具有邊界性質(zhì)的特定點,這些代碼中的特定點就是連接點奢赂。Spring僅支持方法的連接點陪白。
b. 切點(Pointcut):如果連接點相當于數(shù)據(jù)中的記錄,那么切點相當于查詢條件膳灶,一個切點可以匹配多個連接點咱士。Spring AOP的規(guī)則解析引擎負責解析切點所設(shè)定的查詢條件,找到對應的連接點。
c. 增強(Advice):增強是織入到目標類連接點上的一段程序代碼序厉。Spring提供的增強接口都是帶方位名的锐膜,如:BeforeAdvice、AfterReturningAdvice脂矫、ThrowsAdvice等枣耀。很多資料上將增強譯為“通知”,這明顯是個詞不達意的翻譯庭再,讓很多程序員困惑了許久捞奕。
說明:Advice在國內(nèi)的很多書面資料中都被翻譯成"通知",但是很顯然這個翻譯無法表達其本質(zhì)拄轻,有少量的讀物上將這個詞翻譯為"增強"颅围,這個翻譯是對Advice較為準確的詮釋,我們通過AOP將橫切關(guān)注功能加到原有的業(yè)務(wù)邏輯上恨搓,這就是對原有業(yè)務(wù)邏輯的一種增強院促,這種增強可以是前置增強、后置增強斧抱、返回后增強常拓、拋異常時增強和包圍型增強。
d. 引介(Introduction):引介是一種特殊的增強辉浦,它為類添加一些屬性和方法弄抬。這樣,即使一個業(yè)務(wù)類原本沒有實現(xiàn)某個接口宪郊,通過引介功能掂恕,可以動態(tài)的未該業(yè)務(wù)類添加接口的實現(xiàn)邏輯,讓業(yè)務(wù)類成為這個接口的實現(xiàn)類弛槐。
e. 織入(Weaving):織入是將增強添加到目標類具體連接點上的過程懊亡,AOP有三種織入方式:①編譯期織入:需要特殊的Java編譯期(例如AspectJ的ajc);②裝載期織入:要求使用特殊的類加載器乎串,在裝載類的時候?qū)︻愡M行增強店枣;③運行時織入:在運行時為目標類生成代理實現(xiàn)增強。Spring采用了動態(tài)代理的方式實現(xiàn)了運行時織入叹誉,而AspectJ采用了編譯期織入和裝載期織入的方式艰争。
f. 切面(Aspect):切面是由切點和增強(引介)組成的,它包括了對橫切關(guān)注功能的定義桂对,也包括了對連接點的定義甩卓。
補充:代理模式是GoF提出的23種設(shè)計模式中最為經(jīng)典的模式之一,代理模式是對象的結(jié)構(gòu)模式蕉斜,它給某一個對象提供一個代理對象逾柿,并由代理對象控制對原對象的引用缀棍。簡單的說,代理對象可以完成比原對象更多的職責机错,當需要為原對象添加橫切關(guān)注功能時爬范,就可以使用原對象的代理對象。我們在打開Office系列的Word文檔時弱匪,如果文檔中有插圖青瀑,當文檔剛加載時,文檔中的插圖都只是一個虛框占位符萧诫,等用戶真正翻到某頁要查看該圖片時斥难,才會真正加載這張圖,這其實就是對代理模式的使用帘饶,代替真正圖片的虛框就是一個虛擬代理哑诊;Hibernate的load方法也是返回一個虛擬代理對象,等用戶真正需要訪問對象的屬性時及刻,才向數(shù)據(jù)庫發(fā)出SQL語句獲得真實對象镀裤。
下面用一個找槍手代考的例子演示代理模式的使用:
說明:使用Java的動態(tài)代理有一個局限性就是代理的類必須要實現(xiàn)接口,雖然面向接口編程是每個優(yōu)秀的Java程序都知道的規(guī)則缴饭,但現(xiàn)實往往不盡如人意暑劝,對于沒有實現(xiàn)接口的類如何為其生成代理呢?繼承颗搂!繼承是最經(jīng)典的擴展已有代碼能力的手段铃岔,雖然繼承常常被初學者濫用,但繼承也常常被進階的程序員忽視峭火。CGLib采用非常底層的字節(jié)碼生成技術(shù),通過為一個類創(chuàng)建子類來生成代理智嚷,它彌補了Java動態(tài)代理的不足卖丸,因此Spring中動態(tài)代理和CGLib都是創(chuàng)建代理的重要手段,對于實現(xiàn)了接口的類就用動態(tài)代理為其生成代理類盏道,而沒有實現(xiàn)接口的類就用CGLib通過繼承的方式為其創(chuàng)建代理稍浆。
151、Spring中自動裝配的方式有哪些猜嘱?
答:
- no:不進行自動裝配衅枫,手動設(shè)置Bean的依賴關(guān)系。
- byName:根據(jù)Bean的名字進行自動裝配朗伶。
- byType:根據(jù)Bean的類型進行自動裝配弦撩。
- constructor:類似于byType,不過是應用于構(gòu)造器的參數(shù)论皆,如果正好有一個Bean與構(gòu)造器的參數(shù)類型相同則可以自動裝配益楼,否則會導致錯誤猾漫。
- autodetect:如果有默認的構(gòu)造器,則通過constructor的方式進行自動裝配感凤,否則使用byType的方式進行自動裝配悯周。
說明:自動裝配沒有自定義裝配方式那么精確,而且不能自動裝配簡單屬性(基本類型陪竿、字符串等)禽翼,在使用時應注意。
152族跛、Spring中如何使用注解來配置Bean闰挡?有哪些相關(guān)的注解?
答:首先需要在Spring配置文件中增加如下配置:
然后可以用@Component庸蔼、@Controller解总、@Service、@Repository注解來標注需要由Spring IoC容器進行對象托管的類姐仅。這幾個注解沒有本質(zhì)區(qū)別花枫,只不過@Controller通常用于控制器,@Service通常用于業(yè)務(wù)邏輯類掏膏,@Repository通常用于倉儲類(例如我們的DAO實現(xiàn)類)劳翰,普通的類用@Component來標注。
153馒疹、Spring支持的事務(wù)管理類型有哪些佳簸?你在項目中使用哪種方式?
答:Spring支持編程式事務(wù)管理和聲明式事務(wù)管理颖变。許多Spring框架的用戶選擇聲明式事務(wù)管理生均,因為這種方式和應用程序的關(guān)聯(lián)較少,因此更加符合輕量級容器的概念腥刹。聲明式事務(wù)管理要優(yōu)于編程式事務(wù)管理马胧,盡管在靈活性方面它弱于編程式事務(wù)管理,因為編程式事務(wù)允許你通過代碼控制業(yè)務(wù)衔峰。
事務(wù)分為全局事務(wù)和局部事務(wù)佩脊。全局事務(wù)由應用服務(wù)器管理,需要底層服務(wù)器JTA支持(如WebLogic垫卤、WildFly等)威彰。局部事務(wù)和底層采用的持久化方案有關(guān),例如使用JDBC進行持久化時穴肘,需要使用Connetion對象來操作事務(wù)歇盼;而采用Hibernate進行持久化時,需要使用Session對象來操作事務(wù)评抚。
Spring提供了如下所示的事務(wù)管理器旺遮。
事務(wù)管理器實現(xiàn)類目標對象
DataSourceTransactionManager注入DataSource
HibernateTransactionManager注入SessionFactory
JdoTransactionManager管理JDO事務(wù)
JtaTransactionManager使用JTA管理事務(wù)
PersistenceBrokerTransactionManager管理Apache的OJB事務(wù)
這些事務(wù)的父接口都是PlatformTransactionManager赵讯。Spring的事務(wù)管理機制是一種典型的策略模式,PlatformTransactionManager代表事務(wù)管理接口耿眉,該接口定義了三個方法边翼,該接口并不知道底層如何管理事務(wù),但是它的實現(xiàn)類必須提供getTransaction()方法(開啟事務(wù))鸣剪、commit()方法(提交事務(wù))组底、rollback()方法(回滾事務(wù))的多態(tài)實現(xiàn),這樣就可以用不同的實現(xiàn)類代表不同的事務(wù)管理策略筐骇。使用JTA全局事務(wù)策略時债鸡,需要底層應用服務(wù)器支持,而不同的應用服務(wù)器所提供的JTA全局事務(wù)可能存在細節(jié)上的差異铛纬,因此實際配置全局事務(wù)管理器是可能需要使用JtaTransactionManager的子類厌均,如:WebLogicJtaTransactionManager(Oracle的WebLogic服務(wù)器提供)、UowJtaTransactionManager(IBM的WebSphere服務(wù)器提供)等告唆。
編程式事務(wù)管理如下所示棺弊。
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
PropertyPlaceholderConfigurer">jdbc.properties${db.driver}${db.url}${db.username}${db.password}
DataSourceTransactionManager"scope="singleton">
TransactionTemplate">
packagecom.jackfrued.dao.impl;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.jdbc.core.JdbcTemplate;importcom.jackfrued.dao.EmpDao;importcom.jackfrued.entity.Emp;@RepositorypublicclassEmpDaoImplimplementsEmpDao{@AutowiredprivateJdbcTemplate jdbcTemplate;@Overridepublicbooleansave(Emp emp){? ? ? ? String sql ="insert into emp values (?,?,?)";returnjdbcTemplate.update(sql, emp.getId(), emp.getName(), emp.getBirthday()) ==1;? ? }}
packagecom.jackfrued.biz.impl;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.transaction.TransactionStatus;importorg.springframework.transaction.support.TransactionCallbackWithoutResult;importorg.springframework.transaction.support.TransactionTemplate;importcom.jackfrued.biz.EmpService;importcom.jackfrued.dao.EmpDao;importcom.jackfrued.entity.Emp;@ServicepublicclassEmpServiceImplimplementsEmpService{@AutowiredprivateTransactionTemplate txTemplate;@AutowiredprivateEmpDao empDao;@OverridepublicvoidaddEmp(finalEmp emp){? ? ? ? txTemplate.execute(newTransactionCallbackWithoutResult() {@OverrideprotectedvoiddoInTransactionWithoutResult(TransactionStatus txStatus){? ? ? ? ? ? ? ? empDao.save(emp);? ? ? ? ? ? }? ? ? ? });? ? }}
聲明式事務(wù)如下圖所示,以Spring整合Hibernate 3為例擒悬,包括完整的DAO和業(yè)務(wù)邏輯代碼模她。
154、如何在Web項目中配置Spring的IoC容器懂牧?
答:如果需要在Web項目中使用Spring的IoC容器侈净,可以在Web項目配置文件web.xml中做出如下配置:
contextConfigLocationclasspath:applicationContext.xmlorg.springframework.web.context.ContextLoaderListener
155、如何在Web項目中配置Spring MVC僧凤?
答:要使用Spring MVC需要在Web項目配置文件中配置其前端控制器DispatcherServlet畜侦,如下所示:
exampleorg.springframework.web.servlet.DispatcherServlet1example*.html
說明:上面的配置中使用了*.html的后綴映射,這樣做一方面不能夠通過URL推斷采用了何種服務(wù)器端的技術(shù)躯保,另一方面可以欺騙搜索引擎旋膳,因為搜索引擎不會搜索動態(tài)頁面,這種做法稱為偽靜態(tài)化吻氧。
156、Spring MVC的工作原理是怎樣的咏连?
答:Spring MVC的工作原理如下圖所示:
① 客戶端的所有請求都交給前端控制器DispatcherServlet來處理盯孙,它會負責調(diào)用系統(tǒng)的其他模塊來真正處理用戶的請求。
② DispatcherServlet收到請求后祟滴,將根據(jù)請求的信息(包括URL振惰、HTTP協(xié)議方法、請求頭垄懂、請求參數(shù)骑晶、Cookie等)以及HandlerMapping的配置找到處理該請求的Handler(任何一個對象都可以作為請求的Handler)痛垛。
③在這個地方Spring會通過HandlerAdapter對該處理器進行封裝。
④ HandlerAdapter是一個適配器桶蛔,它用統(tǒng)一的接口對各種Handler中的方法進行調(diào)用匙头。
⑤ Handler完成對用戶請求的處理后,會返回一個ModelAndView對象給DispatcherServlet仔雷,ModelAndView顧名思義蹂析,包含了數(shù)據(jù)模型以及相應的視圖的信息。
⑥ ModelAndView的視圖是邏輯視圖碟婆,DispatcherServlet還要借助ViewResolver完成從邏輯視圖到真實視圖對象的解析工作电抚。
⑦ 當?shù)玫秸嬲囊晥D對象后,DispatcherServlet會利用視圖對象對模型數(shù)據(jù)進行渲染竖共。
⑧ 客戶端得到響應蝙叛,可能是一個普通的HTML頁面,也可以是XML或JSON字符串公给,還可以是一張圖片或者一個PDF文件借帘。
157、如何在Spring IoC容器中配置數(shù)據(jù)源妓布?
答:
DBCP配置:
C3P0配置:
提示:DBCP的詳細配置在第153題中已經(jīng)完整的展示過了姻蚓。
158、如何配置配置事務(wù)增強匣沼?
答:
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
of an operation defined by the FooService interface --> definitions here -->
159狰挡、選擇使用Spring框架的原因(Spring框架為企業(yè)級開發(fā)帶來的好處有哪些)?
答:可以從以下幾個方面作答:
- 非侵入式:支持基于POJO的編程模式释涛,不強制性的要求實現(xiàn)Spring框架中的接口或繼承Spring框架中的類加叁。
- IoC容器:IoC容器幫助應用程序管理對象以及對象之間的依賴關(guān)系,對象之間的依賴關(guān)系如果發(fā)生了改變只需要修改配置文件而不是修改代碼唇撬,因為代碼的修改可能意味著項目的重新構(gòu)建和完整的回歸測試它匕。有了IoC容器,程序員再也不需要自己編寫工廠窖认、單例豫柬,這一點特別符合Spring的精神"不要重復的發(fā)明輪子"。
- AOP(面向切面編程):將所有的橫切關(guān)注功能封裝到切面(aspect)中扑浸,通過配置的方式將橫切關(guān)注功能動態(tài)添加到目標代碼上烧给,進一步實現(xiàn)了業(yè)務(wù)邏輯和系統(tǒng)服務(wù)之間的分離。另一方面喝噪,有了AOP程序員可以省去很多自己寫代理類的工作础嫡。
- MVC:Spring的MVC框架是非常優(yōu)秀的,從各個方面都可以甩Struts 2幾條街,為Web表示層提供了更好的解決方案榴鼎。
- 事務(wù)管理:Spring以寬廣的胸懷接納多種持久層技術(shù)伯诬,并且為其提供了聲明式的事務(wù)管理,在不需要任何一行代碼的情況下就能夠完成事務(wù)管理巫财。
- 其他:選擇Spring框架的原因還遠不止于此盗似,Spring為Java企業(yè)級開發(fā)提供了一站式選擇,你可以在需要的時候使用它的部分和全部翁涤,更重要的是桥言,你甚至可以在感覺不到Spring存在的情況下,在你的項目中使用Spring提供的各種優(yōu)秀的功能葵礼。
160号阿、Spring IoC容器配置Bean的方式?
答:
- 基于XML文件進行配置鸳粉。
- 基于注解進行配置扔涧。
- 基于Java程序進行配置(Spring 3+)
packagecom.jackfrued.bean;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;@ComponentpublicclassPerson{privateString name;privateintage;@AutowiredprivateCar car;publicPerson(String name,intage){this.name = name;this.age = age;? ? }publicvoidsetCar(Car car){this.car = car;? ? }@OverridepublicStringtoString(){return"Person [name="+ name +", age="+ age +", car="+ car +"]";? ? }}
packagecom.jackfrued.bean;importorg.springframework.stereotype.Component;@ComponentpublicclassCar{privateString brand;privateintmaxSpeed;publicCar(String brand,intmaxSpeed){this.brand = brand;this.maxSpeed = maxSpeed;? ? }@OverridepublicStringtoString(){return"Car [brand="+ brand +", maxSpeed="+ maxSpeed +"]";? ? }}
packagecom.jackfrued.config;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importcom.jackfrued.bean.Car;importcom.jackfrued.bean.Person;@ConfigurationpublicclassAppConfig{@BeanpublicCarcar(){returnnewCar("Benz",320);? ? }@BeanpublicPersonperson(){returnnewPerson("駱昊",34);? ? }}
packagecom.jackfrued.test;importorg.springframework.context.ConfigurableApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;importcom.jackfrued.bean.Person;importcom.jackfrued.config.AppConfig;classTest{publicstaticvoidmain(String[] args){// TWR (Java7+)try(ConfigurableApplicationContext factory =newAnnotationConfigApplicationContext(AppConfig.class)) {? ? ? ? ? ? Person person = factory.getBean(Person.class);System.out.println(person);}? ? }}
161、闡述Spring框架中Bean的生命周期届谈?
答:
① Spring IoC容器找到關(guān)于Bean的定義并實例化該Bean枯夜。
② Spring IoC容器對Bean進行依賴注入。
③ 如果Bean實現(xiàn)了BeanNameAware接口艰山,則將該Bean的id傳給setBeanName方法湖雹。
④ 如果Bean實現(xiàn)了BeanFactoryAware接口,則將BeanFactory對象傳給setBeanFactory方法曙搬。
⑤ 如果Bean實現(xiàn)了BeanPostProcessor接口摔吏,則調(diào)用其postProcessBeforeInitialization方法。
⑥ 如果Bean實現(xiàn)了InitializingBean接口纵装,則調(diào)用其afterPropertySet方法征讲。
⑦ 如果有和Bean關(guān)聯(lián)的BeanPostProcessors對象,則這些對象的postProcessAfterInitialization方法被調(diào)用橡娄。
⑧ 當銷毀Bean實例時诗箍,如果Bean實現(xiàn)了DisposableBean接口,則調(diào)用其destroy方法挽唉。
162滤祖、依賴注入時如何注入集合屬性?
答:可以在定義Bean屬性時瓶籽,通過 / / / 分別為其注入列表匠童、集合、映射和鍵值都是字符串的映射屬性棘劣。
163俏让、Spring中的自動裝配有哪些限制楞遏?
答:
- 如果使用了構(gòu)造器注入或者setter注入茬暇,那么將覆蓋自動裝配的依賴關(guān)系首昔。
- 基本數(shù)據(jù)類型的值、字符串字面量糙俗、類字面量無法使用自動裝配來注入勒奇。
- 優(yōu)先考慮使用顯式的裝配來進行更精確的依賴注入而不是使用自動裝配。
164巧骚、在Web項目中如何獲得Spring的IoC容器赊颠?
答:
WebApplicationContext ctx=WebApplicationContextUtils.getWebApplicationContext(servletContext);
165. 大型網(wǎng)站在架構(gòu)上應當考慮哪些問題?
答:
- 分層:分層是處理任何復雜系統(tǒng)最常見的手段之一劈彪,將系統(tǒng)橫向切分成若干個層面竣蹦,每個層面只承擔單一的職責,然后通過下層為上層提供的基礎(chǔ)設(shè)施和服務(wù)以及上層對下層的調(diào)用來形成一個完整的復雜的系統(tǒng)沧奴。計算機網(wǎng)絡(luò)的開放系統(tǒng)互聯(lián)參考模型(OSI/RM)和Internet的TCP/IP模型都是分層結(jié)構(gòu)痘括,大型網(wǎng)站的軟件系統(tǒng)也可以使用分層的理念將其分為持久層(提供數(shù)據(jù)存儲和訪問服務(wù))、業(yè)務(wù)層(處理業(yè)務(wù)邏輯滔吠,系統(tǒng)中最核心的部分)和表示層(系統(tǒng)交互纲菌、視圖展示)。需要指出的是:(1)分層是邏輯上的劃分疮绷,在物理上可以位于同一設(shè)備上也可以在不同的設(shè)備上部署不同的功能模塊翰舌,這樣可以使用更多的計算資源來應對用戶的并發(fā)訪問;(2)層與層之間應當有清晰的邊界冬骚,這樣分層才有意義椅贱,才更利于軟件的開發(fā)和維護。
- 分割:分割是對軟件的縱向切分唉韭。我們可以將大型網(wǎng)站的不同功能和服務(wù)分割開夜涕,形成高內(nèi)聚低耦合的功能模塊(單元)。在設(shè)計初期可以做一個粗粒度的分割属愤,將網(wǎng)站分割為若干個功能模塊女器,后期還可以進一步對每個模塊進行細粒度的分割,這樣一方面有助于軟件的開發(fā)和維護住诸,另一方面有助于分布式的部署驾胆,提供網(wǎng)站的并發(fā)處理能力和功能的擴展。
- 分布式:除了上面提到的內(nèi)容贱呐,網(wǎng)站的靜態(tài)資源(JavaScript丧诺、CSS、圖片等)也可以采用獨立分布式部署并采用獨立的域名奄薇,這樣可以減輕應用服務(wù)器的負載壓力驳阎,也使得瀏覽器對資源的加載更快。數(shù)據(jù)的存取也應該是分布式的,傳統(tǒng)的商業(yè)級關(guān)系型數(shù)據(jù)庫產(chǎn)品基本上都支持分布式部署呵晚,而新生的NoSQL產(chǎn)品幾乎都是分布式的蜘腌。當然,網(wǎng)站后臺的業(yè)務(wù)處理也要使用分布式技術(shù)饵隙,例如查詢索引的構(gòu)建撮珠、數(shù)據(jù)分析等,這些業(yè)務(wù)計算規(guī)模龐大金矛,可以使用Hadoop以及MapReduce分布式計算框架來處理芯急。
- 集群:集群使得有更多的服務(wù)器提供相同的服務(wù),可以更好的提供對并發(fā)的支持驶俊。
- 緩存:所謂緩存就是用空間換取時間的技術(shù)娶耍,將數(shù)據(jù)盡可能放在距離計算最近的位置。使用緩存是網(wǎng)站優(yōu)化的第一定律饼酿。我們通常說的CDN伺绽、反向代理、熱點數(shù)據(jù)都是對緩存技術(shù)的使用嗜湃。
- 異步:異步是實現(xiàn)軟件實體之間解耦合的又一重要手段奈应。異步架構(gòu)是典型的生產(chǎn)者消費者模式,二者之間沒有直接的調(diào)用關(guān)系购披,只要保持數(shù)據(jù)結(jié)構(gòu)不變杖挣,彼此功能實現(xiàn)可以隨意變化而不互相影響,這對網(wǎng)站的擴展非常有利刚陡。使用異步處理還可以提高系統(tǒng)可用性惩妇,加快網(wǎng)站的響應速度(用Ajax加載數(shù)據(jù)就是一種異步技術(shù)),同時還可以起到削峰作用(應對瞬時高并發(fā))筐乳。"歌殃;能推遲處理的都要推遲處理"是網(wǎng)站優(yōu)化的第二定律,而異步是踐行網(wǎng)站優(yōu)化第二定律的重要手段蝙云。
- 冗余:各種服務(wù)器都要提供相應的冗余服務(wù)器以便在某臺或某些服務(wù)器宕機時還能保證網(wǎng)站可以正常工作氓皱,同時也提供了災難恢復的可能性。冗余是網(wǎng)站高可用性的重要保證勃刨。
166波材、你用過的網(wǎng)站前端優(yōu)化的技術(shù)有哪些?
答:
① 瀏覽器訪問優(yōu)化:
- 減少HTTP請求數(shù)量:合并CSS身隐、合并JavaScript廷区、合并圖片(CSS Sprite)
- 使用瀏覽器緩存:通過設(shè)置HTTP響應頭中的Cache-Control和Expires屬性,將CSS贾铝、JavaScript隙轻、圖片等在瀏覽器中緩存埠帕,當這些靜態(tài)資源需要更新時,可以更新HTML文件中的引用來讓瀏覽器重新請求新的資源
- 啟用壓縮
- CSS前置玖绿,JavaScript后置
- 減少Cookie傳輸
② CDN加速:CDN(Content Distribute Network)的本質(zhì)仍然是緩存搞监,將數(shù)據(jù)緩存在離用戶最近的地方,CDN通常部署在網(wǎng)絡(luò)運營商的機房镰矿,不僅可以提升響應速度,還可以減少應用服務(wù)器的壓力俘种。當然秤标,CDN緩存的通常都是靜態(tài)資源。
③ 反向代理:反向代理相當于應用服務(wù)器的一個門面宙刘,可以保護網(wǎng)站的安全性苍姜,也可以實現(xiàn)負載均衡的功能,當然最重要的是它緩存了用戶訪問的熱點資源悬包,可以直接從反向代理將某些內(nèi)容返回給用戶瀏覽器衙猪。
167、你使用過的應用服務(wù)器優(yōu)化技術(shù)有哪些布近?
答:
① 分布式緩存:緩存的本質(zhì)就是內(nèi)存中的哈希表垫释,如果設(shè)計一個優(yōu)質(zhì)的哈希函數(shù),那么理論上哈希表讀寫的漸近時間復雜度為O(1)撑瞧。緩存主要用來存放那些讀寫比很高棵譬、變化很少的數(shù)據(jù),這樣應用程序讀取數(shù)據(jù)時先到緩存中讀取预伺,如果沒有或者數(shù)據(jù)已經(jīng)失效再去訪問數(shù)據(jù)庫或文件系統(tǒng)订咸,并根據(jù)擬定的規(guī)則將數(shù)據(jù)寫入緩存。對網(wǎng)站數(shù)據(jù)的訪問也符合二八定律(Pareto分布酬诀,冪律分布)脏嚷,即80%的訪問都集中在20%的數(shù)據(jù)上,如果能夠?qū)⑦@20%的數(shù)據(jù)緩存起來瞒御,那么系統(tǒng)的性能將得到顯著的改善父叙。當然,使用緩存需要解決以下幾個問題:
- 頻繁修改的數(shù)據(jù)肴裙;
- 數(shù)據(jù)不一致與臟讀高每;
- 緩存雪崩(可以采用分布式緩存服務(wù)器集群加以解決,memcached是廣泛采用的解決方案)践宴;
- 緩存預熱鲸匿;
- 緩存穿透(惡意持續(xù)請求不存在的數(shù)據(jù))。
② 異步操作:可以使用消息隊列將調(diào)用異步化阻肩,通過異步處理將短時間高并發(fā)產(chǎn)生的事件消息存儲在消息隊列中带欢,從而起到削峰作用运授。電商網(wǎng)站在進行促銷活動時,可以將用戶的訂單請求存入消息隊列乔煞,這樣可以抵御大量的并發(fā)訂單請求對系統(tǒng)和數(shù)據(jù)庫的沖擊吁朦。目前二汛,絕大多數(shù)的電商網(wǎng)站即便不進行促銷活動诵冒,訂單系統(tǒng)都采用了消息隊列來處理。
③ 使用集群剃诅。
④ 代碼優(yōu)化:
- 多線程:基于Java的Web開發(fā)基本上都通過多線程的方式響應用戶的并發(fā)請求空骚,使用多線程技術(shù)在編程上要解決線程安全問題纺讲,主要可以考慮以下幾個方面:A. 將對象設(shè)計為無狀態(tài)對象(這和面向?qū)ο蟮木幊逃^點是矛盾的,在面向?qū)ο蟮氖澜缰斜灰暈椴涣荚O(shè)計)囤屹,這樣就不會存在并發(fā)訪問時對象狀態(tài)不一致的問題熬甚。B. 在方法內(nèi)部創(chuàng)建對象,這樣對象由進入方法的線程創(chuàng)建肋坚,不會出現(xiàn)多個線程訪問同一對象的問題乡括。使用ThreadLocal將對象與線程綁定也是很好的做法,這一點在前面已經(jīng)探討過了智厌。C. 對資源進行并發(fā)訪問時應當使用合理的鎖機制诲泌。
- 非阻塞I/O: 使用單線程和非阻塞I/O是目前公認的比多線程的方式更能充分發(fā)揮服務(wù)器性能的應用模式,基于Node.js構(gòu)建的服務(wù)器就采用了這樣的方式铣鹏。Java在JDK 1.4中就引入了NIO(Non-blocking I/O),在Servlet 3規(guī)范中又引入了異步Servlet的概念档礁,這些都為在服務(wù)器端采用非阻塞I/O提供了必要的基礎(chǔ)。
- 資源復用:資源復用主要有兩種方式吝沫,一是單例呻澜,二是對象池,我們使用的數(shù)據(jù)庫連接池惨险、線程池都是對象池化技術(shù)羹幸,這是典型的用空間換取時間的策略,另一方面也實現(xiàn)對資源的復用辫愉,從而避免了不必要的創(chuàng)建和釋放資源所帶來的開銷栅受。
168、什么是XSS攻擊恭朗?什么是SQL注入攻擊屏镊?什么是CSRF攻擊?
答:
- XSS(Cross Site Script痰腮,跨站腳本攻擊)是向網(wǎng)頁中注入惡意腳本在用戶瀏覽網(wǎng)頁時在用戶瀏覽器中執(zhí)行惡意腳本的攻擊方式而芥。跨站腳本攻擊分有兩種形式:反射型攻擊(誘使用戶點擊一個嵌入惡意腳本的鏈接以達到攻擊的目標膀值,目前有很多攻擊者利用論壇棍丐、微博發(fā)布含有惡意腳本的URL就屬于這種方式)和持久型攻擊(將惡意腳本提交到被攻擊網(wǎng)站的數(shù)據(jù)庫中误辑,用戶瀏覽網(wǎng)頁時,惡意腳本從數(shù)據(jù)庫中被加載到頁面執(zhí)行歌逢,QQ郵箱的早期版本就曾經(jīng)被利用作為持久型跨站腳本攻擊的平臺)巾钉。XSS雖然不是什么新鮮玩意,但是攻擊的手法卻不斷翻新秘案,防范XSS主要有兩方面:消毒(對危險字符進行轉(zhuǎn)義)和HttpOnly(防范XSS攻擊者竊取Cookie數(shù)據(jù))砰苍。
- SQL注入攻擊是注入攻擊最常見的形式(此外還有OS注入攻擊(Struts 2的高危漏洞就是通過OGNL實施OS注入攻擊導致的)),當服務(wù)器使用請求參數(shù)構(gòu)造SQL語句時阱高,惡意的SQL被嵌入到SQL中交給數(shù)據(jù)庫執(zhí)行赚导。SQL注入攻擊需要攻擊者對數(shù)據(jù)庫結(jié)構(gòu)有所了解才能進行,攻擊者想要獲得表結(jié)構(gòu)有多種方式:(1)如果使用開源系統(tǒng)搭建網(wǎng)站讨惩,數(shù)據(jù)庫結(jié)構(gòu)也是公開的(目前有很多現(xiàn)成的系統(tǒng)可以直接搭建論壇,電商網(wǎng)站寒屯,雖然方便快捷但是風險是必須要認真評估的)荐捻;(2)錯誤回顯(如果將服務(wù)器的錯誤信息直接顯示在頁面上,攻擊者可以通過非法參數(shù)引發(fā)頁面錯誤從而通過錯誤信息了解數(shù)據(jù)庫結(jié)構(gòu)寡夹,Web應用應當設(shè)置友好的錯誤頁处面,一方面符合最小驚訝原則,一方面屏蔽掉可能給系統(tǒng)帶來危險的錯誤回顯信息)菩掏;(3)盲注魂角。防范SQL注入攻擊也可以采用消毒的方式,通過正則表達式對請求參數(shù)進行驗證智绸,此外野揪,參數(shù)綁定也是很好的手段,這樣惡意的SQL會被當做SQL的參數(shù)而不是命令被執(zhí)行瞧栗,JDBC中的PreparedStatement就是支持參數(shù)綁定的語句對象斯稳,從性能和安全性上都明顯優(yōu)于Statement。
- CSRF攻擊(Cross Site Request Forgery迹恐,跨站請求偽造)是攻擊者通過跨站請求挣惰,以合法的用戶身份進行非法操作(如轉(zhuǎn)賬或發(fā)帖等)。CSRF的原理是利用瀏覽器的Cookie或服務(wù)器的Session殴边,盜取用戶身份憎茂,其原理如下圖所示。防范CSRF的主要手段是識別請求者的身份锤岸,主要有以下幾種方式:(1)在表單中添加令牌(token)竖幔;(2)驗證碼;(3)檢查請求頭中的Referer(前面提到防圖片盜鏈接也是用的這種方式)是偷。令牌和驗證都具有一次消費性的特征赏枚,因此在原理上一致的亡驰,但是驗證碼是一種糟糕的用戶體驗,不是必要的情況下不要輕易使用驗證碼饿幅,目前很多網(wǎng)站的做法是如果在短時間內(nèi)多次提交一個表單未獲得成功后才要求提供驗證碼凡辱,這樣會獲得較好的用戶體驗。
補充:防火墻的架設(shè)是Web安全的重要保障栗恩,ModSecurity是開源的Web防火墻中的佼佼者透乾。企業(yè)級防火墻的架設(shè)應當有兩級防火墻,Web服務(wù)器和部分應用服務(wù)器可以架設(shè)在兩級防火墻之間的DMZ磕秤,而數(shù)據(jù)和資源服務(wù)器應當架設(shè)在第二級防火墻之后乳乌。
169. 什么是領(lǐng)域模型(domain model)?貧血模型(anaemic domain model)和充血模型(rich domain model)有什么區(qū)別市咆?
答:領(lǐng)域模型是領(lǐng)域內(nèi)的概念類或現(xiàn)實世界中對象的可視化表示汉操,又稱為概念模型或分析對象模型,它專注于分析問題領(lǐng)域本身蒙兰,發(fā)掘重要的業(yè)務(wù)領(lǐng)域概念磷瘤,并建立業(yè)務(wù)領(lǐng)域概念之間的關(guān)系。貧血模型是指使用的領(lǐng)域?qū)ο笾兄挥衧etter和getter方法(POJO)搜变,所有的業(yè)務(wù)邏輯都不包含在領(lǐng)域?qū)ο笾卸欠旁跇I(yè)務(wù)邏輯層采缚。有人將我們這里說的貧血模型進一步劃分成失血模型(領(lǐng)域?qū)ο笸耆珱]有業(yè)務(wù)邏輯)和貧血模型(領(lǐng)域?qū)ο笥猩倭康臉I(yè)務(wù)邏輯),我們這里就不對此加以區(qū)分了挠他。充血模型將大多數(shù)業(yè)務(wù)邏輯和持久化放在領(lǐng)域?qū)ο笾邪獬椋瑯I(yè)務(wù)邏輯(業(yè)務(wù)門面)只是完成對業(yè)務(wù)邏輯的封裝、事務(wù)和權(quán)限等的處理殖侵。下面兩張圖分別展示了貧血模型和充血模型的分層架構(gòu)贸呢。
貧血模型
充血模型
貧血模型下組織領(lǐng)域邏輯通常使用事務(wù)腳本模式,讓每個過程對應用戶可能要做的一個動作拢军,每個動作由一個過程來驅(qū)動贮尉。也就是說在設(shè)計業(yè)務(wù)邏輯接口的時候,每個方法對應著用戶的一個操作朴沿,這種模式有以下幾個有點:
- 它是一個大多數(shù)開發(fā)者都能夠理解的簡單過程模型(適合國內(nèi)的絕大多數(shù)開發(fā)者)猜谚。
- 它能夠與一個使用行數(shù)據(jù)入口或表數(shù)據(jù)入口的簡單數(shù)據(jù)訪問層很好的協(xié)作。
- 事務(wù)邊界的顯而易見赌渣,一個事務(wù)開始于腳本的開始魏铅,終止于腳本的結(jié)束,很容易通過代理(或切面)實現(xiàn)聲明式事務(wù)坚芜。
然而览芳,事務(wù)腳本模式的缺點也是很多的,隨著領(lǐng)域邏輯復雜性的增加鸿竖,系統(tǒng)的復雜性將迅速增加沧竟,程序結(jié)構(gòu)將變得極度混亂铸敏。開源中國社區(qū)上有一篇很好的譯文《貧血領(lǐng)域模型是如何導致糟糕的軟件產(chǎn)生》對這個問題做了比較細致的闡述。
170. 談一談測試驅(qū)動開發(fā)(TDD)的好處以及你的理解悟泵。
答:TDD是指在編寫真正的功能實現(xiàn)代碼之前先寫測試代碼杈笔,然后根據(jù)需要重構(gòu)實現(xiàn)代碼。在JUnit的作者Kent Beck的大作《測試驅(qū)動開發(fā):實戰(zhàn)與模式解析》(Test-Driven Development: by Example)一書中有這么一段內(nèi)容:“消除恐懼和不確定性是編寫測試驅(qū)動代碼的重要原因”糕非。因為編寫代碼時的恐懼會讓你小心試探蒙具,讓你回避溝通,讓你羞于得到反饋朽肥,讓你變得焦躁不安禁筏,而TDD是消除恐懼、讓Java開發(fā)者更加自信更加樂于溝通的重要手段衡招。TDD會帶來的好處可能不會馬上呈現(xiàn)篱昔,但是你在某個時候一定會發(fā)現(xiàn),這些好處包括:
- 更清晰的代碼 — 只寫需要的代碼
- 更好的設(shè)計
- 更出色的靈活性 — 鼓勵程序員面向接口編程
- 更快速的反饋 — 不會到系統(tǒng)上線時才知道bug的存在
補充:敏捷軟件開發(fā)的概念已經(jīng)有很多年了始腾,而且也部分的改變了軟件開發(fā)這個行業(yè)州刽,TDD也是敏捷開發(fā)所倡導的。
TDD可以在多個層級上應用窘茁,包括單元測試(測試一個類中的代碼)怀伦、集成測試(測試類之間的交互)脆烟、系統(tǒng)測試(測試運行的系統(tǒng))和系統(tǒng)集成測試(測試運行的系統(tǒng)包括使用的第三方組件)山林。TDD的實施步驟是:紅(失敗測試)- 綠(通過測試) - 重構(gòu)。關(guān)于實施TDD的詳細步驟請參考另一篇文章《測試驅(qū)動開發(fā)之初窺門徑》邢羔。
在使用TDD開發(fā)時驼抹,經(jīng)常會遇到需要被測對象需要依賴其他子系統(tǒng)的情況,但是你希望將測試代碼跟依賴項隔離拜鹤,以保證測試代碼僅僅針對當前被測對象或方法展開框冀,這時候你需要的是測試替身。測試替身可以分為四類:
- 虛設(shè)替身:只傳遞但是不會使用到的對象敏簿,一般用于填充方法的參數(shù)列表
- 存根替身:總是返回相同的預設(shè)響應明也,其中可能包括一些虛設(shè)狀態(tài)
- 偽裝替身:可以取代真實版本的可用版本(比真實版本還是會差很多)
- 模擬替身:可以表示一系列期望值的對象,并且可以提供預設(shè)響應
Java世界中實現(xiàn)模擬替身的第三方工具非常多惯裕,包括EasyMock温数、Mockito、jMock等蜻势。