JAVA知識點集錦(下)

這部分主要是開源Java EE框架方面的內容,包括Hibernate、MyBatis、Spring、Spring MVC等,由于Struts 2已經是明日黃花,在這里就不討論Struts 2的面試題,如果需要了解相關內容,可以參考我的另一篇文章《Java面試題集(86-115)》。此外,這篇文章還對企業應用架構、大型網站架構和應用服務器優化等內容進行了簡單的探討,這些內容相信對面試會很有幫助。

126、什么是ORM?

答:對象關系映射(Object-Relational Mapping,簡稱ORM)是一種為了解決程序的面向對象模型與數據庫的關系模型互不匹配問題的技術;簡單的說,ORM是通過使用描述對象和數據庫之間映射的元數據(在Java中可以用XML或者是注解),將程序中的對象自動持久化到關系數據庫中或者將關系數據庫表中的行轉換成Java對象,其本質上就是將數據從一種形式轉換到另外一種形式。

127、持久層設計要考慮的問題有哪些?你用過的持久層框架有哪些?

答:所謂"持久"就是將數據保存到可掉電式存儲設備中以便今后使用,簡單的說,就是將內存中的數據保存到關系型數據庫、文件系統、消息隊列等提供持久化支持的設備中。持久層就是系統中專注于實現數據持久化的相對獨立的層面。

持久層設計的目標包括:

- 數據存儲邏輯的分離,提供抽象化的數據訪問接口。

- 數據訪問底層實現的分離,可以在不修改代碼的情況下切換底層實現。

- 資源管理和調度的分離,在數據訪問層實現統一的資源調度(如緩存機制)。

- 數據抽象,提供更面向對象的數據操作。

持久層框架有:

-Hibernate

-MyBatis

-TopLink

-Guzz

-jOOQ

-Spring Data

-ActiveJDBC

128、Hibernate中SessionFactory是線程安全的嗎?Session是線程安全的嗎(兩個線程能夠共享同一個Session嗎)?

