這部分主要是開源Java EE框架方面的內容,包括Hibernate、MyBatis、Spring、Spring MVC等,因爲Struts 2已是明日黃花,在這裏就不討論Struts 2的面試題,若是須要了解相關內容,能夠參考個人另外一篇文章《Java面試題集(86-115)》。此外,這篇文章還對企業應用架構、大型網站架構和應用服務器優化等內容進行了簡單的探討,這些內容相信對面試會頗有幫助。php
12六、什麼是ORM?
答:對象關係映射(Object-Relational Mapping,簡稱ORM)是一種爲了解決程序的面向對象模型與數據庫的關係模型互不匹配問題的技術;簡單的說,ORM是經過使用描述對象和數據庫之間 映射的元數據(在Java中能夠用XML或者是註解),將程序中的對象自動持久化到關係數據庫中或者將關係數據庫表中的行轉換成Java對象,其本質上就 是將數據從一種形式轉換到另一種形式。html
12七、持久層設計要考慮的問題有哪些?你用過的持久層框架有哪些?
答:所謂"持久"就是將數據保存到可掉電式存儲設備中以便從此使用,簡單的說,就是將內存中的數據保存到關係型數據庫、文件系統、消息隊列等提供持久化支持的設備中。持久層就是系統中專一於實現數據持久化的相對獨立的層面。前端
持久層設計的目標包括:
- 數據存儲邏輯的分離,提供抽象化的數據訪問接口。
- 數據訪問底層實現的分離,能夠在不修改代碼的狀況下切換底層實現。
- 資源管理和調度的分離,在數據訪問層實現統一的資源調度(如緩存機制)。
- 數據抽象,提供更面向對象的數據操做。java
持久層框架有:
- Hibernate
- MyBatis
- TopLink
- Guzz
- jOOQ
- Spring Data
- ActiveJDBCmysql
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方法再也不是對二級緩存只寫不讀,它也是能夠訪問二級緩存的。web
說明:對於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()方法是把一個沒有更改過的脫管狀態的對象變成持久狀態。正則表達式
13一、闡述Session加載實體對象的過程。
答:Session加載實體對象的步驟是:
① Session在調用數據庫查詢功能以前,首先會在一級緩存中經過實體類型和主鍵進行查找,若是一級緩存查找命中且數據狀態合法,則直接返回;
② 若是一級緩存沒有命中,接下來Session會在當前NonExists記錄(至關於一個查詢黑名單,若是出現重複的無效查詢能夠迅速作出判斷,從而提高性能)中進行查找,若是NonExists中存在一樣的查詢條件,則返回null;
③ 若是一級緩存查詢失敗則查詢二級緩存,若是二級緩存命中則直接返回;
④ 若是以前的查詢都未命中,則發出SQL語句,若是查詢未發現對應記錄則將這次查詢添加到Session的NonExists中加以記錄,並返回null;
⑤ 根據映射配置和SQL語句獲得ResultSet,並建立對應的實體對象;
⑥ 將對象歸入Session(一級緩存)的管理;
⑦ 若是有對應的攔截器,則執行攔截器的onLoad方法;
⑧ 若是開啓並設置了要使用二級緩存,則將數據對象歸入二級緩存;
⑨ 返回數據對象。spring
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
下面是映射文件的片斷。
<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>
固然也能夠像下面這些書寫。
<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>
再看看下面這個例子。
<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類其實並不困難,代碼以下所示:
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語句得到真實對象。
下面用一個找槍手代考的例子演示代理模式的使用:
/** * 參考人員接口 * @author 駱昊 * */ public interface Candidate { /** * 答題 */ public void answerTheQuestions(); }
/** * 懶學生 * @author 駱昊 * */ public class LazyStudent implements Candidate { private String name; // 姓名 public LazyStudent(String name) { this.name = name; } @Override public void answerTheQuestions() { // 懶學生只能寫出本身的名字不會答題 System.out.println("姓名: " + name); } }
/** * 槍手 * @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("交卷"); } }
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的大小:
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; } }
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配置文件中增長以下配置:
<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服務器提供)等。
編程式事務管理以下所示。
<?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>
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; } }
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和業務邏輯代碼。
<?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>
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);
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; } }
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; } }
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(); }
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; } }
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); }
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); } }
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); } }
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; } }
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); }
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中作出以下配置:
<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,以下所示:
<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配置:
<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配置:
<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八、如何配置配置事務加強?
答:
<?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+)
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 + "]"; } }
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 + "]"; } }
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); }
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容器?
答:
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等。