Java面試題全集(四)

110、使用標籤庫有什麼好處?如何自定義JSP標籤? 
答:使用標籤庫的好處包括以下幾個方面: 
- 分離JSP頁面的內容和邏輯,簡化了Web開發; 
- 開發者可以創建自定義標籤來封裝業務邏輯和顯示邏輯; 
- 標籤具有很好的可移植性、可維護性和可重用性; 
- 避免了對Scriptlet(小腳本)的使用(很多公司的項目開發都不允許在JSP中書寫小腳本)

自定義JSP標籤包括以下幾個步驟: 
- 編寫一個Java類實現實現Tag/BodyTag/IterationTag接口(開發中通常不直接實現這些接口而是繼承TagSupport/BodyTagSupport/SimpleTagSupport類,這是對缺省適配模式的應用),重寫doStartTag()、doEndTag()等方法,定義標籤要完成的功能 
- 編寫擴展名爲tld的標籤描述文件對自定義標籤進行部署,tld文件通常放在WEB-INF文件夾下或其子目錄中 
- 在JSP頁面中使用taglib指令引用該標籤庫

下面是一個自定義標籤庫的例子。

步驟1 - 標籤類源代碼TimeTag.java:

package com.jackfrued.tags;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;

public class TimeTag extends TagSupport {
    private static final long serialVersionUID = 1L;

    private String format = "yyyy-MM-dd hh:mm:ss";
    private String foreColor = "black";
    private String backColor = "white";

    public int doStartTag() throws JspException {
         SimpleDateFormat sdf = new SimpleDateFormat(format);
         JspWriter writer = pageContext.getOut();
         StringBuilder sb = new StringBuilder();
         sb.append(String.format("<span style='color:%s;background-color:%s'>%s</span>",
             foreColor, backColor, sdf.format(new Date())));
         try {
           writer.print(sb.toString());
         } catch(IOException e) {
           e.printStackTrace();
         }
         return SKIP_BODY;
      }

    public void setFormat(String format) {
        this.format = format;
    }

    public void setForeColor(String foreColor) {
        this.foreColor = foreColor;
    }

    public void setBackColor(String backColor) {
        this.backColor = backColor;
    }
}
  • 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
  • 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

步驟2 - 編寫標籤庫描述文件my.tld:

<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
    http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">

    <description>定義標籤庫</description>
    <tlib-version>1.0</tlib-version>
    <short-name>MyTag</short-name>
    <tag>
        <name>time</name>
        <tag-class>com.jackfrued.tags.TimeTag</tag-class>
        <body-content>empty</body-content>
        <attribute>
            <name>format</name>
            <required>false</required>
        </attribute>
        <attribute>
            <name>foreColor</name>
        </attribute>
        <attribute>
            <name>backColor</name>
        </attribute>
    </tag>
</taglib>
  • 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
  • 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

步驟3 - 在JSP頁面中使用自定義標籤:

<%@ page pageEncoding="UTF-8"%>
<%@ taglib prefix="my" uri="/WEB-INF/tld/my.tld" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>

<!DOCTYPE html>
<html>
  <head>
    <base href="<%=basePath%>">
    <title>首頁</title>
    <style type="text/css">
        * { font-family: "Arial"; font-size:72px; }
    </style>
  </head>

  <body>
    <my:time format="yyyy-MM-dd" backColor="blue" foreColor="yellow"/>
  </body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

提示:如果要將自定義的標籤庫發佈成JAR文件,需要將標籤庫描述文件(tld文件)放在JAR文件的META-INF目錄下,可以JDK中的jar工具完成JAR文件的生成,如果不清楚如何操作,可以請教谷老師和百老師

111、說一下表達式語言(EL)的隱式對象及其作用。 
答:EL的隱式對象包括:pageContext、initParam(訪問上下文參數)、param(訪問請求參數)、paramValues、header(訪問請求頭)、headerValues、cookie(訪問cookie)、applicationScope(訪問application作用域)、sessionScope(訪問session作用域)、requestScope(訪問request作用域)、pageScope(訪問page作用域)。

用法如下所示:

${pageContext.request.method}
${pageContext["request"]["method"]}
${pageContext.request["method"]}
${pageContext["request"].method}
${initParam.defaultEncoding}
${header["accept-language"]}
${headerValues["accept-language"][0]}
${cookie.jsessionid.value}
${sessionScope.loginUser.username}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

補充:表達式語言的.和[]運算作用是一致的,唯一的差別在於如果訪問的屬性名不符合Java標識符命名規則,例如上面的accept-language就不是一個有效的Java標識符,那麼這時候就只能用[]運算符而不能使用.運算符獲取它的值

112、表達式語言(EL)支持哪些運算符? 
答:除了.和[]運算符,EL還提供了: 
- 算術運算符:+、-、*、/或div、%或mod 
- 關係運算符:==或eq、!=或ne、>或gt、>=或ge、<或lt、<=或le 
- 邏輯運算符:&&或and、||或or、!或not 
- 條件運算符:${statement? A : B}(跟Java的條件運算符類似) 
- empty運算符:檢查一個值是否爲null或者空(數組長度爲0或集合中沒有元素也返回true)

113、Java Web開發的Model 1和Model 2分別指的是什麼? 
答:Model 1是以頁面爲中心的Java Web開發,使用JSP+JavaBean技術將頁面顯示邏輯和業務邏輯處理分開,JSP實現頁面顯示,JavaBean對象用來保存數據和實現業務邏輯。Model 2是基於MVC(模型-視圖-控制器,Model-View-Controller)架構模式的開發模型,實現了模型和視圖的徹底分離,利於團隊開發和代碼複用,如下圖所示。

這裏寫圖片描述

