1. 程式人生 > >Java修煉之道--Web

Java修煉之道--Web

前言

在本文中將總結 Java Web 開發技術和相關框架的核心知識。因框架知識體系比較龐大,具體每個框架的使用我將放在 ../JavaWeb 這個目錄下,包含 Spring、Strust2、Hibernate、Spring Boot 等框架。

  • Spring
  • Strust2
  • Hibernate
  • Mybatis
  • Spring MVC
  • Spring Boot
  • Dubbo

在面試指南中將列舉面試中常見的考點,包含Servlet、JSP、Spring、中介軟體等常考Java Web框架知識

參考資料:

一、Servlet / JSP / Web

1. 什麼是Servlet

Servlet 是在伺服器上執行的小程式。一個 servlet 就是一個 Java 類,並且可以通過 “請求—響應” 程式設計模式來訪問的這個駐留在伺服器記憶體裡的 servlet 程式。

類的繼承關係如下:

Servlet三種實現方式:

  • 實現javax.servlet.Servlet介面

  • 繼承javax.servlet.GenericServlet類

  • 繼承javax.servlet.http.HttpServlet類

通常會去繼承HttpServlet類來完成Servlet。

2. Tomcat容器等級

Tomcat的容器分為4個等級,Servlet的容器管理Context容器,一個Context對應一個Web工程。

3. Servlet執行流程

主要描述了從瀏覽器到伺服器,再從伺服器到瀏覽器的整個執行過程

瀏覽器請求

瀏覽器向伺服器請求時,伺服器不會直接執行我們的類,而是到 web.xml 裡尋找路徑名 ① 瀏覽器輸入訪問路徑後,攜帶了請求行,頭,體 ② 根據訪問路徑找到已註冊的 servlet 名稱 ③ 根據對映找到對應的 servlet 名 ④ 根據根據 servlet 名找到我們全限定類名,既我們自己寫的類

伺服器建立物件

① 伺服器找到全限定類名後,通過反射建立物件,同時也建立了 servletConfig,裡面存放了一些初始化資訊(注意伺服器只會建立一次 servlet 物件,所以 servletConfig 也只有一個)

呼叫init方法

① 物件建立好之後,首先要執行 init 方法,但是我們發現我們自定義類下沒有 init 方法,所以程式會到其父類 HttpServlet 裡找 ② 我們發現 HttpServlet 裡也沒有 init 方法,所以繼續向上找,既向其父類 GenericServlet 中繼續尋找,在 GenericServlet 中我們發現了 init 方法,則執行 init 方法(對介面 Servlet 中的 init 方法進行了重寫)

注意: 在 GenericServlet 中執行 public void init(ServletConfig config) 方法的時候,又呼叫了自己無參無方法體的 init() 方法,其目的是為了方便開發者,如果開發者在初始化的過程中需要實現一些功能,可以重寫此方法。

呼叫service方法

接著,伺服器會先建立兩個物件:ServletRequest 請求物件和 ServletResponse 響應物件,用來封裝瀏覽器的請求資料和封裝向瀏覽器的響應資料 ① 接著伺服器會預設在我們寫的類裡尋找 service(ServletRequest req, ServletResponse res) 方法,但是 DemoServlet 中不存在,那麼會到其父類中尋找 ② 到父類 HttpServlet 中發現有此方法,則直接呼叫此方法,並將之前建立好的兩個物件傳入 ③ 然後將傳入的兩個引數強轉,並呼叫 HttpServlet 下的另外個 service 方法 ④ 接著執行 service(HttpServletRequest req, HttpServletResponse resp)方法,在此方法內部進行了判斷請求方式,並執行doGet和doPost,但是doGet和doPost方法已經被我們自己重寫了,所以會執行我們重寫的方法 看到這裡,你或許有疑問:為什麼我們不直接重寫service方法? 因為如果重寫service方法的話,我們需要將強轉,以及一系列的安全保護判斷重新寫一遍,會存在安全隱患

向瀏覽器響應

4. Servlet生命週期

  • void init(ServletConfig servletConfig):Servlet物件建立之後馬上執行的初始化方法,只執行一次;
  • void service(ServletRequest servletRequest, ServletResponse servletResponse):每次處理請求都是在呼叫這個方法,它會被呼叫多次;
  • void destroy():在Servlet被銷燬之前呼叫,負責釋放 Servlet 物件佔用的資源的方法;

特性:

  • 執行緒不安全的,所以它的效率高。
  • 單例,一個類只有一個物件,當然可能存在多個 Servlet 類

Servlet 類由自己編寫,但物件由伺服器來建立,並由伺服器來呼叫相應的方法

伺服器啟動時 ( web.xml中配置load-on-startup=1,預設為0 ) 或者第一次請求該 servlet 時,就會初始化一個 Servlet 物件,也就是會執行初始化方法 init(ServletConfig conf)

該 servlet 物件去處理所有客戶端請求,在 service(ServletRequest req,ServletResponse res) 方法中執行

最後伺服器關閉時,才會銷燬這個 servlet 物件,執行 destroy() 方法。

總結(面試會問):

1)Servlet何時建立

預設第一次訪問servlet時建立該物件(呼叫init()方法)

2)Servlet何時銷燬

伺服器關閉servlet就銷燬了(呼叫destroy()方法)

3)每次訪問必須執行的方法

public void service(ServletRequest arg0, ServletResponse arg1)

5. Tomcat裝載Servlet的三種情況

  1. Servlet容器啟動時自動裝載某些Servlet,實現它只需要在web.xml檔案中的 <servlet></servlet> 之間新增以下程式碼:
<load-on-startup>1</load-on-startup>

其中,數字越小表示優先順序越高。

例如:我們在 web.xml 中設定 TestServlet2 的優先順序為 1,而 TestServlet1 的優先順序為 2,啟動和關閉Tomcat:優先順序高的先啟動也先關閉。

  1. 客戶端首次向某個Servlet傳送請求

  2. Servlet 類被修改後,Tomcat 容器會重新裝載 Servlet。

6. forward和redirect

本節參考:《Java程式設計師面試筆試寶典》P172