答:SessionFactory對應Hibernate的一個數據存儲的概念,它是線程安全的,可以被多個線程并發訪問。SessionFactory一般只會在啟動的時候構建。對于應用程序,最好將SessionFactory通過單例模式進行封裝以便于訪問。Session是一個輕量級非線程安全的對象(線程間不能共享session),它表示與數據庫進行交互的一個工作單元。Session是由SessionFactory創建的,在任務完成之后它會被關閉。Session是持久層服務對外提供的主要接口。Session會延遲獲取數據庫連接(也就是在需要的時候才會獲?。?。為了避免創建太多的session,可以使用ThreadLocal將session和當前線程綁定在一起,這樣可以讓同一個線程獲得的總是同一個session。Hibernate 3中SessionFactory的getCurrentSession()方法就可以做到。

129、Hibernate中Session的load和get方法的區別是什么?

答:主要有以下三項區別:

① 如果沒有找到符合條件的記錄,get方法返回null,load方法拋出異常。

② get方法直接返回實體類對象,load方法返回實體類對象的代理。

③ 在Hibernate 3之前,get方法只在一級緩存中進行數據查找,如果沒有找到對應的數據則越過二級緩存,直接發出SQL語句完成數據讀?。籰oad方法則可以從二級緩存中獲取數據;從Hibernate 3開始,get方法不再是對二級緩存只寫不讀,它也是可以訪問二級緩存的。

說明:對于load()方法Hibernate認為該數據在數據庫中一定存在可以放心的使用代理來實現延遲加載,如果沒有數據就拋出異常,而通過get()方法獲取的數據可以不存在。

130、Session的save()、update()、merge()、lock()、saveOrUpdate()和persist()方法分別是做什么的?有什么區別?

答:Hibernate的對象有三種狀態:瞬時態(transient)、持久態(persistent)和游離態(detached),如第135題中的圖所示。瞬時態的實例可以通過調用save()、persist()或者saveOrUpdate()方法變成持久態;游離態的實例可以通過調用 update()、saveOrUpdate()、lock()或者replicate()變成持久態。save()和persist()將會引發SQL的INSERT語句,而update()或merge()會引發UPDATE語句。save()和update()的區別在于一個是將瞬時態對象變成持久態,一個是將游離態對象變為持久態。merge()方法可以完成save()和update()方法的功能,它的意圖是將新的狀態合并到已有的持久化對象上或創建新的持久化對象。對于persist()方法,按照官方文檔的說明:① persist()方法把一個瞬時態的實例持久化,但是并不保證標識符被立刻填入到持久化實例中,標識符的填入可能被推遲到flush的時間;② persist()方法保證當它在一個事務外部被調用的時候并不觸發一個INSERT語句,當需要封裝一個長會話流程的時候,persist()方法是很有必要的;③ save()方法不保證第②條,它要返回標識符,所以它會立即執行INSERT語句,不管是在事務內部還是外部。至于lock()方法和update()方法的區別,update()方法是把一個已經更改過的脫管狀態的對象變成持久狀態;lock()方法是把一個沒有更改過的脫管狀態的對象變成持久狀態。

131、闡述Session加載實體對象的過程。

答:Session加載實體對象的步驟是:

① Session在調用數據庫查詢功能之前,首先會在一級緩存中通過實體類型和主鍵進行查找,如果一級緩存查找命中且數據狀態合法,則直接返回;

② 如果一級緩存沒有命中,接下來Session會在當前NonExists記錄(相當于一個查詢黑名單,如果出現重復的無效查詢可以迅速做出判斷,從而提升性能)中進行查找,如果NonExists中存在同樣的查詢條件,則返回null;

③ 如果一級緩存查詢失敗則查詢二級緩存,如果二級緩存命中則直接返回;

④ 如果之前的查詢都未命中,則發出SQL語句,如果查詢未發現對應記錄則將此次查詢添加到Session的NonExists中加以記錄,并返回null;

⑤ 根據映射配置和SQL語句得到ResultSet,并創建對應的實體對象;

⑥ 將對象納入Session(一級緩存)的管理;

⑦ 如果有對應的攔截器,則執行攔截器的onLoad方法;

⑧ 如果開啟并設置了要使用二級緩存,則將數據對象納入二級緩存;

⑨ 返回數據對象。

132、Query接口的list方法和iterate方法有什么區別?

答:

① list()方法無法利用一級緩存和二級緩存(對緩存只寫不讀),它只能在開啟查詢緩存的前提下使用查詢緩存;iterate()方法可以充分利用緩存,如果目標數據只讀或者讀取頻繁,使用iterate()方法可以減少性能開銷。

② list()方法不會引起N+1查詢問題,而iterate()方法可能引起N+1查詢問題

說明:關于N+1查詢問題,可以參考CSDN上的一篇文章《什么是N+1查詢》

133、Hibernate如何實現分頁查詢?

答:通過Hibernate實現分頁查詢,開發人員只需要提供HQL語句(調用Session的createQuery()方法)或查詢條件(調用Session的createCriteria()方法)、設置查詢起始行數(調用Query或Criteria接口的setFirstResult()方法)和最大查詢行數(調用Query或Criteria接口的setMaxResults()方法),并調用Query或Criteria接口的list()方法,Hibernate會自動生成分頁查詢的SQL語句。

134、鎖機制有什么用?簡述Hibernate的悲觀鎖和樂觀鎖機制。

答:有些業務邏輯在執行過程中要求對數據進行排他性的訪問,于是需要通過一些機制保證在此過程中數據被鎖住不會被外界修改,這就是所謂的鎖機制。

Hibernate支持悲觀鎖和樂觀鎖兩種鎖機制。悲觀鎖,顧名思義悲觀的認為在數據處理過程中極有可能存在修改數據的并發事務(包括本系統的其他事務或來自外部系統的事務),于是將處理的數據設置為鎖定狀態。悲觀鎖必須依賴數據庫本身的鎖機制才能真正保證數據訪問的排他性,關于數據庫的鎖機制和事務隔離級別在《Java面試題大全(上)》中已經討論過了。樂觀鎖,顧名思義,對并發事務持樂觀態度(認為對數據的并發操作不會經常性的發生),通過更加寬松的鎖機制來解決由于悲觀鎖排他性的數據訪問對系統性能造成的嚴重影響。最常見的樂觀鎖是通過數據版本標識來實現的,讀取數據時獲得數據的版本號,更新數據時將此版本號加1,然后和數據庫表對應記錄的當前版本號進行比較,如果提交的數據版本號大于數據庫中此記錄的當前版本號則更新數據,否則認為是過期數據無法更新。Hibernate中通過Session的get()和load()方法從數據庫中加載對象時可以通過參數指定使用悲觀鎖;而樂觀鎖可以通過給實體類加整型的版本字段再通過XML或@Version注解進行配置。

提示:使用樂觀鎖會增加了一個版本字段,很明顯這需要額外的空間來存儲這個版本字段,浪費了空間,但是樂觀鎖會讓系統具有更好的并發性,這是對時間的節省。因此樂觀鎖也是典型的空間換時間的策略。

135、闡述實體對象的三種狀態以及轉換關系。

答:最新的Hibernate文檔中為Hibernate對象定義了四種狀態(原來是三種狀態,面試的時候基本上問的也是三種狀態),分別是:瞬時態(new, or transient)、持久態(managed, or persistent)、游狀態(detached)和移除態(removed,以前Hibernate文檔中定義的三種狀態中沒有移除態),如下圖所示,就以前的Hibernate文檔中移除態被視為是瞬時態。

瞬時態:當new一個實體對象后,這個對象處于瞬時態,即這個對象只是一個保存臨時數據的內存區域,如果沒有變量引用這個對象,則會被JVM的垃圾回收機制回收。這個對象所保存的數據與數據庫沒有任何關系,除非通過Session的save()、saveOrUpdate()、persist()、merge()方法把瞬時態對象與數據庫關聯,并把數據插入或者更新到數據庫,這個對象才轉換為持久態對象。

持久態:持久態對象的實例在數據庫中有對應的記錄,并擁有一個持久化標識(ID)。對持久態對象進行delete操作后,數據庫中對應的記錄將被刪除,那么持久態對象與數據庫記錄不再存在對應關系,持久態對象變成移除態(可以視為瞬時態)。持久態對象被修改變更后,不會馬上同步到數據庫,直到數據庫事務提交。

游離態:當Session進行了close()、clear()、evict()或flush()后,實體對象從持久態變成游離態,對象雖然擁有持久和與數據庫對應記錄一致的標識值,但是因為對象已經從會話中清除掉,對象不在持久化管理之內,所以處于游離態(也叫脫管態)。游離態的對象與臨時狀態對象是十分相似的,只是它還含有持久化標識。

提示:關于這個問題,在Hibernate的官方文檔中有更為詳細的解讀。

136、如何理解Hibernate的延遲加載機制?在實際應用中,延遲加載與Session關閉的矛盾是如何處理的?

答:延遲加載就是并不是在讀取的時候就把數據加載進來,而是等到使用時再加載。Hibernate使用了虛擬代理機制實現延遲加載,我們使用Session的load()方法加載數據或者一對多關聯映射在使用延遲加載的情況下從一的一方加載多的一方,得到的都是虛擬代理,簡單的說返回給用戶的并不是實體本身,而是實體對象的代理。代理對象在用戶調用getter方法時才會去數據庫加載數據。但加載數據就需要數據庫連接。而當我們把會話關閉時,數據庫連接就同時關閉了。

延遲加載與session關閉的矛盾一般可以這樣處理:

① 關閉延遲加載特性。這種方式操作起來比較簡單,因為Hibernate的延遲加載特性是可以通過映射文件或者注解進行配置的,但這種解決方案存在明顯的缺陷。首先,出現"no session or session was closed"通常說明系統中已經存在主外鍵關聯,如果去掉延遲加載的話,每次查詢的開銷都會變得很大。

② 在session關閉之前先獲取需要查詢的數據,可以使用工具方法Hibernate.isInitialized()判斷對象是否被加載,如果沒有被加載則可以使用Hibernate.initialize()方法加載對象。

③ 使用攔截器或過濾器延長Session的生命周期直到視圖獲得數據。Spring整合Hibernate提供的OpenSessionInViewFilter和OpenSessionInViewInterceptor就是這種做法。

137、舉一個多對多關聯的例子,并說明如何實現多對多關聯映射。

答:例如:商品和訂單、學生和課程都是典型的多對多關系。可以在實體類上通過@ManyToMany注解配置多對多關聯或者通過映射文件中的和標簽配置多對多關聯,但是實際項目開發中,很多時候都是將多對多關聯映射轉換成兩個多對一關聯映射來實現的。

138、談一下你對繼承映射的理解。

答:繼承關系的映射策略有三種:

① 每個繼承結構一張表(table per class hierarchy),不管多少個子類都用一張表。

② 每個子類一張表(table per subclass),公共信息放一張表,特有信息放單獨的表。

③ 每個具體類一張表(table per concrete class),有多少個子類就有多少張表。

第一種方式屬于單表策略,其優點在于查詢子類對象的時候無需表連接,查詢速度快,適合多態查詢;缺點是可能導致表很大。后兩種方式屬于多表策略,其優點在于數據存儲緊湊,其缺點是需要進行連接查詢,不適合多態查詢。

139、簡述Hibernate常見優化策略。

答:這個問題應當挑自己使用過的優化策略回答,常用的有:

① 制定合理的緩存策略(二級緩存、查詢緩存)。

② 采用合理的Session管理機制。

③ 盡量使用延遲加載特性。

④ 設定合理的批處理參數。

⑤ 如果可以,選用UUID作為主鍵生成器。

⑥ 如果可以,選用基于版本號的樂觀鎖替代悲觀鎖。

⑦ 在開發過程中, 開啟hibernate.show_sql選項查看生成的SQL,從而了解底層的狀況;開發完成后關閉此選項。

⑧ 考慮數據庫本身的優化,合理的索引、恰當的數據分區策略等都會對持久層的性能帶來可觀的提升,但這些需要專業的DBA(數據庫管理員)提供支持。

140、談一談Hibernate的一級緩存、二級緩存和查詢緩存。

答:Hibernate的Session提供了一級緩存的功能,默認總是有效的,當應用程序保存持久化實體、修改持久化實體時,Session并不會立即把這種改變提交到數據庫,而是緩存在當前的Session中,除非顯示調用了Session的flush()方法或通過close()方法關閉Session。通過一級緩存,可以減少程序與數據庫的交互,從而提高數據庫訪問性能。

SessionFactory級別的二級緩存是全局性的,所有的Session可以共享這個二級緩存。不過二級緩存默認是關閉的,需要顯示開啟并指定需要使用哪種二級緩存實現類(可以使用第三方提供的實現)。一旦開啟了二級緩存并設置了需要使用二級緩存的實體類,SessionFactory就會緩存訪問過的該實體類的每個對象,除非緩存的數據超出了指定的緩存空間。

一級緩存和二級緩存都是對整個實體進行緩存,不會緩存普通屬性,如果希望對普通屬性進行緩存,可以使用查詢緩存。查詢緩存是將HQL或SQL語句以及它們的查詢結果作為鍵值對進行緩存,對于同樣的查詢可以直接從緩存中獲取數據。查詢緩存默認也是關閉的,需要顯示開啟。

141、Hibernate中DetachedCriteria類是做什么的?

答:DetachedCriteria和Criteria的用法基本上是一致的,但Criteria是由Session的createCriteria()方法創建的,也就意味著離開創建它的Session,Criteria就無法使用了。DetachedCriteria不需要Session就可以創建(使用DetachedCriteria.forClass()方法創建),所以通常也稱其為離線的Criteria,在需要進行查詢操作的時候再和Session綁定(調用其getExecutableCriteria(Session)方法),這也就意味著一個DetachedCriteria可以在需要的時候和不同的Session進行綁定。

142、@OneToMany注解的mappedBy屬性有什么作用?

答:@OneToMany用來配置一對多關聯映射,但通常情況下,一對多關聯映射都由多的一方來維護關聯關系,例如學生和班級,應該在學生類中添加班級屬性來維持學生和班級的關聯關系(在數據庫中是由學生表中的外鍵班級編號來維護學生表和班級表的多對一關系),如果要使用雙向關聯,在班級類中添加一個容器屬性來存放學生,并使用@OneToMany注解進行映射,此時mappedBy屬性就非常重要。如果使用XML進行配置,可以用標簽的inverse="true"設置來達到同樣的效果。

143、MyBatis中使用#和$書寫占位符有什么區別?

答:#將傳入的數據都當成一個字符串,會對傳入的數據自動加上引號;$將傳入的數據直接顯示生成在SQL中。注意:使用$占位符可能會導致SQL注射攻擊,能用#的地方就不要使用$,寫order by子句的時候應該用$而不是#。

144、解釋一下MyBatis中命名空間(namespace)的作用。

答:在大型項目中,可能存在大量的SQL語句,這時候為每個SQL語句起一個唯一的標識(ID)就變得并不容易了。為了解決這個問題,在MyBatis中,可以為每個映射文件起一個唯一的命名空間,這樣定義在這個映射文件中的每個SQL語句就成了定義在這個命名空間中的一個ID。只要我們能夠保證每個命名空間中這個ID是唯一的,即使在不同映射文件中的語句ID相同,也不會再產生沖突了。

145、MyBatis中的動態SQL是什么意思?

答:對于一些復雜的查詢,我們可能會指定多個查詢條件,但是這些條件可能存在也可能不存在,例如在58同城上面找房子,我們可能會指定面積、樓層和所在位置來查找房源,也可能會指定面積、價格、戶型和所在位置來查找房源,此時就需要根據用戶指定的條件動態生成SQL語句。如果不使用持久層框架我們可能需要自己拼裝SQL語句,還好MyBatis提供了動態SQL的功能來解決這個問題。MyBatis中用于實現動態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是如何實現的?

答:IoC叫控制反轉,是Inversion of Control的縮寫,DI(Dependency Injection)叫依賴注入,是對IoC更簡單的詮釋??刂品崔D是把傳統上由程序代碼直接操控的對象的調用權交給容器,通過容器來實現對象組件的裝配和管理。所謂的"控制反轉"就是對組件對象控制權的轉移,從程序代碼本身轉移到了外部容器,由容器來創建對象并管理對象之間的依賴關系。IoC體現了好萊塢原則 - "Don’t call me, we will call you"。依賴注入的基本原則是應用組件不應該負責查找資源或者其他依賴的協作對象。配置對象的工作應該由容器負責,查找資源的邏輯應該從應用組件的代碼中抽取出來,交給容器來完成。DI是對IoC更準確的描述,即組件之間的依賴關系由容器在運行期決定,形象的來說,即由容器動態的將某種依賴關系注入到組件之中。

舉個例子:一個類A需要用到接口B中的方法,那么就需要為類A和接口B建立關聯或依賴關系,最原始的方法是在類A中創建一個接口B的實現類C的實例,但這種方法需要開發人員自行維護二者的依賴關系,也就是說當依賴關系發生變動的時候需要修改代碼并重新構建整個系統。如果通過一個容器來管理這些對象以及對象的依賴關系,則只需要在類A中定義好用于關聯接口B的方法(構造器或setter方法),將類A和接口B的實現類C放入容器中,通過對容器的配置來實現二者的關聯。

依賴注入可以通過setter方法注入(設值注入)、構造器注入和接口注入三種方式來實現,Spring支持setter注入和構造器注入,通常使用構造器注入來注入必須的依賴關系,對于可選的依賴關系,則setter注入是更好的選擇,setter注入需要類提供無參構造器或者無參的靜態工廠方法來創建對象。

147、Spring中Bean的作用域有哪些?

答:在Spring的早期版本中,僅有兩個作用域:singleton和prototype,前者表示Bean以單例的方式存在;后者表示每次從容器中調用Bean時,都會返回一個新的實例,prototype通常翻譯為原型。

補充:設計模式中的創建型模式中也有一個原型模式,原型模式也是一個常用的模式,例如做一個室內設計軟件,所有的素材都在工具箱中,而每次從工具箱中取出的都是素材對象的一個原型,可以通過對象克隆來實現原型模式。

Spring 2.x中針對WebApplicationContext新增了3個作用域,分別是:request(每次HTTP請求都會創建一個新的Bean)、session(同一個HttpSession共享同一個Bean,不同的HttpSession使用不同的Bean)和globalSession(同一個全局Session共享一個Bean)。

說明:單例模式和原型模式都是重要的設計模式。一般情況下,無狀態或狀態不可變的類適合使用單例模式。在傳統開發中,由于DAO持有Connection這個非線程安全對象因而沒有使用單例模式;但在Spring環境下,所有DAO類對可以采用單例模式,因為Spring利用AOP和Java API中的ThreadLocal對非線程安全的對象進行了特殊處理。

ThreadLocal為解決多線程程序的并發問題提供了一種新的思路。ThreadLocal,顧名思義是線程的一個本地化對象,當工作于多線程中的對象使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程分配一個獨立的變量副本,所以每一個線程都可以獨立的改變自己的副本,而不影響其他線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量。

ThreadLocal類非常簡單好用,只有四個方法,能用上的也就是下面三個方法:

- void set(T value):設置當前線程的線程局部變量的值。

- T get():獲得當前線程所對應的線程局部變量的值。

- void remove():刪除當前線程中線程局部變量的值。

ThreadLocal是如何做到為每一個線程維護一份獨立的變量副本的呢?在ThreadLocal類中有一個Map,鍵為線程對象,值是其線程對應的變量的副本,自己要模擬實現一個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)指一種程序設計范型,該范型以一種稱為切面(aspect)的語言構造為基礎,切面是一種新的模塊化機制,用來描述分散在對象、類或方法中的橫切關注點(crosscutting concern)。