114、Servlet 3中的異步處理指的是什麼? 
答:在Servlet 3中引入了一項新的技術可以讓Servlet異步處理請求。有人可能會質疑,既然都有多線程了,還需要異步處理請求嗎?答案是肯定的,因爲如果一個任務處理時間相當長,那麼Servlet或Filter會一直佔用着請求處理線程直到任務結束,隨着併發用戶的增加,容器將會遭遇線程超出的風險,這這種情況下很多的請求將會被堆積起來而後續的請求可能會遭遇拒絕服務,直到有資源可以處理請求爲止。異步特性可以幫助應用節省容器中的線程,特別適合執行時間長而且用戶需要得到結果的任務,如果用戶不需要得到結果則直接將一個Runnable對象交給Executor並立即返回即可。(如果不清楚多線程和線程池的相關內容,請查看《Java面試題全集(上)》關於多線程和線程池的部分或閱讀我的另一篇文章《關於Java併發編程的總結和思考》

補充:多線程在Java誕生初期無疑是一個亮點,而Servlet單實例多線程的工作方式也曾爲其贏得美名,然而技術的發展往往會顛覆我們很多的認知,就如同當年愛因斯坦的相對論顛覆了牛頓的經典力學一般。事實上,異步處理絕不是Serlvet 3首創,如果你瞭解Node.js的話,對Servlet 3的這個重要改進就不以爲奇了。

下面是一個支持異步處理請求的Servlet的例子。

import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = {"/async"}, asyncSupported = true)
public class AsyncServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 開啓Tomcat異步Servlet支持
        req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

        final AsyncContext ctx = req.startAsync();  // 啓動異步處理的上下文
        // ctx.setTimeout(30000);
        ctx.start(new Runnable() {

            @Override
            public void run() {
                // 在此處添加異步處理的代碼

                ctx.complete();
            }
        });
    }
}
  • 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
  • 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

115、如何在基於Java的Web項目中實現文件上傳和下載? 
答:在Sevlet 3 以前,Servlet API中沒有支持上傳功能的API,因此要實現上傳功能需要引入第三方工具從POST請求中獲得上傳的附件或者通過自行處理輸入流來獲得上傳的文件,我們推薦使用Apache的commons-fileupload。 
從Servlet 3開始,文件上傳變得無比簡單,相信看看下面的例子一切都清楚了。

上傳頁面index.jsp:

<%@ page pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Photo Upload</title>
</head>
<body>
<h1>Select your photo and upload</h1>
<hr/>
<div style="color:red;font-size:14px;">${hint}</div>
<form action="UploadServlet" method="post" enctype="multipart/form-data">
    Photo file: <input type="file" name="photo" />
    <input type="submit" value="Upload" />
</form>
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

支持上傳的Servlet:

package com.jackfrued.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

@WebServlet("/UploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        // 可以用request.getPart()方法獲得名爲photo的上傳附件
        // 也可以用request.getParts()獲得所有上傳附件(多文件上傳)
        // 然後通過循環分別處理每一個上傳的文件
        Part part = request.getPart("photo");
        if (part != null && part.getSubmittedFileName().length() > 0) {
            // 用ServletContext對象的getRealPath()方法獲得上傳文件夾的絕對路徑
            String savePath = request.getServletContext().getRealPath("/upload");
            // Servlet 3.1規範中可以用Part對象的getSubmittedFileName()方法獲得上傳的文件名
            // 更好的做法是爲上傳的文件進行重命名(避免同名文件的相互覆蓋)
            part.write(savePath + "/" + part.getSubmittedFileName());
            request.setAttribute("hint", "Upload Successfully!");
        } else {
            request.setAttribute("hint", "Upload failed!");
        }
        // 跳轉回到上傳頁面
        request.getRequestDispatcher("index.jsp").forward(request, response);
    }

}
  • 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
  • 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

116、服務器收到用戶提交的表單數據,到底是調用Servlet的doGet()還是doPost()方法? 
答:HTML的<form>元素有一個method屬性,用來指定提交表單的方式,其值可以是get或post。我們自定義的Servlet一般情況下會重寫doGet()或doPost()兩個方法之一或全部,如果是GET請求就調用doGet()方法,如果是POST請求就調用doPost()方法,那爲什麼爲什麼這樣呢?我們自定義的Servlet通常繼承自HttpServlet,HttpServlet繼承自GenericServlet並重寫了其中的service()方法,這個方法是Servlet接口中定義的。HttpServlet重寫的service()方法會先獲取用戶請求的方法,然後根據請求方法調用doGet()、doPost()、doPut()、doDelete()等方法,如果在自定義Servlet中重寫了這些方法,那麼顯然會調用重寫過的(自定義的)方法,這顯然是對模板方法模式的應用(如果不理解,請參考閻宏博士的《Java與模式》一書的第37章)。當然,自定義Servlet中也可以直接重寫service()方法,那麼不管是哪種方式的請求,都可以通過自己的代碼進行處理,這對於不區分請求方法的場景比較合適。

117、JSP中的靜態包含和動態包含有什麼區別? 
答:靜態包含是通過JSP的include指令包含頁面,動態包含是通過JSP標準動作<jsp:forward>包含頁面。靜態包含是編譯時包含,如果包含的頁面不存在則會產生編譯錯誤,而且兩個頁面的"contentType"屬性應保持一致,因爲兩個頁面會合二爲一,只產生一個class文件,因此被包含頁面發生的變動再包含它的頁面更新前不會得到更新。動態包含是運行時包含,可以向被包含的頁面傳遞參數,包含頁面和被包含頁面是獨立的,會編譯出兩個class文件,如果被包含的頁面不存在,不會產生編譯錯誤,也不影響頁面其他部分的執行。代碼如下所示:

<%-- 靜態包含 --%>
<%@ include file="..." %>

<%-- 動態包含 --%>
<jsp:include page="...">
    <jsp:param name="..." value="..." />
</jsp:include>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

118、Servlet中如何獲取用戶提交的查詢參數或表單數據? 
答:可以通過請求對象(HttpServletRequest)的getParameter()方法通過參數名獲得參數值。如果有包含多個值的參數(例如複選框),可以通過請求對象的getParameterValues()方法獲得。當然也可以通過請求對象的getParameterMap()獲得一個參數名和參數值的映射(Map)。