在設計 Web 應用程式時,經常需要把一個系統進行結構化設計,即按照模組進行劃分,讓不同的 Servlet 來實現不同的功能,例如可以讓其中一個 Servlet 接收使用者的請求,另外一個 Servlet 來處理使用者的請求。為了實現這種程式的模組化,就需要保證在不同的 Servlet 之間可以相互跳轉,而 Servlet 中主要有兩種實現跳轉的方式:forward 與 redirect 方式。

forward 是伺服器內部的重定向,伺服器直接訪問目標地址的 URL,把那個 URL 的響應內容讀取過來,而客戶端並不知道,因此在客戶端瀏覽器的位址列中不會顯示轉向後的地址,還是原來的地址。由於在整個定向的過程中用的是同一個 Request,因此 forward 會將 Request 的資訊帶到被定向的 JSP 或 Servlet 中使用。

redirect 則是客戶端的重定向,是完全的跳轉,即客戶端瀏覽器會獲取到跳轉後的地址,然後重新發送請求,因此瀏覽器中會顯示跳轉後的地址。同事,由於這種方式比 forward 方式多了一次網路請求,因此其效率要低於 forward 方式。需要注意的是,客戶端的重定向可以通過設定特定的 HTTP 頭或改寫 JavaScript 指令碼實現。

下圖可以更好的說明二者的區別:

鑑於以上的區別,一般當 forward 方式可以滿足需求時,儘可能地使用 forward 方式。但在有些情況下,例如,需要跳轉到下一個其他伺服器上的資源,則必須使用 redirect 方式。

引申:filter的作用是什麼?主要實現什麼方法?

filter 使使用者可以改變一個 request 並且修改一個 response。filter 不是一個 Servlet,它不能產生一個 response,但它能夠在一個 request 到達 Servlet 之前預處理 request,也可以在離開 Servlet 時處理 response。filter 其實是一個 “Servlet Chaining” (Servler 鏈)。

一個 filter 的作用包括以下幾個方面:

1)在 Servlet 被呼叫之前截獲

2)在 Servlet 被呼叫之前檢查 Servlet Request

3)根據需要修改 Request 頭和 Request 資料

4)根據需要修改 Response 頭和 Response 資料

5)在 Servlet 被呼叫之後截獲

7. Jsp和Servlet的區別

1、不同之處在哪?

  • Servlet 在 Java 程式碼中通過 HttpServletResponse 物件動態輸出 HTML 內容
  • JSP 在靜態 HTML 內容中嵌入 Java 程式碼,Java 程式碼被動態執行後生成 HTML 內容

2、各自的特點

  • Servlet 能夠很好地組織業務邏輯程式碼,但是在 Java 原始檔中通過字串拼接的方式生成動態 HTML 內容會導致程式碼維護困難、可讀性差
  • JSP 雖然規避了 Servlet 在生成 HTML 內容方面的劣勢,但是在 HTML 中混入大量、複雜的業務邏輯同樣也是不可取的

3、通過MVC雙劍合璧

既然 JSP 和 Servlet 都有自身的適用環境,那麼能否揚長避短,讓它們發揮各自的優勢呢?答案是肯定的——MVC(Model-View-Controller)模式非常適合解決這一問題。

MVC模式(Model-View-Controller)是軟體工程中的一種軟體架構模式,把軟體系統分為三個基本部分:模型(Model)、檢視(View)和控制器(Controller):

  • Controller——負責轉發請求,對請求進行處理
  • View——負責介面顯示
  • Model——業務功能編寫(例如演算法實現)、資料庫設計以及資料存取操作實現

在 JSP/Servlet 開發的軟體系統中,這三個部分的描述如下所示:

  1. Web 瀏覽器傳送 HTTP 請求到服務端,被 Controller(Servlet) 獲取並進行處理(例如引數解析、請求轉發)
  2. Controller(Servlet) 呼叫核心業務邏輯——Model部分,獲得結果
  3. Controller(Servlet) 將邏輯處理結果交給 View(JSP),動態輸出 HTML 內容
  4. 動態生成的 HTML 內容返回到瀏覽器顯示

MVC 模式在 Web 開發中的好處是非常明顯,它規避了 JSP 與 Servlet 各自的短板,Servlet 只負責業務邏輯而不會通過 out.append() 動態生成 HTML 程式碼;JSP 中也不會充斥著大量的業務程式碼。這大大提高了程式碼的可讀性和可維護性。

8. tomcat和Servlet的聯絡

Tomcat是Web應用伺服器,是一個Servlet/JSP容器。Tomcat 作為 Servlet 容器,負責處理客戶請求,把請求傳送給Servlet,並將Servlet的響應傳送回給客戶。而 Servlet 是一種執行在支援 Java 語言的伺服器上的元件。Servlet最常見的用途是擴充套件 Java Web 伺服器功能,提供非常安全的,可移植的,易於使用的CGI替代品。

從 http 協議中的請求和響應可以得知,瀏覽器發出的請求是一個請求文字,而瀏覽器接收到的也應該是一個響應文字。但是在上面這個圖中,並不知道是如何轉變的,只知道瀏覽器傳送過來的請求也就是 request,我們響應回去的就用 response。忽略了其中的細節,現在就來探究一下。

① Tomcat 將 http 請求文字接收並解析,然後封裝成 HttpServletRequest 型別的 request 物件,所有的 HTTP 頭資料讀可以通過 request 物件呼叫對應的方法查詢到。

② Tomcat 同時會要響應的資訊封裝為 HttpServletResponse 型別的 response 物件,通過設定 response 屬性就可以控制要輸出到瀏覽器的內容,然後將 response 交給 tomcat,tomcat 就會將其變成響應文字的格式傳送給瀏覽器

Java Servlet API 是 Servlet 容器(tomcat) 和 servlet 之間的介面,它定義了 serlvet 的各種方法,還定義了 Servlet 容器傳送給 Servlet 的物件類,其中最重要的就是 ServletRequest 和 ServletResponse。所以說我們在編寫 servlet 時,需要實現 Servlet 介面,按照其規範進行操作。

9. cookie和session的區別

類似這種面試題,實際上都屬於“開放性”問題,你扯到哪裡都可以。不過如果我是面試官的話,我還是希望對方能做到一點——不要混淆 session 和 session 實現。