149、你是如何理解"橫切關注"這個概念的?

答:"橫切關注"是會影響到整個應用程序的關注功能,它跟正常的業務邏輯是正交的,沒有必然的聯系,但是幾乎所有的業務邏輯都會涉及到這些關注功能。通常,事務、日志、安全性等關注就是應用中的橫切關注功能。

150、你如何理解AOP中的連接點(Joinpoint)、切點(Pointcut)、增強(Advice)、引介(Introduction)、織入(Weaving)、切面(Aspect)這些概念?

答:

a. 連接點(Joinpoint):程序執行的某個特定位置(如:某個方法調用前、調用后,方法拋出異常后)。一個類或一段程序代碼擁有一些具有邊界性質的特定點,這些代碼中的特定點就是連接點。Spring僅支持方法的連接點。

b. 切點(Pointcut):如果連接點相當于數據中的記錄,那么切點相當于查詢條件,一個切點可以匹配多個連接點。Spring AOP的規則解析引擎負責解析切點所設定的查詢條件,找到對應的連接點。

c. 增強(Advice):增強是織入到目標類連接點上的一段程序代碼。Spring提供的增強接口都是帶方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。很多資料上將增強譯為“通知”,這明顯是個詞不達意的翻譯,讓很多程序員困惑了許久。