119、Servlet中如何獲取用戶配置的初始化參數以及服務器上下文參數? 
答:可以通過重寫Servlet接口的init(ServletConfig)方法並通過ServletConfig對象的getInitParameter()方法來獲取Servlet的初始化參數。可以通過ServletConfig對象的getServletContext()方法獲取ServletContext對象,並通過該對象的getInitParameter()方法來獲取服務器上下文參數。當然,ServletContext對象也在處理用戶請求的方法(如doGet()方法)中通過請求對象的getServletContext()方法來獲得。

120、如何設置請求的編碼以及響應內容的類型? 
答:通過請求對象(ServletRequest)的setCharacterEncoding(String)方法可以設置請求的編碼,其實要徹底解決亂碼問題就應該讓頁面、服務器、請求和響應、Java程序都使用統一的編碼,最好的選擇當然是UTF-8;通過響應對象(ServletResponse)的setContentType(String)方法可以設置響應內容的類型,當然也可以通過HttpServletResponsed對象的setHeader(String, String)方法來設置。

說明:現在如果還有公司在面試的時候問JSP的聲明標記、表達式標記、小腳本標記這些內容的話,這樣的公司也不用去了,其實JSP內置對象、JSP指令這些東西基本上都可以忘卻了,關於Java Web開發的相關知識,可以看一下我的《Servlet&JSP思維導圖》,上面有完整的知識點的羅列。想了解如何實現自定義MVC框架的,可以看一下我的《Java Web自定義MVC框架詳解》

121、解釋一下網絡應用的模式及其特點。 
答:典型的網絡應用模式大致有三類:B/S、C/S、P2P。其中B代表瀏覽器(Browser)、C代表客戶端(Client)、S代表服務器(Server),P2P是對等模式,不區分客戶端和服務器。B/S應用模式中可以視爲特殊的C/S應用模式,只是將C/S應用模式中的特殊的客戶端換成了瀏覽器,因爲幾乎所有的系統上都有瀏覽器,那麼只要打開瀏覽器就可以使用應用,沒有安裝、配置、升級客戶端所帶來的各種開銷。P2P應用模式中,成千上萬臺彼此連接的計算機都處於對等的地位,整個網絡一般來說不依賴專用的集中服務器。網絡中的每一臺計算機既能充當網絡服務的請求者,又對其它計算機的請求作出響應,提供資源和服務。通常這些資源和服務包括:信息的共享和交換、計算資源(如CPU的共享)、存儲共享(如緩存和磁盤空間的使用)等,這種應用模式最大的阻力安全性、版本等問題,目前有很多應用都混合使用了多種應用模型,最常見的網絡視頻應用,它幾乎把三種模式都用上了。

補充:此題要跟"電子商務模式"區分開,因爲有很多人被問到這個問題的時候馬上想到的是B2B(如阿里巴巴)、B2C(如噹噹、亞馬遜、京東)、C2C(如淘寶、拍拍)、C2B(如威客)、O2O(如美團、餓了麼)。對於這類問題,可以去百度上面科普一下。

122、什麼是Web Service(Web服務)? 
答:從表面上看,Web Service就是一個應用程序,它向外界暴露出一個能夠通過Web進行調用的API。這就是說,你能夠用編程的方法透明的調用這個應用程序,不需要了解它的任何細節,跟你使用的編程語言也沒有關係。例如可以創建一個提供天氣預報的Web Service,那麼無論你用哪種編程語言開發的應用都可以通過調用它的API並傳入城市信息來獲得該城市的天氣預報。之所以稱之爲Web Service,是因爲它基於HTTP協議傳輸數據,這使得運行在不同機器上的不同應用無須藉助附加的、專門的第三方軟件或硬件,就可相互交換數據或集成。

補充:這裏必須要提及的一個概念是SOA(Service-Oriented Architecture,面向服務的架構),SOA是一種思想,它將應用程序的不同功能單元通過中立的契約聯繫起來,獨立於硬件平臺、操作系統和編程語言,使得各種形式的功能單元能夠更好的集成。顯然,Web Service是SOA的一種較好的解決方案,它更多的是一種標準,而不是一種具體的技術。

123、概念解釋:SOAP、WSDL、UDDI。 
答: 
- SOAP:簡單對象訪問協議(Simple Object Access Protocol),是Web Service中交換數據的一種協議規範。 
- WSDL:Web服務描述語言(Web Service Description Language),它描述了Web服務的公共接口。這是一個基於XML的關於如何與Web服務通訊和使用的服務描述;也就是描述與目錄中列出的Web服務進行交互時需要綁定的協議和信息格式。通常採用抽象語言描述該服務支持的操作和信息,使用的時候再將實際的網絡協議和信息格式綁定給該服務。 
- UDDI:統一描述、發現和集成(Universal Description, Discovery and Integration),它是一個基於XML的跨平臺的描述規範,可以使世界範圍內的企業在互聯網上發佈自己所提供的服務。簡單的說,UDDI是訪問各種WSDL的一個門面(可以參考設計模式中的門面模式)。

提示:關於Web Service的相關概念和知識可以在W3CSchool上找到相關的資料。

124、Java規範中和Web Service相關的規範有哪些? 
答:Java規範中和Web Service相關的有三個: 
- JAX-WS(JSR 224):這個規範是早期的基於SOAP的Web Service規範JAX-RPC的替代版本,它並不提供向下兼容性,因爲RPC樣式的WSDL以及相關的API已經在Java EE5中被移除了。WS-MetaData是JAX-WS的依賴規範,提供了基於註解配置Web Service和SOAP消息的相關API。 
- JAXM(JSR 67):定義了發送和接收消息所需的API,相當於Web Service的服務器端。 
- JAX-RS(JSR 311 & JSR 339 & JSR 370):是Java針對REST(Representation State Transfer)架構風格制定的一套Web Service規範。REST是一種軟件架構模式,是一種風格,它不像SOAP那樣本身承載着一種消息協議, (兩種風格的Web Service均採用了HTTP做傳輸協議,因爲HTTP協議能穿越防火牆,Java的遠程方法調用(RMI)等是重量級協議,通常不能穿越防火牆),因此可以將REST視爲基於HTTP協議的軟件架構。REST中最重要的兩個概念是資源定位和資源操作,而HTTP協議恰好完整的提供了這兩個點。HTTP協議中的URI可以完成資源定位,而GET、POST、OPTION、DELETE方法可以完成資源操作。因此REST完全依賴HTTP協議就可以完成Web Service,而不像SOAP協議那樣只利用了HTTP的傳輸特性,定位和操作都是由SOAP協議自身完成的,也正是由於SOAP消息的存在使得基於SOAP的Web Service顯得笨重而逐漸被淘汰。