本來 session 是一個抽象概念,開發者為了實現中斷和繼續等操作,將 user agent 和 server 之間一對一的互動,抽象為“會話”,進而衍生出“會話狀態”,也就是 session 的概念。

而 cookie 是一個實際存在的東西,http 協議中定義在 header 中的欄位。可以認為是 session 的一種後端無狀態實現。

而我們今天常說的 “session”,是為了繞開 cookie 的各種限制,通常藉助 cookie 本身和後端儲存實現的,一種更高階的會話狀態實現。

所以 cookie 和 session,你可以認為是同一層次的概念,也可以認為是不同層次的概念。具體到實現,session 因為 session id 的存在,通常要藉助 cookie 實現,但這並非必要,只能說是通用性較好的一種實現方案。

引申

  1. 由於 HTTP 協議是無狀態的協議,所以服務端需要記錄使用者的狀態時,就需要用某種機制來識具體的使用者,這個機制就是 Session。典型的場景比如購物車,當你點選下單按鈕時,由於 HTTP 協議無狀態,所以並不知道是哪個使用者操作的,所以服務端要為特定的使用者建立了特定的 Session,用用於標識這個使用者,並且跟蹤使用者,這樣才知道購物車裡面有幾本書。這個 Session 是儲存在服務端的,有一個唯一標識。在服務端儲存Session 的方法很多,記憶體、資料庫、檔案都有。叢集的時候也要考慮 Session 的轉移,在大型的網站,一般會有專門的 Session 伺服器叢集,用來儲存使用者會話,這個時候 Session 資訊都是放在記憶體的,使用一些快取服務比如 Memcached 之類的來放 Session。

  2. 思考一下服務端如何識別特定的客戶?

    這個時候 Cookie 就登場了。每次 HTTP 請求的時候,客戶端都會發送相應的 Cookie 資訊到服務端。實際上大多數的應用都是用 Cookie 來實現 Session 跟蹤的,第一次建立 Session 的時候,服務端會在 HTTP 協議中告訴客戶端,需要在 Cookie 裡面記錄一個Session ID,以後每次請求把這個會話 ID 傳送到伺服器,我就知道你是誰了。有人問,如果客戶端的瀏覽器禁用了 Cookie 怎麼辦?一般這種情況下,會使用一種叫做URL重寫的技術來進行會話跟蹤,即每次 HTTP 互動,URL後面都會被附加上一個諸如 sid=xxxxx 這樣的引數,服務端據此來識別使用者。

  3. Cookie 其實還可以用在一些方便使用者的場景下,設想你某次登陸過一個網站,下次登入的時候不想再次輸入賬號了,怎麼辦?這個資訊可以寫到 Cookie 裡面,訪問網站的時候,網站頁面的指令碼可以讀取這個資訊,就自動幫你把使用者名稱給填了,能夠方便一下使用者。這也是 Cookie 名稱的由來,給使用者的一點甜頭。

所以,總結一下:

  • Session 是在服務端儲存的一個數據結構,用來跟蹤使用者的狀態,這個資料可以儲存在叢集、資料庫、檔案中;
  • Cookie 是客戶端儲存使用者資訊的一種機制,用來記錄使用者的一些資訊,也是實現 Session 的一種方式。

10. JavaEE中的三層結構和MVC

做企業應用開發時,經常採用三層架構分層:表示層、業務層、持久層。表示層負責接收使用者請求、轉發請求、顯示資料等;業務層負責組織業務邏輯;持久層負責持久化業務物件。

這三個分層,每一層都有不同的模式,就是架構模式。表示層最常用的架構模式就是MVC。

因此,MVC 是三層架構中表示層最常用的架構模式。

MVC 是客戶端的一種設計模式,所以他天然就不考慮資料如何儲存的問題。作為客戶端,只需要解決使用者介面、互動和業務邏輯就好了。在 MVC 模式中,View 負責的是使用者介面,Controller 負責互動,Model 負責業務邏輯。至於資料如何儲存和讀取,當然是由 Model 呼叫服務端的介面來完成。

在三層架構中,並沒有客戶端/服務端的概念,所以表示層、業務層的任務其實和 MVC 沒什麼區別,而持久層在 MVC 裡面是沒有的。

各層次的關係:表現層的控制->服務層->資料持久化層。

參考資料:

11. RESTful 架構

什麼是REST

可以總結為一句話:REST 是所有 Web 應用都應該遵守的架構設計指導原則。 Representational State Transfer,翻譯是”表現層狀態轉化”。 面向資源是 REST 最明顯的特徵,對於同一個資源的一組不同的操作。資源是伺服器上一個可命名的抽象概念,資源是以名詞為核心來組織的,首先關注的是名詞。REST要求,必須通過統一的介面來對資源執行各種操作。對於每個資源只能執行一組有限的操作。(7個HTTP方法:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS)

什麼是RESTful API

符合REST架構設計的API。

RESTful 風格

以豆瓣網為例

  1. 應該儘量將 API 部署在專用域名之下 http://api.douban.com/v2/user/1000001?apikey=XXX

  2. 應該將 API 的版本號放入URL http://api.douban.com/v2/user/1000001?apikey=XXX

  3. 在 RESTful 架構中,每個網址代表一種資源(resource),所以網址中不能有動詞,只能有名詞,而且所用的名詞往往與資料庫的表格名對應。一般來說,資料庫中的表都是同種記錄的”集合”(collection),所以 API 中的名詞也應該使用複數。 http://api.douban.com/v2/book/:id (獲取圖書資訊) http://api.douban.com/v2/movie/subject/:id (電影條目資訊) http://api.douban.com/v2/music/:id (獲取音樂資訊) http://api.douban.com/v2/event/:id (獲取同城活動)

  4. 如果記錄數量很多,伺服器不可能都將它們返回給使用者。API應該提供引數,過濾返回結果

  5. 伺服器向用戶返回的狀態碼和提示資訊 每個狀態碼代表不同意思, 就像代號一樣

    2系 代表正常返回

    4系 代表資料異常

    5系 代表伺服器異常

二、Spring

1. Spring IOC、AOP的理解、實現的原理,以及優點

