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語句完成數據讀取;load方法則可以從二級緩存中獲取數據;從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進行配置,可以用<set>標籤的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

下面是映射文件的片段。

1
2
3
4
5
6
7
8
9
10
11
12
<select id="foo" parameterType="Blog" resultType="Blog">
     select * from t_blog where 1 = 1
     <if test="title != null">
         and title = #{title}
     </if>
     <if test="content != null">
         and content = #{content}
     </if>
     <if test="owner != null">
         and owner = #{owner}
     </if>
</select>

當然也可以像下面這些書寫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="foo" parameterType="Blog" resultType="Blog">
    select * from t_blog where 1 = 1
    <choose>
        <when test="title != null">
            and title = #{title}
        </when>
        <when test="content != null">
            and content = #{content}
        </when>
        <otherwise>
            and owner = "owner1"
        </otherwise>
    </choose>
</select>

再看看下面這個例子。

1
2
3
4
5
6
7
<select id="bar" resultType="Blog">
    select * from t_blog where id in
    <foreach collection="array" index="index"
        item="item" open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

146、什麼是IoC和DI?DI是如何實現的?
答:IoC叫控制反轉,是Inversion of Control的縮寫,DI(Dependency Injection)叫依賴注入,是對IoC更簡單的詮釋。控制反轉是把傳統上由程序代碼直接操控的對象的調用權交給容器,通過容器來實現對象組件的裝配和管理。所謂的”控制反轉”就是對組件對象控制權的轉移,從程序代碼本身轉移到了外部容器,由容器來創建對象並管理對象之間的依賴關係。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類其實並不困難,代碼如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
 
public class MyThreadLocal<T> {
    private Map<Thread, T> map = Collections.synchronizedMap(new HashMap<Thread, T>());
 
    public void set(T newValue) {
        map.put(Thread.currentThread(), newValue);
    }
 
    public T get() {
        return map.get(Thread.currentThread());
    }
 