125、介紹一下你瞭解的Java領域的Web Service框架。 
答:Java領域的Web Service框架很多,包括Axis2(Axis的升級版本)、Jersey(RESTful的Web Service框架)、CXF(XFire的延續版本)、HessianTurmericJBoss SOA等,其中絕大多數都是開源框架。

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

127、持久層設計要考慮的問題有哪些?你用過的持久層框架有哪些? 
答:所謂"持久"就是將數據保存到可掉電式存儲設備中以便今後使用,簡單的說,就是將內存中的數據保存到關係型數據庫、文件系統、消息隊列等提供持久化支持的設備中。持久層就是系統中專注於實現數據持久化的相對獨立的層面。

持久層設計的目標包括: 
- 數據存儲邏輯的分離,提供抽象化的數據訪問接口。 
- 數據訪問底層實現的分離,可以在不修改代碼的情況下切換底層實現。 
- 資源管理和調度的分離,在數據訪問層實現統一的資源調度(如緩存機制)。 
- 數據抽象,提供更面向對象的數據操作。

持久層框架有: 
Hibernate 
MyBatis 
TopLink 
Guzz 
jOOQ 
Spring Data 
ActiveJDBC

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

129、Hibernate中Session的load和get方法的區別是什麼? 
答:主要有以下三項區別: 
① 如果沒有找到符合條件的記錄,get方法返回null,load方法拋出異常。 
② get方法直接返回實體類對象,load方法返回實體類對象的代理。 
③ 在Hibernate 3之前,get方法只在一級緩存中進行數據查找,如果沒有找到對應的數據則越過二級緩存,直接發出SQL語句完成數據讀取;load方法則可以從二級緩存中獲取數據;從Hibernate 3開始,get方法不再是對二級緩存只寫不讀,它也是可以訪問二級緩存的。

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

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

131、闡述Session加載實體對象的過程。 
答:Session加載實體對象的步驟是: 
① Session在調用數據庫查詢功能之前,首先會在一級緩存中通過實體類型和主鍵進行查找,如果一級緩存查找命中且數據狀態合法,則直接返回; 
② 如果一級緩存沒有命中,接下來Session會在當前NonExists記錄(相當於一個查詢黑名單,如果出現重複的無效查詢可以迅速做出判斷,從而提升性能)中進行查找,如果NonExists中存在同樣的查詢條件,則返回null; 
③ 如果一級緩存查詢失敗則查詢二級緩存,如果二級緩存命中則直接返回; 
④ 如果之前的查詢都未命中,則發出SQL語句,如果查詢未發現對應記錄則將此次查詢添加到Session的NonExists中加以記錄,並返回null; 
⑤ 根據映射配置和SQL語句得到ResultSet,並創建對應的實體對象; 
⑥ 將對象納入Session(一級緩存)的管理; 
⑦ 如果有對應的攔截器,則執行攔截器的onLoad方法; 
⑧ 如果開啓並設置了要使用二級緩存,則將數據對象納入二級緩存; 
⑨ 返回數據對象。

132、Query接口的list方法和iterate方法有什麼區別? 
答: 
① list()方法無法利用一級緩存和二級緩存(對緩存只寫不讀),它只能在開啓查詢緩存的前提下使用查詢緩存;iterate()方法可以充分利用緩存,如果目標數據只讀或者讀取頻繁,使用iterate()方法可以減少性能開銷。 
② list()方法不會引起N+1查詢問題,而iterate()方法可能引起N+1查詢問題

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

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

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

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

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

這裏寫圖片描述

  • 瞬時態:當new一個實體對象後,這個對象處於瞬時態,即這個對象只是一個保存臨時數據的內存區域,如果沒有變量引用這個對象,則會被JVM的垃圾回收機制回收。這個對象所保存的數據與數據庫沒有任何關係,除非通過Session的save()、saveOrUpdate()、persist()、merge()方法把瞬時態對象與數據庫關聯,並把數據插入或者更新到數據庫,這個對象才轉換爲持久態對象。
  • 持久態:持久態對象的實例在數據庫中有對應的記錄,並擁有一個持久化標識(ID)。對持久態對象進行delete操作後,數據庫中對應的記錄將被刪除,那麼持久態對象與數據庫記錄不再存在對應關係,持久態對象變成移除態(可以視爲瞬時態)。持久態對象被修改變更後,不會馬上同步到數據庫,直到數據庫事務提交。
  • 遊離態:當Session進行了close()、clear()、evict()或flush()後,實體對象從持久態變成遊離態,對象雖然擁有持久和與數據庫對應記錄一致的標識值,但是因爲對象已經從會話中清除掉,對象不在持久化管理之內,所以處於遊離態(也叫脫管態)。遊離態的對象與臨時狀態對象是十分相似的,只是它還含有持久化標識。

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

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

延遲加載與session關閉的矛盾一般可以這樣處理: 
① 關閉延遲加載特性。這種方式操作起來比較簡單,因爲Hibernate的延遲加載特性是可以通過映射文件或者註解進行配置的,但這種解決方案存在明顯的缺陷。首先,出現"no session or session was closed"通常說明系統中已經存在主外鍵關聯,如果去掉延遲加載的話,每次查詢的開銷都會變得很大。 
② 在session關閉之前先獲取需要查詢的數據,可以使用工具方法Hibernate.isInitialized()判斷對象是否被加載,如果沒有被加載則可以使用Hibernate.initialize()方法加載對象。 
③ 使用攔截器或過濾器延長Session的生命週期直到視圖獲得數據。Spring整合Hibernate提供的OpenSessionInViewFilter和OpenSessionInViewInterceptor就是這種做法。

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