Spring的IoC容器是Spring的核心,Spring AOP是spring框架的重要組成部分

IOC

  • 我的理解

    • 正常的情況下,比如有一個類,在類裡面有方法(不是靜態的方法),呼叫類裡面的方法,建立類的物件,使用物件呼叫方法,建立類物件的過程,需要new出來物件
    • 通過控制反轉,把物件的建立不是通過new方式實現,而是交給Spring配置建立類物件
    • IOC的意思是控制元件反轉也就是由容器控制程式之間的關係,這也是spring的優點所在,把控制元件權交給了外部容器,之前的寫法,由程式程式碼直接操控,而現在控制權由應用程式碼中轉到了外部容器,控制權的轉移是所謂反轉。換句話說之前用new的方式獲取物件,現在由spring給你至於怎麼給你就是di了。
  • Spring IOC實現原理

    • 建立xml配置檔案,配置要建立的物件類
    • 通過反射建立例項;
    • 獲取需要注入的介面實現類並將其賦值給該介面。
  • 優點

    • 解耦合,開發更方便組織分工
    • 高層不依賴於底層(依賴倒置)
    • 是應用更容易測試
    • 因為把物件生成放在了XML裡定義,所以當我們需要換一個實現子類將會變成很簡單(一般這樣的物件都是現實於某種介面的),只要修改XML就可以了,這樣我們甚至可以實現物件的熱插撥

AOP

  • 我的理解

    • AOP(Aspect Oriented Programming )稱為面向切面程式設計,擴充套件功能不是修改原始碼實現,在程式開發中主要用來解決一些系統層面上的問題,比如日誌,事務,許可權等待,Struts2的攔截器設計就是基於AOP的思想,是個比較經典的例子。
    • 面向切面程式設計(aop)是對面向物件程式設計(oop)的補充
    • 面向切面程式設計提供宣告式事務管理
    • AOP就是典型的代理模式的體現
  • Spring AOP實現原理

    • 動態代理(利用反射和動態編譯將代理模式變成動態的)

    • JDK的動態代理

      • JDK內建的Proxy動態代理可以在執行時動態生成位元組碼,而沒必要針對每個類編寫代理類
      • JDKProxy返回動態代理類,是目標類所實現介面的另一個實現版本,它實現了對目標類的代理(如同UserDAOProxy與UserDAOImp的關係)
    • cglib動態代理

      • CGLibProxy返回的動態代理類,則是目標代理類的一個子類(代理類擴充套件了UserDaoImpl類)
      • cglib繼承被代理的類,重寫方法,織入通知,動態生成位元組碼並執行
  • 優點

    • 各個步驟之間的良好隔離性
    • 原始碼無關性
    • 鬆耦合
    • 易擴充套件
    • 程式碼複用

2. 什麼是依賴注入,注入的方式有哪些

  • DI(依賴注入)

    • 所謂依賴注入,就是把底層類作為引數傳入上層類,實現上層類對下層類的控制。DI依賴注入,向類裡面屬性注入值 ,依賴注入不能單獨存在,需要在IOC基礎上完成操作。
    • 使用set方法注入
    • 使用有參構造注入
    • 使用介面注入
    • 註解注入(@Autowire)

3. Spring IOC初始化過程

IOC容器的初始化分為三個過程實現:

  • 第一個過程是Resource資源定位。這個Resouce指的是BeanDefinition的資源定位。這個過程就是容器找資料的過程,就像水桶裝水需要先找到水一樣。
  • 第二個過程是BeanDefinition的載入過程。這個載入過程是把使用者定義好的Bean表示成Ioc容器內部的資料結構,而這個容器內部的資料結構就是BeanDefition。
  • 第三個過程是向IOC容器註冊這些BeanDefinition的過程,這個過程就是將前面的BeanDefition儲存到HashMap中的過程。

參考資料:

4. 專案中Spring AOP用在什麼地方,為什麼這麼用,切點,織入,通知用自己的話描述一下

  • Joinpoint(連線點)(重要)
    • 類裡面可以被增強的方法,這些方法稱為連線點
  • Pointcut(切入點)(重要)
    • 所謂切入點是指我們要對哪些Joinpoint進行攔截的定義
  • Advice(通知/增強)(重要)
    • 所謂通知是指攔截到Joinpoint之後所要做的事情就是通知.通知分為前置通知,後置通知,異常通知,最終通知,環繞通知(切面要完成的功能)
  • Aspect(切面)
    • 是切入點和通知(引介)的結合
  • Introduction(引介)
    • 引介是一種特殊的通知在不修改類程式碼的前提下, Introduction可以在執行期為類動態地新增一些方法或Field.
  • Target(目標物件)
    • 代理的目標物件(要增強的類)
  • Weaving(織入)
    • 是把增強應用到目標的過程,把advice 應用到 target的過程
  • Proxy(代理)
    • 一個類被AOP織入增強後,就產生一個結果代理類

AOP(Aspect Oriented Programming )稱為面向切面程式設計,擴充套件功能不是修改原始碼實現,在程式開發中主要用來解決一些系統層面上的問題,比如日誌,事務,許可權等待,Struts2的攔截器設計就是基於AOP的思想,是個比較經典的例子。

5. AOP動態代理2種實現原理,他們的區別是什麼?

  • 動態代理與cglib實現的區別
    • JDK動態代理只能對實現了介面的類生成代理,而不能針對類.
    • cglib是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法因為是繼承,所以該類或方法最好不要宣告成final。
    • JDK代理是不需要以來第三方的庫,只要JDK環境就可以進行代理
    • cglib必須依賴於cglib的類庫,但是它需要類來實現任何介面代理的是指定的類生成一個子類,覆蓋其中的方法,是一種繼承

6. Struts攔截器和Spring AOP區別

7. Spring 是如何管理事務的,事務管理機制

事務管理可以幫助我們保證資料的一致性,對應企業的實際應用很重要。

Spring的事務機制包括宣告式事務和程式設計式事務。

  • 程式設計式事務管理:Spring推薦使用TransactionTemplate,實際開發中使用宣告式事務較多。
  • 宣告式事務管理:將我們從複雜的事務處理中解脫出來,獲取連線,關閉連線、事務提交、回滾、異常處理等這些操作都不用我們處理了,Spring都會幫我們處理。