說明:Advice在國內的很多書面資料中都被翻譯成"通知",但是很顯然這個翻譯無法表達其本質,有少量的讀物上將這個詞翻譯為"增強",這個翻譯是對Advice較為準確的詮釋,我們通過AOP將橫切關注功能加到原有的業務邏輯上,這就是對原有業務邏輯的一種增強,這種增強可以是前置增強、后置增強、返回后增強、拋異常時增強和包圍型增強。

d. 引介(Introduction):引介是一種特殊的增強,它為類添加一些屬性和方法。這樣,即使一個業務類原本沒有實現某個接口,通過引介功能,可以動態的未該業務類添加接口的實現邏輯,讓業務類成為這個接口的實現類。

e. 織入(Weaving):織入是將增強添加到目標類具體連接點上的過程,AOP有三種織入方式:①編譯期織入:需要特殊的Java編譯期(例如AspectJ的ajc);②裝載期織入:要求使用特殊的類加載器,在裝載類的時候對類進行增強;③運行時織入:在運行時為目標類生成代理實現增強。Spring采用了動態代理的方式實現了運行時織入,而AspectJ采用了編譯期織入和裝載期織入的方式。

f. 切面(Aspect):切面是由切點和增強(引介)組成的,它包括了對橫切關注功能的定義,也包括了對連接點的定義。

