來源:http://www.importnew.com/22087.htmlhtml
這部分主要是開源Java EE框架方面的內容,包括hibernate、MyBatis、spring、Spring MVC等,因爲Struts 2已是明日黃花,在這裏就不討論Struts 2的面試題,若是須要了解相關內容,能夠參考個人另外一篇文章《Java面試題集(86-115)》。此外,這篇文章還對企業應用架構、大型網站架構和應用服務器優化等內容進行了簡單的探討,這些內容相信對面試會頗有幫助。前端
12六、什麼是ORM?
答:對象關係映射(Object-Relational Mapping,簡稱ORM)是一種爲了解決程序的面向對象模型與數據庫的關係模型互不匹配問題的技術;簡單的說,ORM是經過使用描述對象和數據庫之間映射的元數據(在Java中能夠用XML或者是註解),將程序中的對象自動持久化到關係數據庫中或者將關係數據庫表中的行轉換成Java對象,其本質上就是將數據從一種形式轉換到另一種形式。java
12七、持久層設計要考慮的問題有哪些?你用過的持久層框架有哪些?
答:所謂」持久」就是將數據保存到可掉電式存儲設備中以便從此使用,簡單的說,就是將內存中的數據保存到關係型數據庫、文件系統、消息隊列等提供持久化支持的設備中。持久層就是系統中專一於實現數據持久化的相對獨立的層面。mysql
持久層設計的目標包括:
- 數據存儲邏輯的分離,提供抽象化的數據訪問接口。
- 數據訪問底層實現的分離,能夠在不修改代碼的狀況下切換底層實現。
- 資源管理和調度的分離,在數據訪問層實現統一的資源調度(如緩存機制)。
- 數據抽象,提供更面向對象的數據操做。程序員
持久層框架有:
- Hibernate
- MyBatis
- TopLink
- Guzz
- jOOQ
- Spring Data
- ActiveJDBCweb
12八、Hibernate中SessionFactory是線程安全的嗎?Session是線程安全的嗎(兩個線程可以共享同一個Session嗎)?
答:SessionFactory對應Hibernate的一個數據存儲的概念,它是線程安全的,能夠被多個線程併發訪問。SessionFactory通常只會在啓動的時候構建。對於應用程序,最好將SessionFactory經過單例模式進行封裝以便於訪問。Session是一個輕量級非線程安全的對象(線程間不能共享session),它表示與數據庫進行交互的一個工做單元。Session是由SessionFactory建立的,在任務完成以後它會被關閉。Session是持久層服務對外提供的主要接口。Session會延遲獲取數據庫鏈接(也就是在須要的時候纔會獲取)。爲了不建立太多的session,能夠使用ThreadLocal將session和當前線程綁定在一塊兒,這樣可讓同一個線程得到的老是同一個session。Hibernate 3中SessionFactory的getCurrentSession()方法就能夠作到。面試
12九、Hibernate中Session的load和get方法的區別是什麼?
答:主要有如下三項區別:
① 若是沒有找到符合條件的記錄,get方法返回null,load方法拋出異常。
② get方法直接返回實體類對象,load方法返回實體類對象的代理。
③ 在Hibernate 3以前,get方法只在一級緩存中進行數據查找,若是沒有找到對應的數據則越過二級緩存,直接發出SQL語句完成數據讀取;load方法則能夠從二級緩存中獲取數據;從Hibernate 3開始,get方法再也不是對二級緩存只寫不讀,它也是能夠訪問二級緩存的。正則表達式
說明:對於load()方法Hibernate認爲該數據在數據庫中必定存在能夠放心的使用代理來實現延遲加載,若是沒有數據就拋出異常,而經過get()方法獲取的數據能夠不存在。spring
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()方法是把一個沒有更改過的脫管狀態的對象變成持久狀態。sql
13一、闡述Session加載實體對象的過程。
答:Session加載實體對象的步驟是:
① Session在調用數據庫查詢功能以前,首先會在一級緩存中經過實體類型和主鍵進行查找,若是一級緩存查找命中且數據狀態合法,則直接返回;
② 若是一級緩存沒有命中,接下來Session會在當前NonExists記錄(至關於一個查詢黑名單,若是出現重複的無效查詢能夠迅速作出判斷,從而提高性能)中進行查找,若是NonExists中存在一樣的查詢條件,則返回null;
③ 若是一級緩存查詢失敗則查詢二級緩存,若是二級緩存命中則直接返回;
④ 若是以前的查詢都未命中,則發出SQL語句,若是查詢未發現對應記錄則將這次查詢添加到Session的NonExists中加以記錄,並返回null;
⑤ 根據映射配置和SQL語句獲得ResultSet,並建立對應的實體對象;
⑥ 將對象歸入Session(一級緩存)的管理;
⑦ 若是有對應的攔截器,則執行攔截器的onLoad方法;
⑧ 若是開啓並設置了要使用二級緩存,則將數據對象歸入二級緩存;
⑨ 返回數據對象。
13二、Query接口的list方法和iterate方法有什麼區別?
答:
① list()方法沒法利用一級緩存和二級緩存(對緩存只寫不讀),它只能在開啓查詢緩存的前提下使用查詢緩存;iterate()方法能夠充分利用緩存,若是目標數據只讀或者讀取頻繁,使用iterate()方法能夠減小性能開銷。
② list()方法不會引發N+1查詢問題,而iterate()方法可能引發N+1查詢問題
說明:關於N+1查詢問題,能夠參考CSDN上的一篇文章《什麼是N+1查詢》
13三、Hibernate如何實現分頁查詢?
答:經過Hibernate實現分頁查詢,開發人員只須要提供HQL語句(調用Session的createQuery()方法)或查詢條件(調用Session的createCriteria()方法)、設置查詢起始行數(調用Query或Criteria接口的setFirstResult()方法)和最大查詢行數(調用Query或Criteria接口的setMaxResults()方法),並調用Query或Criteria接口的list()方法,Hibernate會自動生成分頁查詢的SQL語句。
13四、鎖機制有什麼用?簡述Hibernate的悲觀鎖和樂觀鎖機制。
答:有些業務邏輯在執行過程當中要求對數據進行排他性的訪問,因而須要經過一些機制保證在此過程當中數據被鎖住不會被外界修改,這就是所謂的鎖機制。
Hibernate支持悲觀鎖和樂觀鎖兩種鎖機制。悲觀鎖,顧名思義悲觀的認爲在數據處理過程當中極有可能存在修改數據的併發事務(包括本系統的其餘事務或來自外部系統的事務),因而將處理的數據設置爲鎖定狀態。悲觀鎖必須依賴數據庫自己的鎖機制才能真正保證數據訪問的排他性,關於數據庫的鎖機制和事務隔離級別在《Java面試題大全(上)》中已經討論過了。樂觀鎖,顧名思義,對併發事務持樂觀態度(認爲對數據的併發操做不會常常性的發生),經過更加寬鬆的鎖機制來解決因爲悲觀鎖排他性的數據訪問對系統性能形成的嚴重影響。最多見的樂觀鎖是經過數據版本標識來實現的,讀取數據時得到數據的版本號,更新數據時將此版本號加1,而後和數據庫表對應記錄的當前版本號進行比較,若是提交的數據版本號大於數據庫中此記錄的當前版本號則更新數據,不然認爲是過時數據沒法更新。Hibernate中經過Session的get()和load()方法從數據庫中加載對象時能夠經過參數指定使用悲觀鎖;而樂觀鎖能夠經過給實體類加整型的版本字段再經過XML或@Version註解進行配置。
提示:使用樂觀鎖會增長了一個版本字段,很明顯這須要額外的空間來存儲這個版本字段,浪費了空間,可是樂觀鎖會讓系統具備更好的併發性,這是對時間的節省。所以樂觀鎖也是典型的空間換時間的策略。
13五、闡述實體對象的三種狀態以及轉換關係。
答:最新的Hibernate文檔中爲Hibernate對象定義了四種狀態(原來是三種狀態,面試的時候基本上問的也是三種狀態),分別是:瞬時態(new, or transient)、持久態(managed, or persistent)、遊狀態(detached)和移除態(removed,之前Hibernate文檔中定義的三種狀態中沒有移除態),以下圖所示,就之前的Hibernate文檔中移除態被視爲是瞬時態。
提示:關於這個問題,在Hibernate的官方文檔中有更爲詳細的解讀。
13六、如何理解Hibernate的延遲加載機制?在實際應用中,延遲加載與Session關閉的矛盾是如何處理的?
答:延遲加載就是並非在讀取的時候就把數據加載進來,而是等到使用時再加載。Hibernate使用了虛擬代理機制實現延遲加載,咱們使用Session的load()方法加載數據或者一對多關聯映射在使用延遲加載的狀況下從一的一方加載多的一方,獲得的都是虛擬代理,簡單的說返回給用戶的並非實體自己,而是實體對象的代理。代理對象在用戶調用getter方法時纔會去數據庫加載數據。但加載數據就須要數據庫鏈接。而當咱們把會話關閉時,數據庫鏈接就同時關閉了。
延遲加載與session關閉的矛盾通常能夠這樣處理:
① 關閉延遲加載特性。這種方式操做起來比較簡單,由於Hibernate的延遲加載特性是能夠經過映射文件或者註解進行配置的,但這種解決方案存在明顯的缺陷。首先,出現」no session or session was closed」一般說明系統中已經存在主外鍵關聯,若是去掉延遲加載的話,每次查詢的開銷都會變得很大。
② 在session關閉以前先獲取須要查詢的數據,能夠使用工具方法Hibernate.isInitialized()判斷對象是否被加載,若是沒有被加載則能夠使用Hibernate.initialize()方法加載對象。
③ 使用攔截器或過濾器延長Session的生命週期直到視圖得到數據。Spring整合Hibernate提供的OpenSessionInViewFilter和OpenSessionInViewInterceptor就是這種作法。
13七、舉一個多對多關聯的例子,並說明如何實現多對多關聯映射。
答:例如:商品和訂單、學生和課程都是典型的多對多關係。能夠在實體類上經過@ManyToMany註解配置多對多關聯或者經過映射文件中的和標籤配置多對多關聯,可是實際項目開發中,不少時候都是將多對多關聯映射轉換成兩個多對一關聯映射來實現的。
13八、談一下你對繼承映射的理解。
答:繼承關係的映射策略有三種:
① 每一個繼承結構一張表(table per class hierarchy),無論多少個子類都用一張表。
② 每一個子類一張表(table per subclass),公共信息放一張表,特有信息放單獨的表。
③ 每一個具體類一張表(table per concrete class),有多少個子類就有多少張表。
第一種方式屬於單表策略,其優勢在於查詢子類對象的時候無需錶鏈接,查詢速度快,適合多態查詢;缺點是可能致使表很大。後兩種方式屬於多表策略,其優勢在於數據存儲緊湊,其缺點是須要進行鏈接查詢,不適合多態查詢。
13九、簡述Hibernate常見優化策略。
答:這個問題應當挑本身使用過的優化策略回答,經常使用的有:
① 制定合理的緩存策略(二級緩存、查詢緩存)。
② 採用合理的Session管理機制。
③ 儘可能使用延遲加載特性。
④ 設定合理的批處理參數。
⑤ 若是能夠,選用UUID做爲主鍵生成器。
⑥ 若是能夠,選用基於版本號的樂觀鎖替代悲觀鎖。
⑦ 在開發過程當中, 開啓hibernate.show_sql選項查看生成的SQL,從而瞭解底層的情況;開發完成後關閉此選項。
⑧ 考慮數據庫自己的優化,合理的索引、恰當的數據分區策略等都會對持久層的性能帶來可觀的提高,但這些須要專業的DBA(數據庫管理員)提供支持。
140、談一談Hibernate的一級緩存、二級緩存和查詢緩存。
答:Hibernate的Session提供了一級緩存的功能,默認老是有效的,當應用程序保存持久化實體、修改持久化實體時,Session並不會當即把這種改變提交到數據庫,而是緩存在當前的Session中,除非顯示調用了Session的flush()方法或經過close()方法關閉Session。經過一級緩存,能夠減小程序與數據庫的交互,從而提升數據庫訪問性能。
SessionFactory級別的二級緩存是全局性的,全部的Session能夠共享這個二級緩存。不過二級緩存默認是關閉的,須要顯示開啓並指定須要使用哪一種二級緩存實現類(能夠使用第三方提供的實現)。一旦開啓了二級緩存並設置了須要使用二級緩存的實體類,SessionFactory就會緩存訪問過的該實體類的每一個對象,除非緩存的數據超出了指定的緩存空間。
一級緩存和二級緩存都是對整個實體進行緩存,不會緩存普通屬性,若是但願對普通屬性進行緩存,能夠使用查詢緩存。查詢緩存是將HQL或SQL語句以及它們的查詢結果做爲鍵值對進行緩存,對於一樣的查詢能夠直接從緩存中獲取數據。查詢緩存默認也是關閉的,須要顯示開啓。
14一、Hibernate中DetachedCriteria類是作什麼的?
答:DetachedCriteria和Criteria的用法基本上是一致的,但Criteria是由Session的createCriteria()方法建立的,也就意味着離開建立它的Session,Criteria就沒法使用了。DetachedCriteria不須要Session就能夠建立(使用DetachedCriteria.forClass()方法建立),因此一般也稱其爲離線的Criteria,在須要進行查詢操做的時候再和Session綁定(調用其getExecutableCriteria(Session)方法),這也就意味着一個DetachedCriteria能夠在須要的時候和不一樣的Session進行綁定。
14二、@OneToMany註解的mappedBy屬性有什麼做用?
答:@OneToMany用來配置一對多關聯映射,但一般狀況下,一對多關聯映射都由多的一方來維護關聯關係,例如學生和班級,應該在學生類中添加班級屬性來維持學生和班級的關聯關係(在數據庫中是由學生表中的外鍵班級編號來維護學生表和班級表的多對一關係),若是要使用雙向關聯,在班級類中添加一個容器屬性來存放學生,並使用@OneToMany註解進行映射,此時mappedBy屬性就很是重要。若是使用XML進行配置,能夠用<set>標籤的inverse=」true」設置來達到一樣的效果。
14三、MyBatis中使用#
和$
書寫佔位符有什麼區別?
答:#
將傳入的數據都當成一個字符串,會對傳入的數據自動加上引號;$
將傳入的數據直接顯示生成在SQL中。注意:使用$
佔位符可能會致使SQL注射攻擊,能用#
的地方就不要使用$
,寫order by子句的時候應該用$
而不是#
。
14四、解釋一下MyBatis中命名空間(namespace)的做用。
答:在大型項目中,可能存在大量的SQL語句,這時候爲每一個SQL語句起一個惟一的標識(ID)就變得並不容易了。爲了解決這個問題,在MyBatis中,能夠爲每一個映射文件起一個惟一的命名空間,這樣定義在這個映射文件中的每一個SQL語句就成了定義在這個命名空間中的一個ID。只要咱們可以保證每一個命名空間中這個ID是惟一的,即便在不一樣映射文件中的語句ID相同,也不會再產生衝突了。
14五、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>
|
14六、什麼是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注入須要類提供無參構造器或者無參的靜態工廠方法來建立對象。
14七、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());
}
}
|
14八、解釋一下什麼叫AOP(面向切面編程)?
答:AOP(Aspect-Oriented Programming)指一種程序設計範型,該範型以一種稱爲切面(aspect)的語言構造爲基礎,切面是一種新的模塊化機制,用來描述分散在對象、類或方法中的橫切關注點(crosscutting concern)。
14九、你是如何理解」橫切關注」這個概念的?
答:」橫切關注」是會影響到整個應用程序的關注功能,它跟正常的業務邏輯是正交的,沒有必然的聯繫,可是幾乎全部的業務邏輯都會涉及到這些關注功能。一般,事務、日誌、安全性等關注就是應用中的橫切關注功能。
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經過繼承的方式爲其建立代理。
15一、Spring中自動裝配的方式有哪些?
答:
- no:不進行自動裝配,手動設置Bean的依賴關係。
- byName:根據Bean的名字進行自動裝配。
- byType:根據Bean的類型進行自動裝配。
- constructor:相似於byType,不過是應用於構造器的參數,若是正好有一個Bean與構造器的參數類型相同則能夠自動裝配,不然會致使錯誤。
- autodetect:若是有默認的構造器,則經過constructor的方式進行自動裝配,不然使用byType的方式進行自動裝配。
說明:自動裝配沒有自定義裝配方式那麼精確,並且不能自動裝配簡單屬性(基本類型、字符串等),在使用時應注意。
15二、Spring中如何使用註解來配置Bean?有哪些相關的註解?
答:首先須要在Spring配置文件中增長以下配置:
1
|
<context:component-scan base-
package
=
"org.example"
/>
|
而後能夠用@Component、@Controller、@Service、@Repository註解來標註須要由Spring IoC容器進行對象託管的類。這幾個註解沒有本質區別,只不過@Controller一般用於控制器,@Service一般用於業務邏輯類,@Repository一般用於倉儲類(例如咱們的DAO實現類),普通的類用@Component來標註。
15三、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;
}
}
|
15四、如何在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>
|
15五、如何在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推斷採用了何種服務器端的技術,另外一方面能夠欺騙搜索引擎,由於搜索引擎不會搜索動態頁面,這種作法稱爲僞靜態化。
15六、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文件。
15七、如何在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題中已經完整的展現過了。
15八、如何配置配置事務加強?
答:
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>
|
15九、選擇使用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);
}
}
}
|
16一、闡述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方法。
16二、依賴注入時如何注入集合屬性?
答:能夠在定義Bean屬性時,經過<list> / <set> / <map> / <props>分別爲其注入列表、集合、映射和鍵值都是字符串的映射屬性。
16三、Spring中的自動裝配有哪些限制?
答:
- 若是使用了構造器注入或者setter注入,那麼將覆蓋自動裝配的依賴關係。
- 基本數據類型的值、字符串字面量、類字面量沒法使用自動裝配來注入。
- 優先考慮使用顯式的裝配來進行更精確的依賴注入而不是使用自動裝配。
16四、在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加載數據就是一種異步技術),同時還能夠起到削峯做用(應對瞬時高併發)。";能推遲處理的都要推遲處理」是網站優化的第二定律,而異步是踐行網站優化第二定律的重要手段。
- 冗餘:各類服務器都要提供相應的冗餘服務器以便在某臺或某些服務器宕機時還能保證網站能夠正常工做,同時也提供了災難恢復的可能性。冗餘是網站高可用性的重要保證。
16六、你用過的網站前端優化的技術有哪些?
答:
① 瀏覽器訪問優化:
- 減小HTTP請求數量:合併CSS、合併JavaScript、合併圖片(CSS Sprite)
- 使用瀏覽器緩存:經過設置HTTP響應頭中的Cache-Control和Expires屬性,將CSS、JavaScript、圖片等在瀏覽器中緩存,當這些靜態資源須要更新時,能夠更新HTML文件中的引用來讓瀏覽器從新請求新的資源
- 啓用壓縮
- CSS前置,JavaScript後置
- 減小Cookie傳輸
② CDN加速:CDN(Content Distribute Network)的本質仍然是緩存,將數據緩存在離用戶最近的地方,CDN一般部署在網絡運營商的機房,不只能夠提高響應速度,還能夠減小應用服務器的壓力。固然,CDN緩存的一般都是靜態資源。
③ 反向代理:反向代理至關於應用服務器的一個門面,能夠保護網站的安全性,也能夠實現負載均衡的功能,固然最重要的是它緩存了用戶訪問的熱點資源,能夠直接從反向代理將某些內容返回給用戶瀏覽器。
16七、你使用過的應用服務器優化技術有哪些?
答:
① 分佈式緩存:緩存的本質就是內存中的哈希表,若是設計一個優質的哈希函數,那麼理論上哈希表讀寫的漸近時間複雜度爲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提供了必要的基礎。
- 資源複用:資源複用主要有兩種方式,一是單例,二是對象池,咱們使用的數據庫鏈接池、線程池都是對象池化技術,這是典型的用空間換取時間的策略,另外一方面也實現對資源的複用,從而避免了沒必要要的建立和釋放資源所帶來的開銷。
16八、什麼是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等。