宣告式事務管理使用了AOP面向切面程式設計實現的,本質就是在目標方法執行前後進行攔截。在目標方法執行前加入或建立一個事務,在執行方法執行後,根據實際情況選擇提交或是回滾事務

如何管理的

Spring事務管理主要包括3個介面,Spring的事務主要是由它們(PlatformTransactionManager,TransactionDefinition,TransactionStatus)三個共同完成的。

1. PlatformTransactionManager:事務管理器–主要用於平臺相關事務的管理

主要有三個方法:

  • commit 事務提交;
  • rollback 事務回滾;
  • getTransaction 獲取事務狀態。

2. TransactionDefinition:事務定義資訊–用來定義事務相關的屬性,給事務管理器PlatformTransactionManager使用

這個介面有下面四個主要方法:

  • getIsolationLevel:獲取隔離級別;
  • getPropagationBehavior:獲取傳播行為;
  • getTimeout:獲取超時時間;
  • isReadOnly:是否只讀(儲存、更新、刪除時屬性變為false–可讀寫,查詢時為true–只讀)

事務管理器能夠根據這個返回值進行優化,這些事務的配置資訊,都可以通過配置檔案進行配置。

3. TransactionStatus:事務具體執行狀態–事務管理過程中,每個時間點事務的狀態資訊。

例如它的幾個方法:

  • hasSavepoint():返回這個事務內部是否包含一個儲存點,
  • isCompleted():返回該事務是否已完成,也就是說,是否已經提交或回滾
  • isNewTransaction():判斷當前事務是否是一個新事務

宣告式事務的優缺點

  • 優點:不需要在業務邏輯程式碼中編寫事務相關程式碼,只需要在配置檔案配置或使用註解(@Transaction),這種方式沒有侵入性。
  • 缺點:宣告式事務的最細粒度作用於方法上,如果像程式碼塊也有事務需求,只能變通下,將程式碼塊變為方法。

8. Spring中bean載入機制,生命週期

載入機制

生命週期

在傳統的Java應用中,bean的生命週期很簡單。使用Java關鍵字new進行bean例項化,然後該bean就可以使用了。一旦該bean不再被使用,則由Java自動進行垃圾回收。

相比之下,Spring容器中的bean的生命週期就顯得相對複雜多了。正確理解Spring bean的生命週期非常重要,因為你或許要利用Spring提供的擴充套件點來自定義bean的建立過程。下圖展示了bean裝載到Spring應用上下文中的一個典型的生命週期過程。

上圖bean在Spring容器中從建立到銷燬經歷了若干階段,每一階段都可以針對Spring如何管理bean進行個性化定製

正如你所見,在bean準備就緒之前,bean工廠執行了若干啟動步驟。我們對上圖進行詳細描述:

  1. Spring 對 Bean 進行例項化;

    • 相當於程式中的new Xx()
  2. Spring 將值和 Bean 的引用注入進 Bean 對應的屬性中;

  3. 如果Bean實現了 BeanNameAware 介面,Spring 將 Bean 的 ID 傳遞給setBeanName()方法

    • 實現BeanNameAware清主要是為了通過Bean的引用來獲得Bean的ID,一般業務中是很少有在Bean的ID的
  4. 如果Bean實現了BeanFactoryAware介面,Spring將呼叫setBeanDactory(BeanFactory bf)方法並把BeanFactory容器例項作為引數傳入。

    • 實現BeanFactoryAware 主要目的是為了獲取Spring容器,如Bean通過Spring容器釋出事件等
  5. 如果Bean實現了ApplicationContextAwaer介面,Spring容器將呼叫setApplicationContext(ApplicationContext ctx)方法,將bean所在的應用上下文的引用傳入進來

    • 作用與BeanFactory類似都是為了獲取Spring容器,不同的是Spring容器在呼叫setApplicationContext方法時會把它自己作為setApplicationContext 的引數傳入,而Spring容器在呼叫setBeanDactory前需要程式設計師自己指定(注入)setBeanDactory裡的引數BeanFactory
  6. 如果Bean實現了BeanPostProcess介面,Spring將呼叫它們的postProcessBeforeInitialization(預初始化)方法

    • 作用是在Bean例項建立成功後對進行增強處理,如對Bean進行修改,增加某個功能
  7. 如果Bean實現了InitializingBean介面,Spring將呼叫它們的afterPropertiesSet方法,作用與在配置檔案中對Bean使用init-method宣告初始化的作用一樣,都是在Bean的全部屬性設定成功後執行的初始化方法。

  8. 如果Bean實現了BeanPostProcess介面,Spring將呼叫它們的postProcessAfterInitialization(後初始化)方法

    • 作用與6的一樣,只不過6是在Bean初始化前執行的,而這個是在Bean初始化後執行的,時機不同
  9. 經過以上的工作後,Bean將一直駐留在應用上下文中給應用使用,直到應用上下文被銷燬

  10. 如果Bean實現了DispostbleBean介面,Spring將呼叫它的destory方法,作用與在配置檔案中對Bean使用destory-method屬性的作用一樣,都是在Bean例項銷燬前執行的方法。

9. Bean例項化的三種方式

  • 使用類的無參構造建立(此種方式用的最多)
  • 使用靜態工廠建立物件
  • 使用例項工廠建立物件

10. BeanFactory 和 FactoryBean的區別

  • BeanFactory是個Factory,也就是IOC容器或物件工廠,在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)來進行管理的,提供了例項化物件和拿物件的功能。
  • FactoryBean是個Bean,這個Bean不是簡單的Bean,而是一個能生產或者修飾物件生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式類似。

11. BeanFactory和ApplicationContext的區別

BeanFactory

是Spring裡面最低層的介面,提供了最簡單的容器的功能,只提供了例項化物件和拿物件的功能。

兩者裝載bean的區別

  • BeanFactory:在啟動的時候不會去例項化Bean,中有從容器中拿Bean的時候才會去例項化;
  • ApplicationContext:在啟動的時候就把所有的Bean全部例項化了。它還可以為Bean配置lazy-init=true來讓Bean延遲例項化;

我們該用BeanFactory還是ApplicationContent