補充:代理模式是GoF提出的23種設計模式中最為經典的模式之一,代理模式是對象的結構模式,它給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用。簡單的說,代理對象可以完成比原對象更多的職責,當需要為原對象添加橫切關注功能時,就可以使用原對象的代理對象。我們在打開Office系列的Word文檔時,如果文檔中有插圖,當文檔剛加載時,文檔中的插圖都只是一個虛框占位符,等用戶真正翻到某頁要查看該圖片時,才會真正加載這張圖,這其實就是對代理模式的使用,代替真正圖片的虛框就是一個虛擬代理;Hibernate的load方法也是返回一個虛擬代理對象,等用戶真正需要訪問對象的屬性時,才向數據庫發出SQL語句獲得真實對象。

下面用一個找槍手代考的例子演示代理模式的使用:

說明:使用Java的動態代理有一個局限性就是代理的類必須要實現接口,雖然面向接口編程是每個優秀的Java程序都知道的規則,但現實往往不盡如人意,對于沒有實現接口的類如何為其生成代理呢?繼承!繼承是最經典的擴展已有代碼能力的手段,雖然繼承常常被初學者濫用,但繼承也常常被進階的程序員忽視。CGLib采用非常底層的字節碼生成技術,通過為一個類創建子類來生成代理,它彌補了Java動態代理的不足,因此Spring中動態代理和CGLib都是創建代理的重要手段,對于實現了接口的類就用動態代理為其生成代理類,而沒有實現接口的類就用CGLib通過繼承的方式為其創建代理。

151、Spring中自動裝配的方式有哪些?

答:

- no:不進行自動裝配,手動設置Bean的依賴關系。

- byName:根據Bean的名字進行自動裝配。

- byType:根據Bean的類型進行自動裝配。

- constructor:類似于byType,不過是應用于構造器的參數,如果正好有一個Bean與構造器的參數類型相同則可以自動裝配,否則會導致錯誤。

- autodetect:如果有默認的構造器,則通過constructor的方式進行自動裝配,否則使用byType的方式進行自動裝配。

說明:自動裝配沒有自定義裝配方式那么精確,而且不能自動裝配簡單屬性(基本類型、字符串等),在使用時應注意。

152、Spring中如何使用注解來配置Bean?有哪些相關的注解?

答:首先需要在Spring配置文件中增加如下配置:

然后可以用@Component、@Controller、@Service、@Repository注解來標注需要由Spring IoC容器進行對象托管的類。這幾個注解沒有本質區別,只不過@Controller通常用于控制器,@Service通常用于業務邏輯類,@Repository通常用于倉儲類(例如我們的DAO實現類),普通的類用@Component來標注。

153、Spring支持的事務管理類型有哪些?你在項目中使用哪種方式?

答:Spring支持編程式事務管理和聲明式事務管理。許多Spring框架的用戶選擇聲明式事務管理,因為這種方式和應用程序的關聯較少,因此更加符合輕量級容器的概念。聲明式事務管理要優于編程式事務管理,盡管在靈活性方面它弱于編程式事務管理,因為編程式事務允許你通過代碼控制業務。

事務分為全局事務和局部事務。全局事務由應用服務器管理,需要底層服務器JTA支持(如WebLogic、WildFly等)。局部事務和底層采用的持久化方案有關,例如使用JDBC進行持久化時,需要使用Connetion對象來操作事務;而采用Hibernate進行持久化時,需要使用Session對象來操作事務。

Spring提供了如下所示的事務管理器。

事務管理器實現類目標對象

DataSourceTransactionManager注入DataSource

HibernateTransactionManager注入SessionFactory

JdoTransactionManager管理JDO事務

JtaTransactionManager使用JTA管理事務

PersistenceBrokerTransactionManager管理Apache的OJB事務

這些事務的父接口都是PlatformTransactionManager。Spring的事務管理機制是一種典型的策略模式,PlatformTransactionManager代表事務管理接口,該接口定義了三個方法,該接口并不知道底層如何管理事務,但是它的實現類必須提供getTransaction()方法(開啟事務)、commit()方法(提交事務)、rollback()方法(回滾事務)的多態實現,這樣就可以用不同的實現類代表不同的事務管理策略。使用JTA全局事務策略時,需要底層應用服務器支持,而不同的應用服務器所提供的JTA全局事務可能存在細節上的差異,因此實際配置全局事務管理器是可能需要使用JtaTransactionManager的子類,如:WebLogicJtaTransactionManager(Oracle的WebLogic服務器提供)、UowJtaTransactionManager(IBM的WebSphere服務器提供)等。

編程式事務管理如下所示。


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);? ? ? ? ? ? }? ? ? ? });? ? }}

聲明式事務如下圖所示,以Spring整合Hibernate 3為例,包括完整的DAO和業務邏輯代碼。


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推斷采用了何種服務器端的技術,另一方面可以欺騙搜索引擎,因為搜索引擎不會搜索動態頁面,這種做法稱為偽靜態化。

156、Spring MVC的工作原理是怎樣的?

答:Spring MVC的工作原理如下圖所示:

① 客戶端的所有請求都交給前端控制器DispatcherServlet來處理,它會負責調用系統的其他模塊來真正處理用戶的請求。

② DispatcherServlet收到請求后,將根據請求的信息(包括URL、HTTP協議方法、請求頭、請求參數、Cookie等)以及HandlerMapping的配置找到處理該請求的Handler(任何一個對象都可以作為請求的Handler)。

③在這個地方Spring會通過HandlerAdapter對該處理器進行封裝。

④ HandlerAdapter是一個適配器,它用統一的接口對各種Handler中的方法進行調用。

⑤ Handler完成對用戶請求的處理后,會返回一個ModelAndView對象給DispatcherServlet,ModelAndView顧名思義,包含了數據模型以及相應的視圖的信息。

⑥ ModelAndView的視圖是邏輯視圖,DispatcherServlet還要借助ViewResolver完成從邏輯視圖到真實視圖對象的解析工作。