    public void remove() {
        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語句獲得真實對象。

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

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * 參考人員接口
 * @author 駱昊
 *
 */
public interface Candidate {
 
    /**
     * 答題
     */
    public void answerTheQuestions();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * 懶學生
 * @author 駱昊
 *
 */
public class LazyStudent implements Candidate {
    private String name;        // 姓名
 
    public LazyStudent(String name) {
        this.name = name;
    }
 
    @Override
    public void answerTheQuestions() {
        // 懶學生只能寫出自己的名字不會答題
        System.out.println("姓名: " + name);
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * 槍手
 * @author 駱昊
 *
 */
public class Gunman implements Candidate {
    private Candidate target;   // 被代理對象
 
    public Gunman(Candidate target) {
        this.target = target;
    }
 
    @Override
    public void answerTheQuestions() {
        // 槍手要寫上代考的學生的姓名
        target.answerTheQuestions();
        // 槍手要幫助懶學生答題並交卷
        System.out.println("奮筆疾書正確答案");
        System.out.println("交卷");
    }
 
}
1
2
3
4
5
6
7
public class ProxyTest1 {
 
    public static void main(String[] args) {
        Candidate c = new Gunman(new LazyStudent("王小二"));
        c.answerTheQuestions();
    }
}

說明:從JDK 1.3開始,Java提供了動態代理技術,允許開發者在運行時創建接口的代理實例,主要包括Proxy類和InvocationHandler接口。下面的例子使用動態代理爲ArrayList編寫一個代理,在添加和刪除元素時,在控制檯打印添加或刪除的元素以及ArrayList的大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
 
public class ListProxy<T> implements InvocationHandler {
    private List<T> target;
 
    public ListProxy(List<T> target) {
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object retVal = null;
        System.out.println("[" + method.getName() + ": " + args[0] + "]");
        retVal = method.invoke(target, args);
        System.out.println("[size=" + target.size() + "]");
        return retVal;
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
 
public class ProxyTest2 {
 
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        Class<?> clazz = list.getClass();
        ListProxy<String> myProxy = new ListProxy<String>(list);
        List<String> newList = (List<String>)
                Proxy.newProxyInstance(clazz.getClassLoader(),
                clazz.getInterfaces(), myProxy);
        newList.add("apple");
        newList.add("banana");
        newList.add("orange");
        newList.remove("banana");
    }
}

說明:使用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配置文件中增加如下配置:

1
<context:component-scan base-package="org.example"/>

然後可以用@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服務器提供)等。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:p="http://www.springframework.org/schema/p"
    xmlns:p="http://www.springframework.org/schema/context"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 
     <context:component-scan base-package="com.jackfrued"/>
 
     <bean id="propertyConfig"
         class="org.springframework.beans.factory.config.
  PropertyPlaceholderConfigurer">
         <property name="location">
             <value>jdbc.properties</value>
         </property>
     </bean>
 
     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
         <property name="driverClassName">
             <value>${db.driver}</value>
         </property>
         <property name="url">
             <value>${db.url}</value>
         </property>
         <property name="username">
             <value>${db.username}</value>
         </property>
         <property name="password">
             <value>${db.password}</value>
         </property>
     </bean>
 
     <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
         <property name="dataSource">
             <ref bean="dataSource" />
         </property>
     </bean>
 
     <!-- JDBC事務管理器 -->
     <bean id="transactionManager"
         class="org.springframework.jdbc.datasource.
       DataSourceTransactionManager" scope="singleton">
         <property name="dataSource">
             <ref bean="dataSource" />
         </property>
     </bean>
 
     <!-- 聲明事務模板 -->
     <bean id="transactionTemplate"
         class="org.springframework.transaction.support.
   TransactionTemplate">
         <property name="transactionManager">
             <ref bean="transactionManager" />
         </property>
     </bean>
 
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.jackfrued.dao.impl;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
 
import com.jackfrued.dao.EmpDao;
import com.jackfrued.entity.Emp;
 
@Repository
public class EmpDaoImpl implements EmpDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
 
    @Override
    public boolean save(Emp emp) {
        String sql = "insert into emp values (?,?,?)";
        return jdbcTemplate.update(sql, emp.getId(), emp.getName(), emp.getBirthday()) == 1;
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.jackfrued.biz.impl;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
 
import com.jackfrued.biz.EmpService;
import com.jackfrued.dao.EmpDao;
import com.jackfrued.entity.Emp;
 
@Service
public class EmpServiceImpl implements EmpService {
    @Autowired
    private TransactionTemplate txTemplate;
    @Autowired
    private EmpDao empDao;
 
    @Override
    public void addEmp(final Emp emp) {
        txTemplate.execute(new TransactionCallbackWithoutResult() {
 
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
                empDao.save(emp);
            }
        });
    }
 
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
 
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
 
 
http://www.springframework.org/schema/context
 
 
http://www.springframework.org/schema/context/spring-context-3.2.xsd
 
 
http://www.springframework.org/schema/aop
 
 
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
 
 
http://www.springframework.org/schema/tx
 
 
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
 
    <!-- 配置由Spring IoC容器託管的對象對應的被註解的類所在的包 -->
    <context:component-scan base-package="com.jackfrued" />
 
    <!-- 配置通過自動生成代理實現AOP功能 -->
    <aop:aspectj-autoproxy />
 
    <!-- 配置數據庫連接池 (DBCP) -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <!-- 配置驅動程序類 -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <!-- 配置連接數據庫的URL -->
        <property name="url" value="jdbc:mysql://localhost:3306/myweb" />
        <!-- 配置訪問數據庫的用戶名 -->
        <property name="username" value="root" />
        <!-- 配置訪問數據庫的口令 -->
        <property name="password" value="123456" />
        <!-- 配置最大連接數 -->
        <property name="maxActive" value="150" />
        <!-- 配置最小空閒連接數 -->
        <property name="minIdle" value="5" />
        <!-- 配置最大空閒連接數 -->
        <property name="maxIdle" value="20" />
        <!-- 配置初始連接數 -->
        <property name="initialSize" value="10" />
        <!-- 配置連接被泄露時是否生成日誌 -->
        <property name="logAbandoned" value="true" />
        <!-- 配置是否刪除超時連接 -->
        <property name="removeAbandoned" value="true" />
        <!-- 配置刪除超時連接的超時門限值(以秒爲單位) -->
        <property name="removeAbandonedTimeout" value="120" />
        <!-- 配置超時等待時間(以毫秒爲單位) -->
        <property name="maxWait" value="5000" />
        <!-- 配置空閒連接回收器線程運行的時間間隔(以毫秒爲單位) -->
        <property name="timeBetweenEvictionRunsMillis" value="300000" />
        <!-- 配置連接空閒多長時間後(以毫秒爲單位)被斷開連接 -->
        <property name="minEvictableIdleTimeMillis" value="60000" />
    </bean>
 
    <!-- 配置Spring提供的支持註解ORM映射的Hibernate會話工廠 -->
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <!-- 通過setter注入數據源屬性 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 配置實體類所在的包 -->
        <property name="packagesToScan" value="com.jackfrued.entity" />
        <!-- 配置Hibernate的相關屬性 -->
        <property name="hibernateProperties">
            <!-- 在項目調試完成後要刪除show_sql和format_sql屬性否則對性能有顯著影響 -->
            <value>
                hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
            </value>
        </property>
    </bean>
 
    <!-- 配置Spring提供的Hibernate事務管理器 -->
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <!-- 通過setter注入Hibernate會話工廠 -->
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
 
    <!-- 配置基於註解配置聲明式事務 -->
    <tx:annotation-driven />
 
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.jackfrued.dao;
 
import java.io.Serializable;
import java.util.List;
 
import com.jackfrued.comm.QueryBean;
import com.jackfrued.comm.QueryResult;
 
/**
 * 數據訪問對象接口(以對象爲單位封裝CRUD操作)
 * @author 駱昊
 *
 * @param <E> 實體類型
 * @param <K> 實體標識字段的類型
 */
public interface BaseDao <E, K extends Serializable> {
 
    /**
     * 新增
     * @param entity 業務實體對象
     * @return 增加成功返回實體對象的標識
     */
    public K save(E entity);
 
    /**
     * 刪除
     * @param entity 業務實體對象
     */
    public void delete(E entity);
 
    /**
     * 根據ID刪除
     * @param id 業務實體對象的標識
     * @return 刪除成功返回true否則返回false
     */
    public boolean deleteById(K id);
 
    /**
     * 修改
     * @param entity 業務實體對象
     * @return 修改成功返回true否則返回false
     */
    public void update(E entity);
 
    /**
     * 根據ID查找業務實體對象
     * @param id 業務實體對象的標識
     * @return 業務實體對象對象或null
     */
    public E findById(K id);
 
    /**
     * 根據ID查找業務實體對象
     * @param id 業務實體對象的標識
     * @param lazy 是否使用延遲加載
     * @return 業務實體對象對象
     */
    public E findById(K id, boolean lazy);
 
    /**
     * 查找所有業務實體對象
     * @return 裝所有業務實體對象的列表容器
     */
    public List<E> findAll();
 
    /**
     * 分頁查找業務實體對象
     * @param page 頁碼
     * @param size 頁面大小
     * @return 查詢結果對象
     */
    public QueryResult<E> findByPage(int page, int size);
 
    /**
     * 分頁查找業務實體對象
     * @param queryBean 查詢條件對象
     * @param page 頁碼
     * @param size 頁面大小
     * @return 查詢結果對象
     */
    public QueryResult<E> findByPage(QueryBean queryBean, int page, int size);
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.jackfrued.dao;
 
import java.io.Serializable;
import java.util.List;
 
import com.jackfrued.comm.QueryBean;
import com.jackfrued.comm.QueryResult;
 
/**
 * BaseDao的缺省適配器
 * @author 駱昊
 *
 * @param <E> 實體類型
 * @param <K> 實體標識字段的類型
 */
public abstract class BaseDaoAdapter<E, K extends Serializable> implements
        BaseDao<E, K> {
 
    @Override
    public K save(E entity) {
        return null;
    }
 
    @Override
    public void delete(E entity) {
    }
 
    @Override
    public boolean deleteById(K id) {
        E entity = findById(id);
        if(entity != null) {
            delete(entity);
            return true;
        }
        return false;
    }
 
    @Override
    public void update(E entity) {
    }
 
    @Override
    public E findById(K id) {
        return null;
    }
 
    @Override
    public E findById(K id, boolean lazy) {
        return null;
    }
 
    @Override
    public List<E> findAll() {
        return null;
    }
 
    @Override
    public QueryResult<E> findByPage(int page, int size) {
        return null;
    }
 
    @Override
    public QueryResult<E> findByPage(QueryBean queryBean, int page, int size) {
        return null;
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package com.jackfrued.dao;
 
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
 
import com.jackfrued.comm.HQLQueryBean;
import com.jackfrued.comm.QueryBean;
import com.jackfrued.comm.QueryResult;
 
/**
 * 基於Hibernate的BaseDao實現類
 * @author 駱昊
 *
 * @param <E> 實體類型
 * @param <K> 主鍵類型
 */
@SuppressWarnings(value = {"unchecked"})
public abstract class BaseDaoHibernateImpl<E, K extends Serializable> extends BaseDaoAdapter<E, K> {
    @Autowired
    protected SessionFactory sessionFactory;
 
    private Class<?> entityClass;       // 業務實體的類對象
    private String entityName;          // 業務實體的名字
 
    public BaseDaoHibernateImpl() {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        entityClass = (Class<?>) pt.getActualTypeArguments()[0];
        entityName = entityClass.getSimpleName();
    }
 
    @Override
    public K save(E entity) {
        return (K) sessionFactory.getCurrentSession().save(entity);
    }
 
    @Override
    public void delete(E entity) {
        sessionFactory.getCurrentSession().delete(entity);
    }
 
    @Override
    public void update(E entity) {
        sessionFactory.getCurrentSession().update(entity);
    }
 
    @Override
    public E findById(K id) {
        return findById(id, false);
    }
 
    @Override
    public E findById(K id, boolean lazy) {
        Session session = sessionFactory.getCurrentSession();
        return (E) (lazy? session.load(entityClass, id) : session.get(entityClass, id));
    }
 
    @Override
    public List<E> findAll() {
        return sessionFactory.getCurrentSession().createCriteria(entityClass).list();
    }
 
    @Override
    public QueryResult<E> findByPage(int page, int size) {
        return new QueryResult<E>(
            findByHQLAndPage("from " + entityName , page, size),
            getCountByHQL("select count(*) from " + entityName)
        );
    }
 
    @Override
    public QueryResult<E> findByPage(QueryBean queryBean, int page, int size) {
        if(queryBean instanceof HQLQueryBean) {
            HQLQueryBean hqlQueryBean = (HQLQueryBean) queryBean;
            return new QueryResult<E>(
                findByHQLAndPage(hqlQueryBean.getQueryString(), page, size, hqlQueryBean.getParameters()),
                getCountByHQL(hqlQueryBean.getCountString(), hqlQueryBean.getParameters())
            );
        }
        return null;
    }
 
    /**
     * 根據HQL和可變參數列表進行查詢
     * @param hql 基於HQL的查詢語句
     * @param params 可變參數列表
     * @return 持有查詢結果的列表容器或空列表容器
     */
    protected List<E> findByHQL(String hql, Object... params) {
        return this.findByHQL(hql, getParamList(params));
    }
 
    /**
     * 根據HQL和參數列表進行查詢
     * @param hql 基於HQL的查詢語句
     * @param params 查詢參數列表
     * @return 持有查詢結果的列表容器或空列表容器
     */
    protected List<E> findByHQL(String hql, List<Object> params) {
        List<E> list = createQuery(hql, params).list();
        return list != null && list.size() > 0 ? list : Collections.EMPTY_LIST;
    }
 
    /**
     * 根據HQL和參數列表進行分頁查詢
     * @param hql 基於HQL的查詢語句
     * @page 頁碼
     * @size 頁面大小
     * @param params 可變參數列表
     * @return 持有查詢結果的列表容器或空列表容器
     */
    protected List<E> findByHQLAndPage(String hql, int page, int size, Object... params) {
        return this.findByHQLAndPage(hql, page, size, getParamList(params));
    }
 
    /**
     * 根據HQL和參數列表進行分頁查詢
     * @param hql 基於HQL的查詢語句
     * @page 頁碼
     * @size 頁面大小
     * @param params 查詢參數列表
     * @return 持有查詢結果的列表容器或空列表容器
     */
    protected List<E> findByHQLAndPage(String hql, int page, int size, List<Object> params) {
        List<E> list = createQuery(hql, params)
                .setFirstResult((page - 1) * size)
                .setMaxResults(size)
                .list();
        return list != null && list.size() > 0 ? list : Collections.EMPTY_LIST;
    }
 
    /**
     * 查詢滿足條件的記錄數
     * @param hql 基於HQL的查詢語句
     * @param params 可變參數列表
     * @return 滿足查詢條件的總記錄數
     */
    protected long getCountByHQL(String hql, Object... params) {
        return this.getCountByHQL(hql, getParamList(params));
    }
 
    /**
     * 查詢滿足條件的記錄數
     * @param hql 基於HQL的查詢語句
     * @param params 參數列表容器
     * @return 滿足查詢條件的總記錄數
     */
    protected long getCountByHQL(String hql, List<Object> params) {
        return (Long) createQuery(hql, params).uniqueResult();
    }
 
    // 創建Hibernate查詢對象(Query)
    private Query createQuery(String hql, List<Object> params) {
        Query query = sessionFactory.getCurrentSession().createQuery(hql);
        for(int i = 0; i < params.size(); i++) {
            query.setParameter(i, params.get(i));
        }
        return query;
    }
 
    // 將可變參數列表組裝成列表容器
    private List<Object> getParamList(Object... params) {
        List<Object> paramList = new ArrayList<>();
        if(params != null) {
            for(int i = 0; i < params.length; i++) {
                paramList.add(params[i]);
            }
        }
        return paramList.size() == 0? Collections.EMPTY_LIST : paramList;
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.jackfrued.comm;
 
import java.util.List;
 
/**
 * 查詢條件的接口
 * @author 駱昊
 *
 */
public interface QueryBean {
 
    /**
     * 添加排序字段
     * @param fieldName 用於排序的字段
     * @param asc 升序還是降序
     * @return 查詢條件對象自身(方便級聯編程)
     */
    public QueryBean addOrder(String fieldName, boolean asc);
 
    /**
     * 添加排序字段
     * @param available 是否添加此排序字段
     * @param fieldName 用於排序的字段
     * @param asc 升序還是降序
     * @return 查詢條件對象自身(方便級聯編程)
     */
    public QueryBean addOrder(boolean available, String fieldName, boolean asc);
 
    /**
     * 添加查詢條件
     * @param condition 條件
     * @param params 替換掉條件中參數佔位符的參數
     * @return 查詢條件對象自身(方便級聯編程)
     */
    public QueryBean addCondition(String condition, Object... params);
 
    /**
     * 添加查詢條件
     * @param available 是否需要添加此條件
     * @param condition 條件
     * @param params 替換掉條件中參數佔位符的參數
     * @return 查詢條件對象自身(方便級聯編程)
     */
    public QueryBean addCondition(boolean available, String condition, Object... params);
 
    /**
     * 獲得查詢語句
     * @return 查詢語句
     */
    public String getQueryString();
 
    /**
     * 獲取查詢記錄數的查詢語句
     * @return 查詢記錄數的查詢語句
     */
    public String getCountString();
 
    /**
     * 獲得查詢參數
     * @return 查詢參數的列表容器
     */
    public List<Object> getParameters();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.jackfrued.comm;
 
import java.util.List;
 
/**
 * 查詢結果
 * @author 駱昊
 *
 * @param <T> 泛型參數
 */
public class QueryResult<T> {
    private List<T> result;     // 持有查詢結果的列表容器
    private long totalRecords;  // 查詢到的總記錄數
 
    /**
     * 構造器
     */
    public QueryResult() {
    }
 
    /**
     * 構造器
     * @param result 持有查詢結果的列表容器
     * @param totalRecords 查詢到的總記錄數
     */
    public QueryResult(List<T> result, long totalRecords) {
        this.result = result;
        this.totalRecords = totalRecords;
    }
 
    public List<T> getResult() {
        return result;
    }
 
    public void setResult(List<T> result) {
        this.result = result;
    }
 
    public long getTotalRecords() {
        return totalRecords;
    }
 
    public void setTotalRecords(long totalRecords) {
        this.totalRecords = totalRecords;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.jackfrued.dao;
 
import com.jackfrued.comm.QueryResult;
import com.jackfrued.entity.Dept;
 
/**
 * 部門數據訪問對象接口
 * @author 駱昊
 *
 */
public interface DeptDao extends BaseDao<Dept, Integer> {
 
    /**
     * 分頁查詢頂級部門
     * @param page 頁碼
     * @param size 頁碼大小
     * @return 查詢結果對象
     */
    public QueryResult<Dept> findTopDeptByPage(int page, int size);
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.jackfrued.dao.impl;
 
import java.util.List;
 
import org.springframework.stereotype.Repository;
 
import com.jackfrued.comm.QueryResult;
import com.jackfrued.dao.BaseDaoHibernateImpl;
import com.jackfrued.dao.DeptDao;
import com.jackfrued.entity.Dept;
 
@Repository
public class DeptDaoImpl extends BaseDaoHibernateImpl<Dept, Integer> implements DeptDao {
    private static final String HQL_FIND_TOP_DEPT = " from Dept as d where d.superiorDept is null ";
 
    @Override
    public QueryResult<Dept> findTopDeptByPage(int page, int size) {
        List<Dept> list = findByHQLAndPage(HQL_FIND_TOP_DEPT, page, size);
        long totalRecords = getCountByHQL(" select count(*) " + HQL_FIND_TOP_DEPT);
        return new QueryResult<>(list, totalRecords);
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.jackfrued.comm;
 
import java.util.List;
 
/**
 * 分頁器
 * @author 駱昊
 *
 * @param <T> 分頁數據對象的類型
 */
public class PageBean<T> {
    private static final int DEFAUL_INIT_PAGE = 1;
    private static final int DEFAULT_PAGE_SIZE = 10;
    private static final int DEFAULT_PAGE_COUNT = 5;
 
    private List<T> data;           // 分頁數據
    private PageRange pageRange;    // 頁碼範圍
    private int totalPage;          // 總頁數
    private int size;               // 頁面大小
    private int currentPage;        // 當前頁碼
    private int pageCount;          // 頁碼數量
 
    /**
     * 構造器
     * @param currentPage 當前頁碼
     * @param size 頁碼大小
     * @param pageCount 頁碼數量
     */
    public PageBean(int currentPage, int size, int pageCount) {
        this.currentPage = currentPage > 0 ? currentPage : 1;
        this.size = size > 0 ? size : DEFAULT_PAGE_SIZE;
        this.pageCount = pageCount > 0 ? size : DEFAULT_PAGE_COUNT;
    }
 
    /**
     * 構造器
     * @param currentPage 當前頁碼
     * @param size 頁碼大小
     */
    public PageBean(int currentPage, int size) {
        this(currentPage, size, DEFAULT_PAGE_COUNT);
    }
 
    /**
     * 構造器
     * @param currentPage 當前頁碼
     */
    public PageBean(int currentPage) {
        this(currentPage, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT);
    }
 
    /**
     * 構造器
     */
    public PageBean() {
        this(DEFAUL_INIT_PAGE, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT);
    }
 
    public List<T> getData() {
        return data;
    }
 
    public int getStartPage() {
        return pageRange != null ? pageRange.getStartPage() : 1;
    }
 
    public int getEndPage() {
        return pageRange != null ? pageRange.getEndPage() : 1;
    }
 
    public long getTotalPage() {
        return totalPage;
    }
 
    public int getSize() {
        return size;
    }
 
    public int getCurrentPage() {
        return currentPage;
    }
 
    /**
     * 將查詢結果轉換爲分頁數據
     * @param queryResult 查詢結果對象
     */
    public void transferQueryResult(QueryResult<T> queryResult) {
        long totalRecords = queryResult.getTotalRecords();
 
        data = queryResult.getResult();
        totalPage = (int) ((totalRecords + size - 1) / size);
        totalPage = totalPage >= 0 ? totalPage : Integer.MAX_VALUE;
        this.pageRange = new PageRange(pageCount, currentPage, totalPage);
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.jackfrued.comm;
 
/**
 * 頁碼範圍
 * @author 駱昊
 *
 */
public class PageRange {
    private int startPage;  // 起始頁碼
    private int endPage;    // 終止頁碼
 
    /**
     * 構造器
     * @param pageCount 總共顯示幾個頁碼
     * @param currentPage 當前頁碼
     * @param totalPage 總頁數
     */
    public PageRange(int pageCount, int currentPage, int totalPage) {
        startPage = currentPage - (pageCount - 1) / 2;
        endPage = currentPage + pageCount / 2;
        if(startPage < 1) {
            startPage = 1;
            endPage = totalPage > pageCount ? pageCount : totalPage;
        }
        if (endPage > totalPage) {
            endPage = totalPage;
            startPage = (endPage - pageCount > 0) ? endPage - pageCount + 1 : 1;
        }
    }
 
    /**
     * 獲得起始頁頁碼
     * @return 起始頁頁碼
     */
    public int getStartPage() {
        return startPage;
    }
 
    /**
     * 獲得終止頁頁碼
     * @return 終止頁頁碼
     */
    public int getEndPage() {
        return endPage;
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.jackfrued.biz;
 
import com.jackfrued.comm.PageBean;
import com.jackfrued.entity.Dept;
 
/**
 * 部門業務邏輯接口
 * @author 駱昊
 *
 */
public interface DeptService {
 
    /**
     * 創建新的部門
     * @param department 部門對象
     * @return 創建成功返回true否則返回false
     */
    public boolean createNewDepartment(Dept department);
 
    /**
     * 刪除指定部門
     * @param id 要刪除的部門的編號
     * @return 刪除成功返回true否則返回false
     */
    public boolean deleteDepartment(Integer id);
 
    /**
     * 分頁獲取頂級部門
     * @param page 頁碼
     * @param size 頁碼大小
     * @return 部門對象的分頁器對象
     */
    public PageBean<Dept> getTopDeptByPage(int page, int size);
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.jackfrued.biz.impl;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import com.jackfrued.biz.DeptService;
import com.jackfrued.comm.PageBean;
import com.jackfrued.comm.QueryResult;
import com.jackfrued.dao.DeptDao;
import com.jackfrued.entity.Dept;
 
@Service
@Transactional  // 聲明式事務的註解
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptDao deptDao;
 
    @Override
    public boolean createNewDepartment(Dept department) {
        return deptDao.save(department) != null;
    }
 
    @Override
    public boolean deleteDepartment(Integer id) {
        return deptDao.deleteById(id);
    }
 
    @Override
    public PageBean<Dept> getTopDeptByPage(int page, int size) {
        QueryResult<Dept> queryResult = deptDao.findTopDeptByPage(page, size);
        PageBean<Dept> pageBean = new PageBean<>(page, size);
        pageBean.transferQueryResult(queryResult);
        return pageBean;
    }
 
}

154、如何在Web項目中配置Spring的IoC容器?
答:如果需要在Web項目中使用Spring的IoC容器,可以在Web項目配置文件web.xml中做出如下配置:

1
2
3
4
5
6
7
8
9
10
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
 
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

155、如何在Web項目中配置Spring MVC?
答:要使用Spring MVC需要在Web項目配置文件中配置其前端控制器DispatcherServlet,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<web-app>
 
    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
 
    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
 
</web-app>

說明:上面的配置中使用了*.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配置:

1
2
3
4
5
6
7
8
9
<bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
 
<context:property-placeholder location="jdbc.properties"/>

C3P0配置:

1
2
3
4
5
6
7
8
9
<bean id="dataSource"
        class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="driverClass" value="${jdbc.driverClassName}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
 
<context:property-placeholder location="jdbc.properties"/>

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:aop="http://www.springframework.org/schema/aop"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
 
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">
 
  <!-- this is the service object that we want to make transactional -->
  <bean id="fooService" class="x.y.service.DefaultFooService"/>
 
  <!-- the transactional advice -->
  <tx:advice id="txAdvice" transaction-manager="txManager">
  <!-- the transactional semantics... -->
  <tx:attributes>
    <!-- all methods starting with 'get' are read-only -->
    <tx:method name="get*" read-only="true"/>
    <!-- other methods use the default transaction settings (see below) -->
    <tx:method name="*"/>
  </tx:attributes>
  </tx:advice>
 
  <!-- ensure that the above transactional advice runs for any execution
    of an operation defined by the FooService interface -->
  <aop:config>
  <aop:pointcut id="fooServiceOperation"
    expression="execution(* x.y.service.FooService.*(..))"/>
  <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
  </aop:config>
 
  <!-- don't forget the DataSource -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
  <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
  <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/>
  <property name="username" value="scott"/>
  <property name="password" value="tiger"/>
  </bean>
 
  <!-- similarly, don't forget the PlatformTransactionManager -->
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
  </bean>
 
  <!-- other <bean/> definitions here -->
 
</beans>

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+)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.jackfrued.bean;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
@Component
public class Person {
    private String name;
    private int age;
    @Autowired
    private Car car;
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    public void setCar(Car car) {
        this.car = car;
    }
 
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.jackfrued.bean;
 
import org.springframework.stereotype.Component;
 
@Component
public class Car {
    private String brand;
    private int maxSpeed;
 
    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }
 
    @Override
    public String toString() {
        return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.jackfrued.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import com.jackfrued.bean.Car;
import com.jackfrued.bean.Person;
 
@Configuration
public class AppConfig {
 
    @Bean
    public Car car() {
        return new Car("Benz", 320);
    }
 
    @Bean
    public Person person() {
        return new Person("駱昊", 34);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.jackfrued.test;
 
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
import com.jackfrued.bean.Person;
import com.jackfrued.config.AppConfig;
 
class Test {
 
    public static void main(String[] args) {
        // TWR (Java 7+)
        try(ConfigurableApplicationContext factory = new AnnotationConfigApplicationContext(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屬性時,通過<list> / <set> / <map> / <props>分別爲其注入列表、集合、映射和鍵值都是字符串的映射屬性。

163、Spring中的自動裝配有哪些限制?
答:
- 如果使用了構造器注入或者setter注入,那麼將覆蓋自動裝配的依賴關係。
- 基本數據類型的值、字符串字面量、類字面量無法使用自動裝配來注入。
- 優先考慮使用顯式的裝配來進行更精確的依賴注入而不是使用自動裝配。

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

1
2
WebApplicationContext ctx =
WebApplicationContextUtils.getWebApplicationContext(servletContext);

165. 大型網站在架構上應當考慮哪些問題?
答:
- 分層:分層是處理任何複雜系統最常見的手段之一,將系統橫向切分成若干個層面,每個層面只承擔單一的職責,然後通過下層爲上層提供的基礎設施和服務以及上層對下層的調用來形成一個完整的複雜的系統。計算機網絡的開放系統互聯參考模型(OSI/RM)和Internet的TCP/IP模型都是分層結構,大型網站的軟件系統也可以使用分層的理念將其分爲持久層(提供數據存儲和訪問服務)、業務層(處理業務邏輯,系統中最核心的部分)和表示層(系統交互、視圖展示)。需要指出的是:(1)分層是邏輯上的劃分,在物理上可以位於同一設備上也可以在不同的設備上部署不同的功能模塊,這樣可以使用更多的計算資源來應對用戶的併發訪問;(2)層與層之間應當有清晰的邊界,這樣分層纔有意義,才更利於軟件的開發和維護。
- 分割:分割是對軟件的縱向切分。我們可以將大型網站的不同功能和服務分割開,形成高內聚低耦合的功能模塊(單元)。在設計初期可以做一個粗粒度的分割,將網站分割爲若干個功能模塊,後期還可以進一步對每個模塊進行細粒度的分割,這樣一方面有助於軟件的開發和維護,另一方面有助於分佈式的部署,提供網站的併發處理能力和功能的擴展。
- 分佈式:除了上面提到的內容,網站的靜態資源(JavaScript、CSS、圖片等)也可以採用獨立分佈式部署並採用獨立的域名,這樣可以減輕應用服務器的負載壓力,也使得瀏覽器對資源的加載更快。數據的存取也應該是分佈式的,傳統的商業級關係型數據庫產品基本上都支持分佈式部署,而新生的NoSQL產品幾乎都是分佈式的。當然,網站後臺的業務處理也要使用分佈式技術,例如查詢索引的構建、數據分析等,這些業務計算規模龐大,可以使用Hadoop以及MapReduce分佈式計算框架來處理。
- 集羣:集羣使得有更多的服務器提供相同的服務,可以更好的提供對併發的支持。
- 緩存:所謂緩存就是用空間換取時間的技術,將數據儘可能放在距離計算最近的位置。使用緩存是網站優化的第一定律。我們通常說的CDN、反向代理、熱點數據都是對緩存技術的使用。
- 異步:異步是實現軟件實體之間解耦合的又一重要手段。異步架構是典型的生產者消費者模式,二者之間沒有直接的調用關係,只要保持數據結構不變,彼此功能實現可以隨意變化而不互相影響,這對網站的擴展非常有利。使用異步處理還可以提高系統可用性,加快網站的響應速度(用Ajax加載數據就是一種異步技術),同時還可以起到削峯作用(應對瞬時高併發)。&quot;能推遲處理的都要推遲處理”是網站優化的第二定律,而異步是踐行網站優化第二定律的重要手段。
- 冗餘:各種服務器都要提供相應的冗餘服務器以便在某臺或某些服務器宕機時還能保證網站可以正常工作,同時也提供了災難恢復的可能性。冗餘是網站高可用性的重要保證。

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等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章