BeanFactory 延遲例項化的優點:

應用啟動的時候佔用資源很少,對資源要求較高的應用,比較有優勢;

缺點:速度會相對來說慢一些。而且有可能會出現空指標異常的錯誤,而且通過bean工廠建立的bean生命週期會簡單一些

ApplicationContext 不延遲例項化的優點:

  • 所有的Bean在啟動的時候都載入,系統執行的速度快;
  • 在啟動的時候所有的Bean都載入了,我們就能在系統啟動的時候,儘早的發現系統中的配置問題
  • 建議web應用,在啟動的時候就把所有的Bean都載入了。

缺點:把費時的操作放到系統啟動中完成,所有的物件都可以預載入,缺點就是消耗伺服器的記憶體

ApplicationContext其他特點

除了提供BeanFactory所支援的所有功能外,ApplicationContext還有額外的功能

  • 預設初始化所有的Singleton,也可以通過配置取消預初始化。
  • 繼承MessageSource,因此支援國際化。
  • 資源訪問,比如訪問URL和檔案(ResourceLoader);
  • 事件機制,(有繼承關係)上下文 ,使得每一個上下文都專注於一個特定的層次,比如應用的web層;
  • 同時載入多個配置檔案。
  • 訊息傳送、響應機制(ApplicationEventPublisher);
  • 以宣告式方式啟動並建立Spring容器。

由於ApplicationContext會預先初始化所有的Singleton Bean,於是在系統建立前期會有較大的系統開銷,但一旦ApplicationContext初始化完成,程式後面獲取Singleton Bean例項時候將有較好的效能。

也可以為bean設定lazy-init屬性為true,即Spring容器將不會預先初始化該bean。

spring的AOP(常用的是攔截器)

一般攔截器都是實現HandlerInterceptor,其中有三個方法preHandle、postHandle、afterCompletion

  1. preHandle:執行controller之前執行
  2. postHandle:執行完controller,return modelAndView之前執行,主要操作modelAndView的值
  3. afterCompletion:controller返回後執行

spring載入多個上下文

不同專案使用不同分模組策略,spring配置檔案分為

  • applicationContext.xml(主檔案,包括JDBC配置,hibernate.cfg.xml,與所有的Service與DAO基類)
  • applicationContext-cache.xml(cache策略,包括hibernate的配置)
  • applicationContext-jmx.xml(JMX,除錯hibernate的cache效能)
  • applicationContext-security.xml(acegi安全)
  • applicationContext-transaction.xml(事務)
  • moduleName-Service.xml
  • moduleName-dao.xml

12. ApplicationContext 上下文的生命週期

PS:可以借鑑Servlet的生命週期,例項化、初始init、接收請求service、銷燬destroy;

Spring上下文中的Bean也類似,【Spring上下文的生命週期】

  1. 例項化一個Bean,也就是我們通常說的new;
  2. 按照Spring上下文對例項化的Bean進行配置,也就是IOC注入
  3. 如果這個Bean實現了BeanNameAware介面,會呼叫它實現的setBeanName(String beanId)方法,此處傳遞的是Spring配置檔案中Bean的ID;
  4. 如果這個Bean實現了BeanFactoryAware介面,會呼叫它實現的setBeanFactory(),傳遞的是Spring工廠本身(可以用這個方法獲取到其他Bean);
  5. 如果這個Bean實現了ApplicationContextAware介面,會呼叫setApplicationContext(ApplicationContext)方法,傳入Spring上下文,該方式同樣可以實現步驟4,但比4更好,以為ApplicationContext是BeanFactory的子介面,有更多的實現方法;
  6. 如果這個Bean關聯了BeanPostProcessor介面,將會呼叫postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor經常被用作是Bean內容的更改,並且由於這個是在Bean初始化結束時呼叫After方法,也可用於記憶體或快取技術;
  7. 如果這個Bean在Spring配置檔案中配置了init-method屬性會自動呼叫其配置的初始化方法;
  8. 如果這個Bean關聯了BeanPostProcessor介面,將會呼叫postAfterInitialization(Object obj, String s)方法;

注意:以上工作完成以後就可以用這個Bean了,那這個Bean是一個single的,所以一般情況下我們呼叫同一個ID的Bean會是在內容地址相同的例項

  1. 當Bean不再需要時,會經過清理階段,如果Bean實現了DisposableBean介面,會呼叫其實現的destroy方法
  2. 最後,如果這個Bean的Spring配置中配置了destroy-method屬性,會自動呼叫其配置的銷燬方法

以上10步驟可以作為面試或者筆試的模板,另外這裡描述的是應用Spring上下文Bean的生命週期,如果應用Spring的工廠也就是BeanFactory的話去掉第5步就Ok了;

13. Spring中autowire和resourse關鍵字的區別

@Resource和@Autowired都是做bean的注入時使用,其實@Resource並不是Spring的註解,它的包是javax.annotation.Resource,需要匯入,但是Spring支援該註解的注入。

1、共同點

兩者都可以寫在欄位和setter方法上。兩者如果都寫在欄位上,那麼就不需要再寫setter方法。

2、不同點

(1)@Autowired

@Autowired為Spring提供的註解,需要匯入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。

public class TestServiceImpl {
    // 下面兩種@Autowired只要使用一種即可
    @Autowired
    private UserDao userDao; // 用於欄位上
    
    @Autowired
    public void setUserDao(UserDao userDao) { // 用於屬性的方法上
        this.userDao = userDao;
    }
}

@Autowired註解是按照型別(byType)裝配依賴物件,預設情況下它要求依賴物件必須存在,如果允許null值,可以設定它的required屬性為false。如果我們想使用按照名稱(byName)來裝配,可以結合@Qualifier註解一起使用。如下:

public class TestServiceImpl {
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao; 
}

(2)@Resource

@Resource預設按照ByName自動注入,由J2EE提供,需要匯入包javax.annotation.Resource。@Resource有兩個重要的屬性:name和type,而Spring將@Resource註解的name屬性解析為bean的名字,而type屬性則解析為bean的型別。所以,如果使用name屬性,則使用byName的自動注入策略,而使用type屬性時則使用byType自動注入策略。如果既不制定name也不制定type屬性,這時將通過反射機制使用byName自動注入策略。