⑦ 當得到真正的視圖對象后,DispatcherServlet會利用視圖對象對模型數據進行渲染。

⑧ 客戶端得到響應,可能是一個普通的HTML頁面,也可以是XML或JSON字符串,還可以是一張圖片或者一個PDF文件。

157、如何在Spring IoC容器中配置數據源?

答:

DBCP配置:

C3P0配置:

提示:DBCP的詳細配置在第153題中已經完整的展示過了。

158、如何配置配置事務增強?

答:


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框架為企業級開發帶來的好處有哪些)?

答:可以從以下幾個方面作答:

- 非侵入式:支持基于POJO的編程模式,不強制性的要求實現Spring框架中的接口或繼承Spring框架中的類。

- IoC容器:IoC容器幫助應用程序管理對象以及對象之間的依賴關系,對象之間的依賴關系如果發生了改變只需要修改配置文件而不是修改代碼,因為代碼的修改可能意味著項目的重新構建和完整的回歸測試。有了IoC容器,程序員再也不需要自己編寫工廠、單例,這一點特別符合Spring的精神"不要重復的發明輪子"。

- AOP(面向切面編程):將所有的橫切關注功能封裝到切面(aspect)中,通過配置的方式將橫切關注功能動態添加到目標代碼上,進一步實現了業務邏輯和系統服務之間的分離。另一方面,有了AOP程序員可以省去很多自己寫代理類的工作。

- MVC:Spring的MVC框架是非常優秀的,從各個方面都可以甩Struts 2幾條街,為Web表示層提供了更好的解決方案。

- 事務管理:Spring以寬廣的胸懷接納多種持久層技術,并且為其提供了聲明式的事務管理,在不需要任何一行代碼的情況下就能夠完成事務管理。

- 其他:選擇Spring框架的原因還遠不止于此,Spring為Java企業級開發提供了一站式選擇,你可以在需要的時候使用它的部分和全部,更重要的是,你甚至可以在感覺不到Spring存在的情況下,在你的項目中使用Spring提供的各種優秀的功能。

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容器找到關于Bean的定義并實例化該Bean。

② Spring IoC容器對Bean進行依賴注入。

③ 如果Bean實現了BeanNameAware接口,則將該Bean的id傳給setBeanName方法。

④ 如果Bean實現了BeanFactoryAware接口,則將BeanFactory對象傳給setBeanFactory方法。

⑤ 如果Bean實現了BeanPostProcessor接口,則調用其postProcessBeforeInitialization方法。

⑥ 如果Bean實現了InitializingBean接口,則調用其afterPropertySet方法。

⑦ 如果有和Bean關聯的BeanPostProcessors對象,則這些對象的postProcessAfterInitialization方法被調用。

⑧ 當銷毀Bean實例時,如果Bean實現了DisposableBean接口,則調用其destroy方法。

162、依賴注入時如何注入集合屬性?

答:可以在定義Bean屬性時,通過 / / / 分別為其注入列表、集合、映射和鍵值都是字符串的映射屬性。

163、Spring中的自動裝配有哪些限制?

答:

- 如果使用了構造器注入或者setter注入,那么將覆蓋自動裝配的依賴關系。

- 基本數據類型的值、字符串字面量、類字面量無法使用自動裝配來注入。

- 優先考慮使用顯式的裝配來進行更精確的依賴注入而不是使用自動裝配。

164、在Web項目中如何獲得Spring的IoC容器?

答:

WebApplicationContext ctx=WebApplicationContextUtils.getWebApplicationContext(servletContext);

165. 大型網站在架構上應當考慮哪些問題?

答:

- 分層:分層是處理任何復雜系統最常見的手段之一,將系統橫向切分成若干個層面,每個層面只承擔單一的職責,然后通過下層為上層提供的基礎設施和服務以及上層對下層的調用來形成一個完整的復雜的系統。計算機網絡的開放系統互聯參考模型(OSI/RM)和Internet的TCP/IP模型都是分層結構,大型網站的軟件系統也可以使用分層的理念將其分為持久層(提供數據存儲和訪問服務)、業務層(處理業務邏輯,系統中最核心的部分)和表示層(系統交互、視圖展示)。需要指出的是:(1)分層是邏輯上的劃分,在物理上可以位于同一設備上也可以在不同的設備上部署不同的功能模塊,這樣可以使用更多的計算資源來應對用戶的并發訪問;(2)層與層之間應當有清晰的邊界,這樣分層才有意義,才更利于軟件的開發和維護。

- 分割:分割是對軟件的縱向切分。我們可以將大型網站的不同功能和服務分割開,形成高內聚低耦合的功能模塊(單元)。在設計初期可以做一個粗粒度的分割,將網站分割為若干個功能模塊,后期還可以進一步對每個模塊進行細粒度的分割,這樣一方面有助于軟件的開發和維護,另一方面有助于分布式的部署,提供網站的并發處理能力和功能的擴展。

- 分布式:除了上面提到的內容,網站的靜態資源(JavaScript、CSS、圖片等)也可以采用獨立分布式部署并采用獨立的域名,這樣可以減輕應用服務器的負載壓力,也使得瀏覽器對資源的加載更快。數據的存取也應該是分布式的,傳統的商業級關系型數據庫產品基本上都支持分布式部署,而新生的NoSQL產品幾乎都是分布式的。當然,網站后臺的業務處理也要使用分布式技術,例如查詢索引的構建、數據分析等,這些業務計算規模龐大,可以使用Hadoop以及MapReduce分布式計算框架來處理。

- 集群:集群使得有更多的服務器提供相同的服務,可以更好的提供對并發的支持。

- 緩存:所謂緩存就是用空間換取時間的技術,將數據盡可能放在距離計算最近的位置。使用緩存是網站優化的第一定律。我們通常說的CDN、反向代理、熱點數據都是對緩存技術的使用。