138、談一下你對繼承映射的理解。 
答:繼承關係的映射策略有三種: 
① 每個繼承結構一張表(table per class hierarchy),不管多少個子類都用一張表。 
② 每個子類一張表(table per subclass),公共信息放一張表,特有信息放單獨的表。 
③ 每個具體類一張表(table per concrete class),有多少個子類就有多少張表。 
第一種方式屬於單表策略,其優點在於查詢子類對象的時候無需表連接,查詢速度快,適合多態查詢;缺點是可能導致表很大。後兩種方式屬於多表策略,其優點在於數據存儲緊湊,其缺點是需要進行連接查詢,不適合多態查詢。

139、簡述Hibernate常見優化策略。 
答:這個問題應當挑自己使用過的優化策略回答,常用的有: 
① 制定合理的緩存策略(二級緩存、查詢緩存)。 
② 採用合理的Session管理機制。 
③ 儘量使用延遲加載特性。 
④ 設定合理的批處理參數。 
⑤ 如果可以,選用UUID作爲主鍵生成器。 
⑥ 如果可以,選用基於版本號的樂觀鎖替代悲觀鎖。 
⑦ 在開發過程中, 開啓hibernate.show_sql選項查看生成的SQL,從而瞭解底層的狀況;開發完成後關閉此選項。 
⑧ 考慮數據庫本身的優化,合理的索引、恰當的數據分區策略等都會對持久層的性能帶來可觀的提升,但這些需要專業的DBA(數據庫管理員)提供支持。

140、談一談Hibernate的一級緩存、二級緩存和查詢緩存。 
答:Hibernate的Session提供了一級緩存的功能,默認總是有效的,當應用程序保存持久化實體、修改持久化實體時,Session並不會立即把這種改變提交到數據庫,而是緩存在當前的Session中,除非顯示調用了Session的flush()方法或通過close()方法關閉Session。通過一級緩存,可以減少程序與數據庫的交互,從而提高數據庫訪問性能。 
SessionFactory級別的二級緩存是全局性的,所有的Session可以共享這個二級緩存。不過二級緩存默認是關閉的,需要顯示開啓並指定需要使用哪種二級緩存實現類(可以使用第三方提供的實現)。一旦開啓了二級緩存並設置了需要使用二級緩存的實體類,SessionFactory就會緩存訪問過的該實體類的每個對象,除非緩存的數據超出了指定的緩存空間。 
一級緩存和二級緩存都是對整個實體進行緩存,不會緩存普通屬性,如果希望對普通屬性進行緩存,可以使用查詢緩存。查詢緩存是將HQL或SQL語句以及它們的查詢結果作爲鍵值對進行緩存,對於同樣的查詢可以直接從緩存中獲取數據。查詢緩存默認也是關閉的,需要顯示開啓。

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

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

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

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

145、MyBatis中的動態SQL是什麼意思? 
答:對於一些複雜的查詢,我們可能會指定多個查詢條件,但是這些條件可能存在也可能不存在,例如在58同城上面找房子,我們可能會指定面積、樓層和所在位置來查找房源,也可能會指定面積、價格、戶型和所在位置來查找房源,此時就需要根據用戶指定的條件動態生成SQL語句。如果不使用持久層框架我們可能需要自己拼裝SQL語句,還好MyBatis提供了動態SQL的功能來解決這個問題。MyBatis中用於實現動態SQL的元素主要有: 
- if 
- choose / when / otherwise 
- trim 
- where 
- set 
- foreach

下面是映射文件的片段。

    <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
  • 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 
        <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
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

再看看下面這個例子。

    <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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

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

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

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

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

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

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

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

ThreadLocal類非常簡單好用,只有四個方法,能用上的也就是下面三個方法: 
- void set(T value):設置當前線程的線程局部變量的值。 
- T get():獲得當前線程所對應的線程局部變量的值。 
- void remove():刪除當前線程中線程局部變量的值。

ThreadLocal是如何做到爲每一個線程維護一份獨立的變量副本的呢?在ThreadLocal類中有一個Map,鍵爲線程對象,值是其線程對應的變量的副本,自己要模擬實現一個ThreadLocal類其實並不困難,代碼如下所示:

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());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

148、解釋一下什麼叫AOP(面向切面編程)? 
答:AOP(Aspect-Oriented Programming)指一種程序設計範型,該範型以一種稱爲切面(aspect)的語言構造爲基礎,切面是一種新的模塊化機制,用來描述分散在對象、類或方法中的橫切關注點(crosscutting concern)。

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

150、你如何理解AOP中的連接點(Joinpoint)、切點(Pointcut)、增強(Advice)、引介(Introduction)、織入(Weaving)、切面(Aspect)這些概念? 
答: 
a. 連接點(Joinpoint):程序執行的某個特定位置(如:某個方法調用前、調用後,方法拋出異常後)。一個類或一段程序代碼擁有一些具有邊界性質的特定點,這些代碼中的特定點就是連接點。Spring僅支持方法的連接點。 
b. 切點(Pointcut):如果連接點相當於數據中的記錄,那麼切點相當於查詢條件,一個切點可以匹配多個連接點。Spring AOP的規則解析引擎負責解析切點所設定的查詢條件,找到對應的連接點。 
c. 增強(Advice):增強是織入到目標類連接點上的一段程序代碼。Spring提供的增強接口都是帶方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。很多資料上將增強譯爲“通知”,這明顯是個詞不達意的翻譯,讓很多程序員困惑了許久。

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