public class TestServiceImpl {
    // 下面兩種@Resource只要使用一種即可
    @Resource(name="userDao")
    private UserDao userDao; // 用於欄位上
    
    @Resource(name="userDao")
    public void setUserDao(UserDao userDao) { // 用於屬性的setter方法上
        this.userDao = userDao;
    }
}

注:最好是將@Resource放在setter方法上,因為這樣更符合面向物件的思想,通過set、get去操作屬性,而不是直接去操作屬性。

@Resource裝配順序:

  1. 如果同時指定了name和type,則從Spring上下文中找到唯一匹配的bean進行裝配,找不到則丟擲異常。

  2. 如果指定了name,則從上下文中查詢名稱(id)匹配的bean進行裝配,找不到則丟擲異常。

  3. 如果指定了type,則從上下文中找到類似匹配的唯一bean進行裝配,找不到或是找到多個,都會丟擲異常。

  4. 如果既沒有指定name,又沒有指定type,則自動按照byName方式進行裝配;如果沒有匹配,則回退為一個原始型別進行匹配,如果匹配則自動裝配。

@Resource的作用相當於@Autowired,只不過@Autowired按照byType自動注入。

14. Spring的註解講一下,介紹Spring中的熟悉的註解

思考:spring怎麼知道應該哪些Java類當初bean類處理?

答案:使用配置檔案或者註解的方式進行標識需要處理的java類!

一: 元件類註解

@Component :標準一個普通的spring Bean類。 @Repository:標註一個DAO元件類。 @Service:標註一個業務邏輯元件類。 @Controller:標註一個控制器元件類。

這些都是註解在平時的開發過程中出鏡率極高,@Component、@Repository、@Service、@Controller實質上屬於同一類註解,用法相同,功能相同,區別在於標識元件的型別。@Component可以代替@Repository、@Service、@Controller,因為這三個註解是被@Component標註的。如下程式碼

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    String value() default "";
}

舉例:

(1)當一個元件代表資料訪問層(DAO)的時候,我們使用@Repository進行註解,如下

@Repository
public class HappyDaoImpl implements HappyDao{
private final static Logger LOGGER = LoggerFactory.getLogger(HappyDaoImpl .class);
public void  club(){
        //do something ,like drinking and singing
    }
}1234567

(2)當一個元件代表業務層時,我們使用@Service進行註解,如下

@Service(value="goodClubService")
//使用@Service註解不加value ,預設名稱是clubService
public class ClubServiceImpl implements ClubService {
    @Autowired
    private ClubDao clubDao;

    public void doHappy(){
        //do some Happy
    }
 }12345678910

(3)當一個元件作為前端互動的控制層,使用@Controller進行註解,如下

@Controller
public class HappyController {
    @Autowired //下面進行講解
    private ClubService clubService;

    // Control the people entering the Club
    // do something
}
/*Controller相關的註解下面進行詳細講解,這裡簡單引入@Controller*/

3、總結注意點

  1. 被註解的java類當做Bean例項,Bean例項的名稱預設是Bean類的首字母小寫,其他部分不變。@Service也可以自定義Bean名稱,但是必須是唯一的!
  2. 儘量使用對應元件註解的類替換@Component註解,在spring未來的版本中,@Controller,@Service,@Repository會攜帶更多語義。並且便於開發和維護!
  3. 指定了某些類可作為Spring Bean類使用後,最好還需要讓spring搜尋指定路徑,在Spring配置檔案加入如下配置:
<!-- 自動掃描指定包及其子包下的所有Bean類 -->
<context:component-scan base-package="org.springframework.*"/>

二:裝配bean時常用的註解

@Autowired:屬於Spring 的org.springframework.beans.factory.annotation包下,可用於為類的屬性、構造器、方法進行注值 @Resource:不屬於spring的註解,而是來自於JSR-250位於java.annotation包下,使用該annotation為目標bean指定協作者Bean。

15. Spring 中用到了那些設計模式?

Spring框架中使用到了大量的設計模式,下面列舉了比較有代表性的:

  • 代理模式—在AOP和remoting中被用的比較多。
  • 單例模式—在spring配置檔案中定義的bean預設為單例模式。
  • 模板方法—用來解決程式碼重複的問題。比如. RestTemplate, JmsTemplate, JpaTemplate。
  • 工廠模式—BeanFactory用來建立物件的例項。
  • 介面卡–spring aop
  • 裝飾器–spring data hashmapper
  • 觀察者– spring 時間驅動模型
  • 回撥–Spring ResourceLoaderAware回撥介面

工廠模式(Factory Method)

Spring容器就是例項化和管理Bean的工廠

工廠模式隱藏了建立類的細節,返回值必定是介面或者抽象類,而不是具體的某個物件,工廠類根據條件生成不同的子類例項。當得到子類的例項後,就可以呼叫基類中的方法,不必考慮返回的是哪一個子類的例項。

這個很明顯,在各種BeanFactory以及ApplicationContext建立中都用到了;

Spring通過配置檔案,就可以管理所有的bean,而這些bean就是Spring工廠能產生的例項,因此,首先我們在Spring配置檔案中對兩個例項進行配置

單態模式【單例模式】(Singleton)

Spring預設將所有的Bean設定成 單例模式,即對所有的相同id的Bean的請求,都將返回同一個共享的Bean例項。這樣就可以大大降低Java建立物件和銷燬時的系統開銷

使用Spring將Bean設定稱為單例行為,則無需自己完成單例模式。

| 可以通過singleton=“true | false” 或者 scope=“?”來指定 |

介面卡(Adapter)

在Spring的Aop中,使用的Advice(通知)來增強被代理類的功能。Spring實現這一AOP功能的原理就使用代理模式(1、JDK動態代理。2、CGLib位元組碼生成技術代理。)對類進行方法級別的切面增強,即,生成被代理類的代理類, 並在代理類的方法前,設定攔截器,通過執行攔截器重的內容增強了代理方法的功能,實現的面向切面程式設計

代理(Proxy)

Spring實現了一種能夠通過額外的方法呼叫完成任務的設計模式 - 代理設計模式,比如JdkDynamicAopProxy和Cglib2AopProxy。