- 異步:異步是實現軟件實體之間解耦合的又一重要手段。異步架構是典型的生產者消費者模式,二者之間沒有直接的調用關系,只要保持數據結構不變,彼此功能實現可以隨意變化而不互相影響,這對網站的擴展非常有利。使用異步處理還可以提高系統可用性,加快網站的響應速度(用Ajax加載數據就是一種異步技術),同時還可以起到削峰作用(應對瞬時高并發)。";能推遲處理的都要推遲處理"是網站優化的第二定律,而異步是踐行網站優化第二定律的重要手段。

- 冗余:各種服務器都要提供相應的冗余服務器以便在某臺或某些服務器宕機時還能保證網站可以正常工作,同時也提供了災難恢復的可能性。冗余是網站高可用性的重要保證。

166、你用過的網站前端優化的技術有哪些?

答:

① 瀏覽器訪問優化:

- 減少HTTP請求數量:合并CSS、合并JavaScript、合并圖片(CSS Sprite)

- 使用瀏覽器緩存:通過設置HTTP響應頭中的Cache-Control和Expires屬性,將CSS、JavaScript、圖片等在瀏覽器中緩存,當這些靜態資源需要更新時,可以更新HTML文件中的引用來讓瀏覽器重新請求新的資源

- 啟用壓縮

- CSS前置,JavaScript后置

- 減少Cookie傳輸

② CDN加速:CDN(Content Distribute Network)的本質仍然是緩存,將數據緩存在離用戶最近的地方,CDN通常部署在網絡運營商的機房,不僅可以提升響應速度,還可以減少應用服務器的壓力。當然,CDN緩存的通常都是靜態資源。

③ 反向代理:反向代理相當于應用服務器的一個門面,可以保護網站的安全性,也可以實現負載均衡的功能,當然最重要的是它緩存了用戶訪問的熱點資源,可以直接從反向代理將某些內容返回給用戶瀏覽器。

167、你使用過的應用服務器優化技術有哪些?

答:

① 分布式緩存:緩存的本質就是內存中的哈希表,如果設計一個優質的哈希函數,那么理論上哈希表讀寫的漸近時間復雜度為O(1)。緩存主要用來存放那些讀寫比很高、變化很少的數據,這樣應用程序讀取數據時先到緩存中讀取,如果沒有或者數據已經失效再去訪問數據庫或文件系統,并根據擬定的規則將數據寫入緩存。對網站數據的訪問也符合二八定律(Pareto分布,冪律分布),即80%的訪問都集中在20%的數據上,如果能夠將這20%的數據緩存起來,那么系統的性能將得到顯著的改善。當然,使用緩存需要解決以下幾個問題:

- 頻繁修改的數據;

- 數據不一致與臟讀;

- 緩存雪崩(可以采用分布式緩存服務器集群加以解決,memcached是廣泛采用的解決方案);

- 緩存預熱;

- 緩存穿透(惡意持續請求不存在的數據)。

② 異步操作:可以使用消息隊列將調用異步化,通過異步處理將短時間高并發產生的事件消息存儲在消息隊列中,從而起到削峰作用。電商網站在進行促銷活動時,可以將用戶的訂單請求存入消息隊列,這樣可以抵御大量的并發訂單請求對系統和數據庫的沖擊。目前,絕大多數的電商網站即便不進行促銷活動,訂單系統都采用了消息隊列來處理。

③ 使用集群。

④ 代碼優化:

- 多線程:基于Java的Web開發基本上都通過多線程的方式響應用戶的并發請求,使用多線程技術在編程上要解決線程安全問題,主要可以考慮以下幾個方面:A. 將對象設計為無狀態對象(這和面向對象的編程觀點是矛盾的,在面向對象的世界中被視為不良設計),這樣就不會存在并發訪問時對象狀態不一致的問題。B. 在方法內部創建對象,這樣對象由進入方法的線程創建,不會出現多個線程訪問同一對象的問題。使用ThreadLocal將對象與線程綁定也是很好的做法,這一點在前面已經探討過了。C. 對資源進行并發訪問時應當使用合理的鎖機制。

- 非阻塞I/O: 使用單線程和非阻塞I/O是目前公認的比多線程的方式更能充分發揮服務器性能的應用模式,基于Node.js構建的服務器就采用了這樣的方式。Java在JDK 1.4中就引入了NIO(Non-blocking I/O),在Servlet 3規范中又引入了異步Servlet的概念,這些都為在服務器端采用非阻塞I/O提供了必要的基礎。

- 資源復用:資源復用主要有兩種方式,一是單例,二是對象池,我們使用的數據庫連接池、線程池都是對象池化技術,這是典型的用空間換取時間的策略,另一方面也實現對資源的復用,從而避免了不必要的創建和釋放資源所帶來的開銷。

168、什么是XSS攻擊?什么是SQL注入攻擊?什么是CSRF攻擊?

答:

- XSS(Cross Site Script,跨站腳本攻擊)是向網頁中注入惡意腳本在用戶瀏覽網頁時在用戶瀏覽器中執行惡意腳本的攻擊方式。跨站腳本攻擊分有兩種形式:反射型攻擊(誘使用戶點擊一個嵌入惡意腳本的鏈接以達到攻擊的目標,目前有很多攻擊者利用論壇、微博發布含有惡意腳本的URL就屬于這種方式)和持久型攻擊(將惡意腳本提交到被攻擊網站的數據庫中,用戶瀏覽網頁時,惡意腳本從數據庫中被加載到頁面執行,QQ郵箱的早期版本就曾經被利用作為持久型跨站腳本攻擊的平臺)。XSS雖然不是什么新鮮玩意,但是攻擊的手法卻不斷翻新,防范XSS主要有兩方面:消毒(對危險字符進行轉義)和HttpOnly(防范XSS攻擊者竊取Cookie數據)。