d. 引介(Introduction):引介是一種特殊的增強,它爲類添加一些屬性和方法。這樣,即使一個業務類原本沒有實現某個接口,通過引介功能,可以動態的未該業務類添加接口的實現邏輯,讓業務類成爲這個接口的實現類。 
e. 織入(Weaving):織入是將增強添加到目標類具體連接點上的過程,AOP有三種織入方式:①編譯期織入:需要特殊的Java編譯期(例如AspectJ的ajc);②裝載期織入:要求使用特殊的類加載器,在裝載類的時候對類進行增強;③運行時織入:在運行時爲目標類生成代理實現增強。Spring採用了動態代理的方式實現了運行時織入,而AspectJ採用了編譯期織入和裝載期織入的方式。 
f. 切面(Aspect):切面是由切點和增強(引介)組成的,它包括了對橫切關注功能的定義,也包括了對連接點的定義。

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

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

/**
 * 參考人員接口
 * @author 駱昊
 *
 */
public interface Candidate {

    /**
     * 答題
     */
    public void answerTheQuestions();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
/**
 * 懶學生
 * @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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
/**
 * 槍手
 * @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
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
public class ProxyTest1 {

    public static void main(String[] args) {
        Candidate c = new Gunman(new LazyStudent("王小二"));
        c.answerTheQuestions();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

說明:從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;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 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.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");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

151、Spring中自動裝配的方式有哪些? 
答: 
- no:不進行自動裝配,手動設置Bean的依賴關係。 
- byName:根據Bean的名字進行自動裝配。 
- byType:根據Bean的類型進行自動裝配。 
- constructor:類似於byType,不過是應用於構造器的參數,如果正好有一個Bean與構造器的參數類型相同則可以自動裝配,否則會導致錯誤。 
- autodetect:如果有默認的構造器,則通過constructor的方式進行自動裝配,否則使用byType的方式進行自動裝配。

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

152、Spring中如何使用註解來配置Bean?有哪些相關的註解? 
答:首先需要在Spring配置文件中增加如下配置:

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

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

153、Spring支持的事務管理類型有哪些?你在項目中使用哪種方式? 
答:Spring支持編程式事務管理和聲明式事務管理。許多Spring框架的用戶選擇聲明式事務管理,因爲這種方式和應用程序的關聯較少,因此更加符合輕量級容器的概念。聲明式事務管理要優於編程式事務管理,儘管在靈活性方面它弱於編程式事務管理,因爲編程式事務允許你通過代碼控制業務。

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

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

事務管理器實現類 目標對象
DataSourceTransactionManager 注入DataSource
HibernateTransactionManager 注入SessionFactory
JdoTransactionManager 管理JDO事務
JtaTransactionManager 使用JTA管理事務
PersistenceBrokerTransactionManager 管理Apache的OJB事務

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

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

<?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
  • 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
  • 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
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
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);
            }
        });
    }


}
  • 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
  • 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

聲明式事務如下圖所示,以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>
  • 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
  • 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
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
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 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;

/**
 * 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
  • 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
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
  • 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
  • 180
  • 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
  • 180
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
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 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 駱昊
 *
 * @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
  • 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
  • 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.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
  • 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.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
  • 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.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
  • 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
  • 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;

/**
 * 頁碼範圍
 * @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
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 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
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
  • 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.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;
    }

}
  • 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
  • 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

154、如何在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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

155、如何在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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

說明:上面的配置中使用了*.html的後綴映射,這樣做一方面不能夠通過URL推斷採用了何種服務器端的技術,另一方面可以欺騙搜索引擎,因爲搜索引擎不會搜索動態頁面,這種做法稱爲僞靜態化。

156、Spring MVC的工作原理是怎樣的? 
答:Spring MVC的工作原理如下圖所示: 
這裏寫圖片描述 
① 客戶端的所有請求都交給前端控制器DispatcherServlet來處理,它會負責調用系統的其他模塊來真正處理用戶的請求。 
② DispatcherServlet收到請求後,將根據請求的信息(包括URL、HTTP協議方法、請求頭、請求參數、Cookie等)以及HandlerMapping的配置找到處理該請求的Handler(任何一個對象都可以作爲請求的Handler)。 
③在這個地方Spring會通過HandlerAdapter對該處理器進行封裝。 
④ HandlerAdapter是一個適配器,它用統一的接口對各種Handler中的方法進行調用。 
⑤ Handler完成對用戶請求的處理後,會返回一個ModelAndView對象給DispatcherServlet,ModelAndView顧名思義,包含了數據模型以及相應的視圖的信息。 
⑥ ModelAndView的視圖是邏輯視圖,DispatcherServlet還要藉助ViewResolver完成從邏輯視圖到真實視圖對象的解析工作。 
⑦ 當得到真正的視圖對象後,DispatcherServlet會利用視圖對象對模型數據進行渲染。 
⑧ 客戶端得到響應,可能是一個普通的HTML頁面,也可以是XML或JSON字符串,還可以是一張圖片或者一個PDF文件。

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

<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"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

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

<?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>
  • 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
  • 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

159、選擇使用Spring框架的原因(Spring框架爲企業級開發帶來的好處有哪些)? 
答:可以從以下幾個方面作答: 
- 非侵入式:支持基於POJO的編程模式,不強制性的要求實現Spring框架中的接口或繼承Spring框架中的類。 
- IoC容器:IoC容器幫助應用程序管理對象以及對象之間的依賴關係,對象之間的依賴關係如果發生了改變只需要修改配置文件而不是修改代碼,因爲代碼的修改可能意味着項目的重新構建和完整的迴歸測試。有了IoC容器,程序員再也不需要自己編寫工廠、單例,這一點特別符合Spring的精神"不要重複的發明輪子"。 
- AOP(面向切面編程):將所有的橫切關注功能封裝到切面(aspect)中,通過配置的方式將橫切關注功能動態添加到目標代碼上,進一步實現了業務邏輯和系統服務之間的分離。另一方面,有了AOP程序員可以省去很多自己寫代理類的工作。 
- MVC:Spring的MVC框架是非常優秀的,從各個方面都可以甩Struts 2幾條街,爲Web表示層提供了更好的解決方案。 
- 事務管理:Spring以寬廣的胸懷接納多種持久層技術,並且爲其提供了聲明式的事務管理,在不需要任何一行代碼的情況下就能夠完成事務管理。 
- 其他:選擇Spring框架的原因還遠不止於此,Spring爲Java企業級開發提供了一站式選擇,你可以在需要的時候使用它的部分和全部,更重要的是,你甚至可以在感覺不到Spring存在的情況下,在你的項目中使用Spring提供的各種優秀的功能。

160、Spring IoC容器配置Bean的方式? 
答: 
- 基於XML文件進行配置。 
- 基於註解進行配置。 
- 基於Java程序進行配置(Spring 3+)

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
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 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.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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
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
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

161、闡述Spring框架中Bean的生命週期? 
答: 
① Spring IoC容器找到關於Bean的定義並實例化該Bean。 
② Spring IoC容器對Bean進行依賴注入。 
③ 如果Bean實現了BeanNameAware接口,則將該Bean的id傳給setBeanName方法。 
④ 如果Bean實現了BeanFactoryAware接口,則將BeanFactory對象傳給setBeanFactory方法。 
⑤ 如果Bean實現了BeanPostProcessor接口,則調用其postProcessBeforeInitialization方法。 
⑥ 如果Bean實現了InitializingBean接口,則調用其afterPropertySet方法。 
⑦ 如果有和Bean關聯的BeanPostProcessors對象,則這些對象的postProcessAfterInitialization方法被調用。 
⑧ 當銷燬Bean實例時,如果Bean實現了DisposableBean接口,則調用其destroy方法。

162、依賴注入時如何注入集合屬性? 
答:可以在定義Bean屬性時,通過<list> / <set> / <map> / <props>分別爲其注入列表、集合、映射和鍵值都是字符串的映射屬性。

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

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

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

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

166、你用過的網站前端優化的技術有哪些? 
答: 
① 瀏覽器訪問優化: 
- 減少HTTP請求數量:合併CSS、合併JavaScript、合併圖片(CSS Sprite) 
- 使用瀏覽器緩存:通過設置HTTP響應頭中的Cache-Control和Expires屬性,將CSS、JavaScript、圖片等在瀏覽器中緩存,當這些靜態資源需要更新時,可以更新HTML文件中的引用來讓瀏覽器重新請求新的資源 
- 啓用壓縮 
- CSS前置,JavaScript後置 
- 減少Cookie傳輸 
② CDN加速:CDN(Content Distribute Network)的本質仍然是緩存,將數據緩存在離用戶最近的地方,CDN通常部署在網絡運營商的機房,不僅可以提升響應速度,還可以減少應用服務器的壓力。當然,CDN緩存的通常都是靜態資源。 
③ 反向代理:反向代理相當於應用服務器的一個門面,可以保護網站的安全性,也可以實現負載均衡的功能,當然最重要的是它緩存了用戶訪問的熱點資源,可以直接從反向代理將某些內容返回給用戶瀏覽器。

167、你使用過的應用服務器優化技術有哪些? 
答: 
① 分佈式緩存:緩存的本質就是內存中的哈希表,如果設計一個優質的哈希函數,那麼理論上哈希表讀寫的漸近時間複雜度爲O(1)。緩存主要用來存放那些讀寫比很高、變化很少的數據,這樣應用程序讀取數據時先到緩存中讀取,如果沒有或者數據已經失效再去訪問數據庫或文件系統,並根據擬定的規則將數據寫入緩存。對網站數據的訪問也符合二八定律(Pareto分佈,冪律分佈),即80%的訪問都集中在20%的數據上,如果能夠將這20%的數據緩存起來,那麼系統的性能將得到顯著的改善。當然,使用緩存需要解決以下幾個問題: 
- 頻繁修改的數據; 
- 數據不一致與髒讀; 
- 緩存雪崩(可以採用分佈式緩存服務器集羣加以解決,memcached是廣泛採用的解決方案); 
- 緩存預熱; 
- 緩存穿透(惡意持續請求不存在的數據)。 
② 異步操作:可以使用消息隊列將調用異步化,通過異步處理將短時間高併發產生的事件消息存儲在消息隊列中,從而起到削峯作用。電商網站在進行促銷活動時,可以將用戶的訂單請求存入消息隊列,這樣可以抵禦大量的併發訂單請求對系統和數據庫的衝擊。目前,絕大多數的電商網站即便不進行促銷活動,訂單系統都採用了消息隊列來處理。 
③ 使用集羣。 
④ 代碼優化: 
- 多線程:基於Java的Web開發基本上都通過多線程的方式響應用戶的併發請求,使用多線程技術在編程上要解決線程安全問題,主要可以考慮以下幾個方面:A. 將對象設計爲無狀態對象(這和麪向對象的編程觀點是矛盾的,在面向對象的世界中被視爲不良設計),這樣就不會存在併發訪問時對象狀態不一致的問題。B. 在方法內部創建對象,這樣對象由進入方法的線程創建,不會出現多個線程訪問同一對象的問題。使用ThreadLocal將對象與線程綁定也是很好的做法,這一點在前面已經探討過了。C. 對資源進行併發訪問時應當使用合理的鎖機制。 
- 非阻塞I/O: 使用單線程和非阻塞I/O是目前公認的比多線程的方式更能充分發揮服務器性能的應用模式,基於Node.js構建的服務器就採用了這樣的方式。Java在JDK 1.4中就引入了NIO(Non-blocking I/O),在Servlet 3規範中又引入了異步Servlet的概念,這些都爲在服務器端採用非阻塞I/O提供了必要的基礎。 
- 資源複用:資源複用主要有兩種方式,一是單例,二是對象池,我們使用的數據庫連接池、線程池都是對象池化技術,這是典型的用空間換取時間的策略,另一方面也實現對資源的複用,從而避免了不必要的創建和釋放資源所帶來的開銷。

168、什麼是XSS攻擊?什麼是SQL注入攻擊?什麼是CSRF攻擊? 
答: 
- XSS(Cross Site Script,跨站腳本攻擊)是向網頁中注入惡意腳本在用戶瀏覽網頁時在用戶瀏覽器中執行惡意腳本的攻擊方式。跨站腳本攻擊分有兩種形式:反射型攻擊(誘使用戶點擊一個嵌入惡意腳本的鏈接以達到攻擊的目標,目前有很多攻擊者利用論壇、微博發佈含有惡意腳本的URL就屬於這種方式)和持久型攻擊(將惡意腳本提交到被攻擊網站的數據庫中,用戶瀏覽網頁時,惡意腳本從數據庫中被加載到頁面執行,QQ郵箱的早期版本就曾經被利用作爲持久型跨站腳本攻擊的平臺)。XSS雖然不是什麼新鮮玩意,但是攻擊的手法卻不斷翻新,防範XSS主要有兩方面:消毒(對危險字符進行轉義)和HttpOnly(防範XSS攻擊者竊取Cookie數據)。 
- SQL注入攻擊是注入攻擊最常見的形式(此外還有OS注入攻擊(Struts 2的高危漏洞就是通過OGNL實施OS注入攻擊導致的)),當服務器使用請求參數構造SQL語句時,惡意的SQL被嵌入到SQL中交給數據庫執行。SQL注入攻擊需要攻擊者對數據庫結構有所瞭解才能進行,攻擊者想要獲得表結構有多種方式:(1)如果使用開源系統搭建網站,數據庫結構也是公開的(目前有很多現成的系統可以直接搭建論壇,電商網站,雖然方便快捷但是風險是必須要認真評估的);(2)錯誤回顯(如果將服務器的錯誤信息直接顯示在頁面上,攻擊者可以通過非法參數引發頁面錯誤從而通過錯誤信息瞭解數據庫結構,Web應用應當設置友好的錯誤頁,一方面符合最小驚訝原則,一方面屏蔽掉可能給系統帶來危險的錯誤回顯信息);(3)盲注。防範SQL注入攻擊也可以採用消毒的方式,通過正則表達式對請求參數進行驗證,此外,參數綁定也是很好的手段,這樣惡意的SQL會被當做SQL的參數而不是命令被執行,JDBC中的PreparedStatement就是支持參數綁定的語句對象,從性能和安全性上都明顯優於Statement。 
- CSRF攻擊(Cross Site Request Forgery,跨站請求僞造)是攻擊者通過跨站請求,以合法的用戶身份進行非法操作(如轉賬或發帖等)。CSRF的原理是利用瀏覽器的Cookie或服務器的Session,盜取用戶身份,其原理如下圖所示。防範CSRF的主要手段是識別請求者的身份,主要有以下幾種方式:(1)在表單中添加令牌(token);(2)驗證碼;(3)檢查請求頭中的Referer(前面提到防圖片盜鏈接也是用的這種方式)。令牌和驗證都具有一次消費性的特徵,因此在原理上一致的,但是驗證碼是一種糟糕的用戶體驗,不是必要的情況下不要輕易使用驗證碼,目前很多網站的做法是如果在短時間內多次提交一個表單未獲得成功後纔要求提供驗證碼,這樣會獲得較好的用戶體驗。

這裏寫圖片描述

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

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

貧血模型 
這裏寫圖片描述

充血模型 
這裏寫圖片描述

貧血模型下組織領域邏輯通常使用事務腳本模式,讓每個過程對應用戶可能要做的一個動作,每個動作由一個過程來驅動。也就是說在設計業務邏輯接口的時候,每個方法對應着用戶的一個操作,這種模式有以下幾個有點: 
- 它是一個大多數開發者都能夠理解的簡單過程模型(適合國內的絕大多數開發者)。 
- 它能夠與一個使用行數據入口或表數據入口的簡單數據訪問層很好的協作。 
- 事務邊界的顯而易見,一個事務開始於腳本的開始,終止於腳本的結束,很容易通過代理(或切面)實現聲明式事務。 
然而,事務腳本模式的缺點也是很多的,隨着領域邏輯複雜性的增加,系統的複雜性將迅速增加,程序結構將變得極度混亂。開源中國社區上有一篇很好的譯文《貧血領域模型是如何導致糟糕的軟件產生》對這個問題做了比較細緻的闡述。

170. 談一談測試驅動開發(TDD)的好處以及你的理解。 
答:TDD是指在編寫真正的功能實現代碼之前先寫測試代碼,然後根據需要重構實現代碼。在JUnit的作者Kent Beck的大作《測試驅動開發:實戰與模式解析》(Test-Driven Development: by Example)一書中有這麼一段內容:“消除恐懼和不確定性是編寫測試驅動代碼的重要原因”。因爲編寫代碼時的恐懼會讓你小心試探,讓你迴避溝通,讓你羞於得到反饋,讓你變得焦躁不安,而TDD是消除恐懼、讓Java開發者更加自信更加樂於溝通的重要手段。TDD會帶來的好處可能不會馬上呈現,但是你在某個時候一定會發現,這些好處包括: 
- 更清晰的代碼 — 只寫需要的代碼 
- 更好的設計 
- 更出色的靈活性 — 鼓勵程序員面向接口編程 
- 更快速的反饋 — 不會到系統上線時才知道bug的存在

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

TDD可以在多個層級上應用,包括單元測試(測試一個類中的代碼)、集成測試(測試類之間的交互)、系統測試(測試運行的系統)和系統集成測試(測試運行的系統包括使用的第三方組件)。TDD的實施步驟是:紅(失敗測試)- 綠(通過測試) - 重構。關於實施TDD的詳細步驟請參考另一篇文章《測試驅動開發之初窺門徑》。 
在使用TDD開發時,經常會遇到需要被測對象需要依賴其他子系統的情況,但是你希望將測試代碼跟依賴項隔離,以保證測試代碼僅僅針對當前被測對象或方法展開,這時候你需要的是測試替身。測試替身可以分爲四類: 
- 虛設替身:只傳遞但是不會使用到的對象,一般用於填充方法的參數列表 
- 存根替身:總是返回相同的預設響應,其中可能包括一些虛設狀態 
- 僞裝替身:可以取代真實版本的可用版本(比真實版本還是會差很多) 
- 模擬替身:可以表示一系列期望值的對象,並且可以提供預設響應 
Java世界中實現模擬替身的第三方工具非常多,包括EasyMock、Mockito、jMock等。


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