代理設計模式的一個很好的例子是org.springframework.aop.framework.ProxyFactoryBean該工廠根據Spring bean構建AOP代理。該類實現了定義getObject()方法的FactoryBean介面。此方法用於將需求Bean的例項返回給bean factory。在這種情況下,它不是返回的例項,而是AOP代理。在執行代理物件的方法之前,可以通過呼叫補充方法來進一步“修飾”代理物件(其實所謂的靜態代理不過是在裝飾模式上加了個要不要你來幹動作行為而已,而不是裝飾模式什麼也不做就加了件衣服,其他還得由你來全權完成)。

觀察者(Observer)

定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。spring中Observer模式常用的地方是listener的實現。如ApplicationListener

補充面試題:Spring裡面的工廠模式和代理模式,IO中的裝飾者模式,挑幾個最熟的能講講思路和虛擬碼實現?

16. Spring 的優點有哪些

  1. 降低了元件之間的耦合性 ,實現了軟體各層之間的解耦
  2. 可以使用容易提供的眾多服務,如事務管理,訊息服務等
  3. 容器提供單例模式支援
  4. 容器提供了AOP技術,利用它很容易實現如許可權攔截,執行期監控等功能
  5. 容器提供了眾多的輔助類,能加快應用的開發
  6. spring對於主流的應用框架提供了整合支援,如hibernate,JPA,Struts等
  7. spring屬於低侵入式設計,程式碼的汙染極低
  8. 獨立於各種應用伺服器
  9. spring的DI機制降低了業務物件替換的複雜性
  10. Spring的高度開放性,並不強制應用完全依賴於Spring,開發者可以自由選擇spring的部分或全部

17. IOC和AOP用到的設計模式

用過spring的朋友都知道spring的強大和高深,都覺得深不可測,其實當你真正花些時間讀一讀原始碼就知道它的一些技術實現其實是建立在一些最基本的技術之上而已;例如AOP(面向方面程式設計)的實現是建立在CGLib提供的類代理和jdk提供的介面代理,IOC(控制反轉)的實現建立在工廠模式、Java反射機制和jdk的操作XML的DOM解析方式.

二、SpringMVC

1. Spring MVC的工作原理

Spring MVC 的工作原理如下圖:

img

  • ① 客戶端的所有請求都交給前端控制器DispatcherServlet來處理,它會負責呼叫系統的其他模組來真正處理使用者的請求。
  • ② DispatcherServlet收到請求後,將根據請求的資訊(包括URL、HTTP協議方法、請求頭、請求引數、Cookie等)以及HandlerMapping的配置找到處理該請求的Handler(任何一個物件都可以作為請求的Handler)。
  • ③ 在這個地方Spring會通過HandlerAdapter對該處理進行封裝。
  • ④ HandlerAdapter是一個介面卡,它用統一的介面對各種Handler中的方法進行呼叫。
  • ⑤ Handler完成對使用者請求的處理後,會返回一個ModelAndView物件給DispatcherServlet,ModelAndView顧名思義,包含了資料模型以及相應的檢視的資訊。
  • ⑥ ModelAndView的檢視是邏輯檢視,DispatcherServlet還要藉助ViewResolver完成從邏輯檢視到真實檢視物件的解析工作。
  • ⑦ 當得到真正的檢視物件後,DispatcherServlet會利用檢視物件對模型資料進行渲染。
  • ⑧ 客戶端得到響應,可能是一個普通的HTML頁面,也可以是XML或JSON字串,還可以是一張圖片或者一個PDF檔案。

元件及其作用

  1. 前端控制器 (DispatcherServlet)

    接收請求,響應結果,相當於轉發器,中央處理器。負責呼叫系統的其他模組來真正處理使用者的請求。

    有了DispatcherServlet減少了其他元件之間的耦合度

  2. 處理器對映器 (HandlerMapping)

    作用:根據請求的 url 查詢 Handler

  3. 處理器 (Handler)

    注意:編寫 Handler 時按照 HandlerAdapter 的要求去做,這樣介面卡才可以去正確執行 Handler

  4. 處理器介面卡 (HandlerAdapter)

    作用:按照特定規則(HandlerAdapter要求的規則)執行Handler。

  5. 檢視解析器 (ViewResolver)

    作用:進行檢視解析,根據邏輯檢視解析成真正的檢視 (View)

  6. 檢視 (View)

    View 是一個介面實現類支援不同的 View 型別(jsp,pdf等等)

注意:只需要程式設計師開發,處理器和檢視。

2. Spring MVC註解的優點

三、Hibernate

1. 簡述Hibernate常見優化策略。

  • 制定合理的快取策略(二級快取、查詢快取)。
  • 採用合理的Session管理機制。
  • 儘量使用延遲載入特性。
  • 設定合理的批處理引數。
  • 如果可以,選用UUID作為主鍵生成器。
  • 如果可以,選用基於版本號的樂觀鎖替代悲觀鎖。
  • 在開發過程中, 開啟hibernate.show_sql選項檢視生成的SQL,從而瞭解底層的狀況;開發完成後關閉此選項。
  • 考慮資料庫本身的優化,合理的索引、恰當的資料分割槽策略等都會對持久層的效能帶來可觀的提升,但這些需要專業的DBA(資料庫管理員)提供支援。

2. Hibernate一級快取與二級快取之間的區別

  • Hibernate的Session提供了一級快取的功能,預設總是有效的,當應用程式儲存持久化實體、修改持久化實體時,Session並不會立即把這種改變提交到資料庫,而是快取在當前的Session中,除非顯示呼叫了Session的flush()方法或通過close()方法關閉Session。通過一級快取,可以減少程式與資料庫的互動,從而提高資料庫訪問效能。
  • SessionFactory級別的二級快取是全域性性的,所有的Session可以共享這個二級快取。不過二級快取預設是關閉的,需要顯示開啟並指定需要使用哪種二級快取實現類(可以使用第三方提供的實現)。一旦開啟了二級快取並設定了需要使用二級快取的實體類,SessionFactory就會快取訪問過的該實體類的每個物件,除非快取的資料超出了指定的快取空間。
  • 一級快取和二級快