- SQL注入攻擊是注入攻擊最常見的形式(此外還有OS注入攻擊(Struts 2的高危漏洞就是通過OGNL實施OS注入攻擊導致的)),當服務器使用請求參數構造SQL語句時,惡意的SQL被嵌入到SQL中交給數據庫執行。SQL注入攻擊需要攻擊者對數據庫結構有所了解才能進行,攻擊者想要獲得表結構有多種方式:(1)如果使用開源系統搭建網站,數據庫結構也是公開的(目前有很多現成的系統可以直接搭建論壇,電商網站,雖然方便快捷但是風險是必須要認真評估的);(2)錯誤回顯(如果將服務器的錯誤信息直接顯示在頁面上,攻擊者可以通過非法參數引發頁面錯誤從而通過錯誤信息了解數據庫結構,Web應用應當設置友好的錯誤頁,一方面符合最小驚訝原則,一方面屏蔽掉可能給系統帶來危險的錯誤回顯信息);(3)盲注。防范SQL注入攻擊也可以采用消毒的方式,通過正則表達式對請求參數進行驗證,此外,參數綁定也是很好的手段,這樣惡意的SQL會被當做SQL的參數而不是命令被執行,JDBC中的PreparedStatement就是支持參數綁定的語句對象,從性能和安全性上都明顯優于Statement。

- CSRF攻擊(Cross Site Request Forgery,跨站請求偽造)是攻擊者通過跨站請求,以合法的用戶身份進行非法操作(如轉賬或發帖等)。CSRF的原理是利用瀏覽器的Cookie或服務器的Session,盜取用戶身份,其原理如下圖所示。防范CSRF的主要手段是識別請求者的身份,主要有以下幾種方式:(1)在表單中添加令牌(token);(2)驗證碼;(3)檢查請求頭中的Referer(前面提到防圖片盜鏈接也是用的這種方式)。令牌和驗證都具有一次消費性的特征,因此在原理上一致的,但是驗證碼是一種糟糕的用戶體驗,不是必要的情況下不要輕易使用驗證碼,目前很多網站的做法是如果在短時間內多次提交一個表單未獲得成功后才要求提供驗證碼,這樣會獲得較好的用戶體驗。

補充:防火墻的架設是Web安全的重要保障,ModSecurity是開源的Web防火墻中的佼佼者。企業級防火墻的架設應當有兩級防火墻,Web服務器和部分應用服務器可以架設在兩級防火墻之間的DMZ,而數據和資源服務器應當架設在第二級防火墻之后。

169. 什么是領域模型(domain model)?貧血模型(anaemic domain model)和充血模型(rich domain model)有什么區別?

答:領域模型是領域內的概念類或現實世界中對象的可視化表示,又稱為概念模型或分析對象模型,它專注于分析問題領域本身,發掘重要的業務領域概念,并建立業務領域概念之間的關系。貧血模型是指使用的領域對象中只有setter和getter方法(POJO),所有的業務邏輯都不包含在領域對象中而是放在業務邏輯層。有人將我們這里說的貧血模型進一步劃分成失血模型(領域對象完全沒有業務邏輯)和貧血模型(領域對象有少量的業務邏輯),我們這里就不對此加以區分了。充血模型將大多數業務邏輯和持久化放在領域對象中,業務邏輯(業務門面)只是完成對業務邏輯的封裝、事務和權限等的處理。下面兩張圖分別展示了貧血模型和充血模型的分層架構。

貧血模型

充血模型

貧血模型下組織領域邏輯通常使用事務腳本模式,讓每個過程對應用戶可能要做的一個動作,每個動作由一個過程來驅動。也就是說在設計業務邏輯接口的時候,每個方法對應著用戶的一個操作,這種模式有以下幾個有點:

- 它是一個大多數開發者都能夠理解的簡單過程模型(適合國內的絕大多數開發者)。

- 它能夠與一個使用行數據入口或表數據入口的簡單數據訪問層很好的協作。

- 事務邊界的顯而易見,一個事務開始于腳本的開始,終止于腳本的結束,很容易通過代理(或切面)實現聲明式事務。

然而,事務腳本模式的缺點也是很多的,隨著領域邏輯復雜性的增加,系統的復雜性將迅速增加,程序結構將變得極度混亂。開源中國社區上有一篇很好的譯文《貧血領域模型是如何導致糟糕的軟件產生》對這個問題做了比較細致的闡述。

170. 談一談測試驅動開發(TDD)的好處以及你的理解。

答:TDD是指在編寫真正的功能實現代碼之前先寫測試代碼,然后根據需要重構實現代碼。在JUnit的作者Kent Beck的大作《測試驅動開發:實戰與模式解析》(Test-Driven Development: by Example)一書中有這么一段內容:“消除恐懼和不確定性是編寫測試驅動代碼的重要原因”。因為編寫代碼時的恐懼會讓你小心試探,讓你回避溝通,讓你羞于得到反饋,讓你變得焦躁不安,而TDD是消除恐懼、讓Java開發者更加自信更加樂于溝通的重要手段。TDD會帶來的好處可能不會馬上呈現,但是你在某個時候一定會發現,這些好處包括:

- 更清晰的代碼 — 只寫需要的代碼

- 更好的設計

- 更出色的靈活性 — 鼓勵程序員面向接口編程

- 更快速的反饋 — 不會到系統上線時才知道bug的存在

補充:敏捷軟件開發的概念已經有很多年了,而且也部分的改變了軟件開發這個行業,TDD也是敏捷開發所倡導的。

TDD可以在多個層級上應用,包括單元測試(測試一個類中的代碼)、集成測試(測試類之間的交互)、系統測試(測試運行的系統)和系統集成測試(測試運行的系統包括使用的第三方組件)。TDD的實施步驟是:紅(失敗測試)- 綠(通過測試) - 重構。關于實施TDD的詳細步驟請參考另一篇文章《測試驅動開發之初窺門徑》。

在使用TDD開發時,經常會遇到需要被測對象需要依賴其他子系統的情況,但是你希望將測試代碼跟依賴項隔離,以保證測試代碼僅僅針對當前被測對象或方法展開,這時候你需要的是測試替身。測試替身可以分為四類:

- 虛設替身:只傳遞但是不會使用到的對象,一般用于填充方法的參數列表

- 存根替身:總是返回相同的預設響應,其中可能包括一些虛設狀態

- 偽裝替身:可以取代真實版本的可用版本(比真實版本還是會差很多)

- 模擬替身:可以表示一系列期望值的對象,并且可以提供預設響應

Java世界中實現模擬替身的第三方工具非常多,包括EasyMock、Mockito、jMock等。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容