JavaWeb進階程式設計下篇:主要介紹标簽庫的使用,以及spring架構的簡單使用方法
Java标準标簽庫
JSP标簽文法中包含一些簡寫可以幫助輕松編寫JSP。這些簡寫中第一個就是taglib指令。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
指令是XML文檔中引用XML命名空間的一種方式,是XMLNS技術的替代品。
指令taglib中的prefix特性代表了在JSP頁面中引用标簽庫時使用的命名空間。
特性uri标志着TLD中為該标簽庫定義的URI。
所有的JSP标簽都遵守相同的基本文法:
<prefix:tagname[ attribute=value[ attribute=value[ ...]]]
</prefix:tagname>
使用核心标簽庫
使用命名空間:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
使用方法:
<c:out value="${someVariable}" default="value not specified" excapeXml="false" />
其中excapeXml設定為假禁止對保留的XML字元進行轉義。default屬性指定當value特性為null時使用的預設值。
标簽
<c:url>
标簽
<c:url>
可以正确地對URL編碼,并且在需要添加會話ID的時候重寫它們,它還可以在JSP中輸出URL。
<c:url value="http://www.example.com/test.jsp">
<c:param name="story" value="${storyId}" />
</c:url>
<c:url>最有用的地方就在于對相對URL的編碼。
标簽
<c:if>
标簽
<c:if>
是一個條件标簽,用于控制是否渲染特定内容。
<c:if test="${something == somethingElse}" var="itWasTrue">
execute only if test is true
</c:if>
<c:if test="${itWasTrue}" />
do something
</c:if>
特性test指定了一個條件,隻有該條件為真時,
<c:if>
标簽中内嵌的内容才會被執行。var特性将把測試結果儲存為變量。
标簽
<c:choose>
,
<c:when>
,
<c:otherwise>
上述三個标簽提供了複雜的if/else-if/else邏輯。
<c:choose>
<c:when test="${something}">
"if"
</c:when>
<c:when test="{somethingElse}">
"else if"
</c:when>
...
<c:otherwise>
"else"
</c:otherwise>
</c:choose>
食用方法如上,類比 if-elseif-elseif-...-else 。
标簽
<c:forEach>
用于疊代并重複它的嵌套主題内容固定次數,或者周遊某些集合或數組。
<c:forEach var="i" begin="0" end="100" step="3">
Line ${i} <br />
</c:forEach>
<c:forEach items="${users}" var="user">
${user.name} <br />
</c:forEach>
特性items中的表達式必須是一些集合、Map、Iterator、Enumeration、對象數組或者原生數組。
标簽
<c:redirect>
該标簽将把使用者重定向至另一個URL,由name屬性決定。
<c:redirect url="http://www.example.com/" />
<c:redirect url="/tickets" >
<c:param name="action" value="view" />
</c:redirect>
标簽
<c:import>
該标簽用于擷取特性的URL資源内容。
<c:import url="/copyright.jsp" />
<c:import url="ad.jsp" context="/store" var="advertisement" scope="request">
<c:param name="category" value="${forumCategory}" />
</c:import>
<c:import url="http://www.example.com/embeddedPlayer.do?video=f8ETe45646"
varReader="player" charEncoding="UTF-8">
<wrox:writeViderPlugin reader="${player}" />
</c:import>
第一個樣例将應用程式本地的copyright.jsp中的内容内嵌在頁面中。第二個樣例将ad.jsp?category=${forumCategory}的内容儲存到請求作用域的字元串變量advertisement中,并對category查詢參數進行正确的編碼。第三個樣例擷取了一些外部資源,并将它導出為名為player的Reader對象。
标簽
<c:set><c:remove>
<c:set>标簽可以設定新的或現有的作用域變量,還可以使用它對應的标簽<c:remove>從作用域中删除變量。
<c:set var="myVariable" value="hello" />
${myVariable}
<c:remove var="myVariable" scope="page" />
編寫自定義标簽和函數庫
為了使用标簽檔案中定義的标簽,可以使用taglib中的tagdir特性幫助你完成:
<%@ taglib prefix="myTags" tagdir="/WEB-INF/tags" %>
tagdir目錄中所有的.tag或者.tagx檔案都将被綁定到myTags命名空間。
為編寫自定義的标簽和函數庫,必須了解JSP标簽庫XSD和使用它編寫标簽的方式。
關于JSP标簽庫XSD有一件重要的事情需要注意:模式使用了嚴格的元素順序,這意味着所有使用的元素必須嚴格按照特定的順序出現,否則該TLD檔案将是無效的。
<description>JSTL 1.2 core library</description>
<display-name>JSTL core</display-name>
<tlib-version>1.2</tlib-version>
<short-name>c</short-name>
<uri>http://java.sun.com/jsp/jstl/core</uri>
關于以上代碼:
-
- 元素和提供了XML工具可以顯示有用的名稱,但與TLD的實際内容無關并且是可選的。如果需要,可以添加許多和,這樣可以為不同的語言指定不同的顯示名稱和描述。
- 元素為可選元素,這裡沒有出現,它必須出現在和之前。
- 是必須元素。它定義了标簽庫的版本,其中隻能使用數字和圓點。
- 表示該标簽庫推薦使用,也是預設的字首,也是必須的,不能包括空白,或者以數字或下劃線開頭。
- 定義了該标簽庫的URI。
元素是TLD的主要元素,負責定義标簽庫中的标簽。
<tag>
<description>
catches throwable
</description>
<name>catch</name>
<tag-class>org.apache.taglibs.standard.tag.common.core.catchTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<description>
name of the exported scoped variable for the exception thrown from a nested action.
</description>
<name>var</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
如一樣,它可以由0個或多個嵌套的、和元素。在這些元素之後需要一個元素,它将指定JSP标簽的名稱在本例中,完整的标簽名稱為<c:catch>,c是标簽庫,catch是标簽的名稱。一個标簽明顯隻可以有一個名稱。接下來是元素,它表示負責執行标簽的标簽處理器類。
接下來是指定了标簽中允許嵌套的内容類型。
在之後是0個或多個元素,該元素提供了使用該标簽定義的結果變量的相關資訊。
在标簽的元素之後,可以定義0個或多個,它将為标簽定義可用的特性。
标簽之後是,該屬性不常見,隻可以指定該布爾元素一次或者忽略它。預設值為假,用于表示是否允許通過元素指定特性值。
在之後是可選的元素,為标簽的使用提供樣例。
标簽檔案實際上就是一種JSP,隻不過使用的語義稍有不同。
JAR庫中的标簽檔案必須定義在TLD中,另外,如果希望将一個或多個标簽檔案配置設定到相同的命名空間,那麼需要在TLD中定義這些标簽,即使它們不再JAR檔案中。
在TLD的所有元素之後,可以添加0個或多個元素,定義屬于庫的标簽檔案。
<tag-file>
<description>this tag outputs bar.</description>
<name>foo</name>
<path>/WEB-INF/tags/foo.tag</path>
</tag-file>
在元素中可選'和元素。
指定字首之後的标簽名;
指定實作自定義标簽的.tag檔案所在的路徑。
在TLD中定義标簽檔案之後,就可以使用元素定義0個或多個JSP函數。
<function>
<description>
...
<description>
<name>...</name>
<function-class>
url
</function-class>
<function-signature>
...
</function-signature>
<example>
...
</example>
</function>
标注一個标準Java類的完全限定名稱,函數簽明實際上是此類的靜态方法簽名。任何公共類上的所有公共靜态方法都可以通過這種方式成為JSP函數。
在TLD中所有的、和标簽之後,可以使用元素定義0個或多個标簽庫擴充。
相對于page指令,标簽檔案有一個tag指令。該指令将替換JSP page指令的必要功能,并且替換了TLD檔案的元素中的許多配置。
建立自定義标簽的最簡單的方式就是:編寫一個标簽使用含有tagdir特性的taglib指令。
<%@ taglib prefix="template" tagdir="/WEB-INF/tags/temple" %>
使用過濾器改進應用程式
過濾器是可以攔截通路資源的請求、資源的響應或者同時攔截兩者的應用元件。
過濾器可以檢測和修改請求和響應,它們甚至可以拒絕、重定向或轉發請求。
過濾器在初始化時将調用init方法,它可以通路過濾器的配置、初始化參數和SevletContext,正如Servlet的init方法一樣。當請求進入過濾器中時,doFilter方法将會被調用,它提供了對ServletRequest、ServletResponse和FilterChain對象的通路。在doFilter之中,可以拒絕請求或者調用FilterChain對象的doFilter方法,可以修改請求和響應,并且可以封裝請求和響應對象。
盡管隻有一個Servlet可以處理請求,但可以使用許多過濾器攔截請求。
如同Servlet一樣,過濾器可以被映射到URL模式,這會決定哪個過濾器将攔截某個請求。在任何比對某個過濾器的URL模式的請求在被比對的Servlet處理之前将首先進入該過濾器。
在聲明和映射過濾器攔截請求之前,必須如同Servlet一樣聲明和映射它們。傳統的方式是在部署描述符中使用和元素。必須至少包含一個名字和類名,它還可以包含描述、顯示名稱、圖示以及一個或多個初始化參數。
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.wrox.MyFilter</filter-class>
</filter>
與Servlet不同的是,過濾器不可以在第一個請求到達時加載。過濾器的init方法總是在應用程式啟動時調用。
在聲明了過濾器之後,可以将它映射到任意數目的URL或Servlet名稱。
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/foo</url-pattern>
<url-pattern>/bar/*</url-pattern>
<servlet-name>myServlet</servlet-name>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ASYNC</dispatcher>
<filter-mapping>
過濾器将會響應所有相對于應用程式的URL/foo和/bar/*的請求,以及任何最終由Servlet/myServlet處理的請求。這裡的兩個元素意味着它可以響應普通的請求和由AsyncContext派發的請求。有效的類型有:REQUEST、FORWARD、INCLUDE、ERROR和ASYNC。
如同Servlet一樣,可以注解聲明和映射過濾器。
@WebFilter(
filterName = "myFilter",
urlPatterns = { "/foo","/bar/*" },
servletNames = { "myServlet" },
dispatcher = { DispatcherType.REQUEST, DispatcherType.ASYNC }
)
public class MyFilter implements Filter{
...
}
使用注解聲明和映射過濾器的主要缺點是:不能對過濾器鍊上的過濾器進行排序。如果希望在不使用部署描述符的情況下控制過濾器的執行順序,那麼需要使用程式設計式配置。
如同Servlet、監聽器和其他元件一樣,可以在ServletContext中以程式設計的方式配置過濾器。不使用部署描述符和注解,調用ServletContext的方法注冊和映射過濾器即可。因為這必須要在ServletContext結束之前完成,所有通常需要在ServletContextListener的contextInitialized方法中也實作。
過濾器順序決定了過濾器在過濾器鍊中出現的位置,這反過來也決定了過濾器什麼時候處理請求。
使用注解時無法對過濾器進行排序。
定義過濾器順序是很簡單的:比對請求的過濾器将按照它們出現在部署描述符或者程式設計式配置中的順序添加到過濾器鍊中。不同的請求将比對不同的過濾器,但使用的過濾器順序總是相同的。URL映射的過濾器優先級比Servlet名稱映射到的過濾器高。如果兩個過濾器都可以比對某個請求,一個是URL模式而另一個是Servlet名稱,那麼在過濾器鍊中,由URL模式比對的過濾器總是出現由Servlet名稱比對的過濾器之前。
如果使用AsyncContext直接處理響應對象,代碼将在所有過濾器的範圍之外執行。不過,如果使用AsyncContext的dispatch方法在内部将請求轉發到某個URL,那麼映射到ASYNC請求的過濾器可以攔截該内部轉發請求,并應用必要的額外邏輯。
使用日志監控應用程式
"日志"不一定就是一個檔案。
日志事件有着不同的類型和嚴重程度。
常見的日志級别:
通用名稱 | 級别 | 常用語義
-
-
| -
緻命錯誤 | 沒有對等的常量 | 表示一種嚴重的錯誤形式,通常這種錯誤會導緻系統崩潰或者提前終止
錯誤 | SEVER | 表示發生了嚴重的問題
警告 | WARNING | 表示發生了一些可能是也可能不是問題的事件,并且可能需要進行檢查
資訊 | INFO | 表示資訊級别的日志,這些日志對應用程式監控和調試來說是非常有用的
介紹SpringFramework
Spring Framework是一個Java應用程式容器,它提供了許多有用的特性,例如反轉控制、依賴注入、抽象資料通路、事務管理等。
Spring Framework的核心特點之一就是對兩個緊密相關的觀念的支援:控制反轉(IoC)和依賴注入(DI)。IoC是一個軟體設計模式:組裝器将在運作時而不是在編譯時綁定對象。當某些程式邏輯元件,例如Service A,依賴于另一個程式邏輯元件Service B時,該依賴将在應用程式運作時實作,而不是由Service A直接執行個體化Service B。
盡管理論上可以通過多種方式實作IoC,但DI時最常見的技術。通過使用DI,一段程式代碼可以聲明它依賴于另一塊程式代碼,然後組裝器可以在運作時注入它依賴的執行個體。
因為Spring Framework負責處理執行個體化和依賴注入,是以它可以通過封裝注入依賴的執行個體,使用其他行為對方法調用進行裝飾。
Spring Framework提供了一個松耦合的消息系統,它使用的時釋出-訂閱模式:系統中的元件通過訂閱消息,聲明它對該消息感興趣,然後這些消息的生産者将會釋出該消息,而無須關系誰對消息感興趣。使用Spring Framework時,一個由Spring管理的bean可以通過實作一個通用接口訂閱特定的消息類型,其他由Spring管理的對象可以釋出這些消息到Spring Framework中,然後由Spring Framework将消息發送到已訂閱的bean中。
Spring Framework提供了一個模型-視圖-控制器(MVC)模式架構,它可以簡化建立互動式Web應用程式的過程。不用動手處理複雜的Servlet、HTTPServletRequest、HttpServletResponse以及JSP轉發,Spring将處理這些任務。控制器類的每個方法都将被映射到了一個不同的請求URL、方法或請求的其他屬性上。模型将以
Map<String Object>
的形式從控制器傳遞到視圖。控制器傳回的視圖或視圖名稱将使Spring把模型轉發到合适的JSP視圖。請求和URL路徑參數将被自動轉換為原始或複雜的控制器方法參數。
使用Spring 的Web MVC架構時,控制器類的行為非常像使用方法級别映射的Servlet。每個方法都可以擁有一個指定特性URL、請求方法、參數存在性、頭的值、内容類型和/或期望相應類型的唯一映射。當單元測試對小的代碼單元進行測試時,控制器類中可以包含許多映射方法,它們将被按邏輯進行分組。傳回到使用者配置樣例中,該控制器可以含有數十個方法,使用它們分别代表對使用者配置的不同操作,但必須使用doGet和DoPost将請求路由到正确的方法。Spring Framework将處理所有的分析和路由工作。
使用Spring時,業務邏輯将被封裝到一組被稱為服務的業務對象中。這些服務将執行所有使用者界面公共的操作。
Spring Framework容器以一個或多個應用上下文的形式存在,由org.springframework.context.ApplicationContext接口表示。一個應用上下文管理一組bean、執行業務邏輯的Java對象、執行任務、持久化和擷取持久化資料、響應HTTP請求等。由Spring管理的bean可以自動進行依賴注入、消息通知、定時方法執行、bean驗證和執行其他關鍵的Spring服務。
一個Spring應用程式至少需要一個應用上下文。
在Java EE Web應用程式中,Spring将使用派發器Servlet處理Web請求,該Servlet将把進入的申請委托給合适的控制器,并按需要對請求和響應實體進行轉換。
當配置告訴Spring如何運作它所包含的應用程式時,啟動程序将啟動Spring并将配置指令傳遞給它。在Java SE應用程式中,隻有一種方式啟動Spring;通過在應用程式的public static void main(String...)方法中以程式設計的方式啟動。在Java EE應用程式中,有兩種選擇:可以使用XML建立部署描述符啟動Spring,也可以在javax.servlet.ServletContainerInitializer中通過程式設計的方式啟動Spring。
傳統的Spring Framework應用程式總是使用Java EE部署描述符啟動Spring。至少,這要求在配置檔案中建立DispatcherServlet的一個執行個體,然後以cpntextConfigLocation啟動參數的形式為它提供配置檔案,并訓示Spring在啟動時加載它。
<servlet>
<servlet-name>springDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/servletContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>/<url-pattern>
</servlet-mapping>
該代碼将為DispatcherServlet建立出單個Spring應用上下文,并訓示Servlet容器在啟動時初始化DispatcherServlet。在初始化的時候,DispatcherServlet将從/WEB-INF/servletContext.xml檔案中加載上下文配置并啟動應用上下文。
實作了ServletContainerInitializer接口的類将在應用程式開始啟動時,并在所有監聽器啟動之前調用它們的onStartup方法。啟動類将使用Spring Java配置通過純Java的方式啟動和配置Spring。
bean是由Spring Framework管理的,是以這是在配置Spring Framework時我們主要需要完成的配置。
我們需要使用 XML命名空間:
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
<mvc:annotation-driven />
<bean name="greetingServiceImpl" class="xxx.GreetingServiceImpl" />
<bean name="helloController" class="xxx.HelloController">
<property name="greetingService" ref="greetingServiceImpl" />
</bean>
</beans>
該XML檔案将告訴Spring執行個體化GreetingServiceImpl和HelloController,并将greetingServiceImpl bean注入到helloController bean的greetingService屬性中。元素是包含了Spring配置的父元素。在其中幾乎可以使用所有其他Spring配置元素--不過一般來講,元素使用的元素都将引起bean的建立。
混合配置的核心是元件掃描和注解配置。所有标注了@org.springframework.stereotype.Component的類,都将變成Spring管理的bean,這意味着Spring将執行個體化它們并注入它們的依賴。
其他符合元件掃描的注解:任何标注了@Component的注解都将變成元件注解,任何标注了另一個元件注解的注解也将變成元件注解。是以,标注了@Controller、@Repository和@Service的類也将成為Spring管理的bean。
可以為任何私有、保護和公開字段或者接受一個或多個參數 的公開設定方法标注@Autowired。@Autowired聲明了Spring應該在執行個體化之後注入的依賴,并且它也可以用于标注構造器。通常由Spring管理的bean必須有無參構造器,但對于隻含有一個标注了@Autowire的構造器的類,Spring将使用該構造器并注入所有的構造器參數。
通常在bean的所有依賴都注入後,在它作為依賴被注入其他bean之前,可以在該bean上執行某種初始化操作。隻需要使用org.springframework.beans.factory.InitializingBean接口就可以輕松實作。在bean的所有配置都完成之後,Spring将調用它的afterPropertiesSet方法。
使用控制器替代Servlet
@RequestMapping是Spring工具集中一個非常強大的工具,通過它可以映射請求、請求的Content-Type或者Accept頭、HTTP請求頭、指定請求參數或頭是否存在,或者這些資訊的任意組合。使用了@RequestMapping之後,在Servlet的doGet或者類似的方法中選擇正确的方法時,就不再需要使用複雜的切換或者邏輯分支。請求将被自動路由到正确的控制器和方法。
@RequestMapping注解将把請求被映射到的方法縮小到特定的方法上。可以隻在控制器方法中添加@RequestMapping,或者同時在控制器類和它的方法中添加。
@RequestMapping("viewProduct")
public String viewProduct(...){...}
@RequestMapping("addToCart")
public String addProductToCart(...){...}
@RequestMapping("writeReview")
public String writeReview(...){...}
在上例中,如果将DispatcherServlet映射到上下文根(/),那麼這些方法相對于應用程式的URL将分别變成/viewProduct、/addToCart、/writeReview。
如果控制器中的許多URL都共享一個相同的元素,那麼可以使用映射繼承來減少映射中的備援。
@RequestMapping("product")
public class ProductController
{
@RequestMapping("view")
public String viewProduct(...){...}
@RequestMapping("addToCart")
public String addToCart(...){...}
@RequestMapping("writeReview")
public String writeProductReview(...){...}
}
在上例中,如果DispatcherServlet映射到上下文根的話,那麼方法URL将分别變成/product/view、/product/addToCart、和/product/writeReview。
URL映射的另一個重要方面是:如果請求比對到多個不同的URL映射,那麼最具體的映射勝出。
關于@RequestMapping value特性需要了解的最後一件事情是:它可以接受一組URL映射。是以,可以将多個URL映射到指定的方法上。在下例中,home方法将響應URL/,/home和/dashboard:
@RequestMapping({"/","home","dashboard"})
public String home(...){...}
控制器方法可以有任意數量的不同類型的參數。
可以使用幾個參數注解表示方法參數的值應該從請求的某些屬性中擷取。
@RequestParam注解表示被注解的方法參數應該派生自命名請求參數。使用value特性指定請求參數的名稱。
@RequestMapping("uesr")
public String user(@RequestParam("id") long userId,
@RequestParam(value="name", request=false) String name,
@RequestParam(value="key",defaultValue="") String key)
{...}
@RequestHeader的工作方式與@RequestParam一緻,它提供了對請求頭的值的通路,它指定了一個必須的或者可選的請求頭,用作相應方法的參數值。因為HTTP頭也可以有多個值,是以如果出現這種請求的話,應該使用數組或集合參數類型。
Spring Framework中的URL映射不必是靜态值。相反,該URL可以包含一個模闆,表示URL的某個部分是不可變的,它的值将在運作時決定。下面的代碼腳本示範可如何在URL映射中指定一個URL模闆,并通過@PathVariable的方式将該模闆變量用作方法參數的值。
@RequestMapping(value="user/(userId)", method=RequestMothod.GET)
public String user(@PathVariable("userId") long userId){...}
URL映射中可以包含多個模闆變量,每個模闆變量都可以有一個關聯的方法參數。另外,還可以将類型為Map<String,String>的單個方法參數标注為@PathVariavble,它将包含URL中所有URI模闆變量值。
Spring提供了@MatrixVariable注解,從URL中提取路徑參數用作方法參數。
Spring Framework允許指定一個表單對象作為控制器方法的參數。表單對象是含有設定和讀取方法的簡單POJO。它們不必事先實作任何特殊的接口,也不需要使用任何特殊的注解對控制器方法參數進行标記,Spring将把它識别為一個表單對象。
通過使用@RequestBody注解,Spring将自動把一個請求實體轉換為控制器方法參數。
public String update(@RequestBody Account account){...}
Spring将再繼續執行兩個步驟,将模型從請求中完全離開,并提供可以通過無限種方式實作的進階View接口。InternalResourceView和JstlView将分别實作傳統的JSP和JSTL增強JSP視圖。它們負責将模型特性轉換成請求特性,并将請求轉發到正确的JSP。
當控制器方法傳回一個View、或者ModelAndView的實作時,Spring将直接使用該View,并且不需要額外的邏輯用于判斷如何向用戶端展示模型。如果控制器方法傳回了一個字元串視圖名稱或者使用字元串視圖名稱構造的ModelAndView,Spring必須使用已配置的org.springframework.web.servlet.ViewResolver将視圖名稱解析成一個真正的視圖。如果方法傳回的是模型或者模型特性,Spring首先使用已配置的RequestToViewNameTranslator隐式地将請求轉換成視圖名稱,然後使用ViewResolver解析已命名地視圖。最後,當控制器方法傳回的是響應實體ResponseEntity或者HttpEntity時,Spring将使用内容協商決定将實體展示到哪個視圖中。
使用服務和倉庫支援控制器
模型-視圖-控制器模式
-
- 第一步:視圖發送指令到控制器
- 第二步:控制器從模型中讀取或操作資料
- 第三步:模型将資料發送到控制器
- 第四步:控制器将模型發送到視圖
使用者界面邏輯是所有隻用于支援特定使用者界面的邏輯。如果無論使用者如何與應用程式互動,都需要某一塊相同的代碼邏輯,那麼該邏輯就是業務邏輯。不過,如果一塊代碼邏輯隻對特定的使用者界面有用。
如同将使用者界面邏輯和業務邏輯分開一樣,也應該将持久邏輯與業務邏輯分隔開。
在控制器-服務-倉庫中,倉庫是最低的一層,它負責所有的持久化邏輯,将資料儲存到資料存儲中并從資料存儲中讀取已儲存的資料。使用
@Repository
注解标記出倉庫,表示它的語義目的。啟用了元件掃描之後,
@Repository
類所屬的Spring應用上下文将自動執行個體化、注入和管理這些倉庫。通常,每個倉庫負責一種持久化對象或實體。
倉庫需要實作特定的接口。
服務是倉庫之上的下一層。服務封裝了應用程式業務邏輯,它将使用其他服務和倉庫,但不能使用更高層應用程式的資源。服務被标記上了
@Service
注解,使它們可以自動執行個體化和依賴注入。如倉庫一樣,它也需要實作特定的接口。
如果要為應用程式建立一個RESTful或者SOAP Web服務,那麼我們可能需要在應用程式的上下文中建立一個單獨的DispatcherServer和
@Configuration
,并且配置也将變得不同,已反應該上下文中控制器處理請求的不同方式。
不應該在Web應用上下文中管理服務和倉庫,而是應該在根應用上下文中,它是所有Web應用上下文的父親。
在使用
@ComponentScan
注解時,要使用String[] basePackages特性告訴Spring掃描哪些Java包,查找可用的類。Spring将定義出這些包或子包中的所有類,并針對每個類應用資源過濾器。
對于Spring在基本包中找到的每個類,它都将應用已配置的過濾器。過濾器分為包含過濾器和派出過濾器。如果每個類觸發了任意一個包含過濾器,并且未觸發任何排除過濾器,那麼它将變成Spring bean,這意味着它将被構造、注入、初始化,并執行任何應用在Spring管理bean上的操作。
Spring Framework定義了不同但緊密相關的概念:執行器和排程器。執行器如它的名字所示:它執行任務。排程器負責記住任務應該什麼時候執行,然後按時執行。
為了在
@Async
方法上啟用異步方法執行,我們需要在
@Configuration
類上注解
@EnableAsync
。同樣地,為了在
@Scheduled
方法上啟用計劃方法執行,我們需要使用
@EnableScheduling
注解,你會希望在RootContextConfiguration上添加這些注解,以便在應用程式的所有bean之間共享配置。不過,
@EnableAsync
和
@EnableScheduling
它們自己可以建立出預設的異步和計劃配置。為了自定義該行為,我們需要實作AsyncConfigurer接口傳回正确的異步執行器,并通過實作SchedulingConfigurer類将正确的執行器賦給排程器。
@Configuration
@EnableAsync(proxyTargetClass = true)
@EnableScheduling
...
public class RootContextConfiguration implements AsyncConfigurer, SchedulingConfigurer
{
....
@Bean
public ThreadPoolTaskScheduler taskScheduler(){
...
}
@Override
public Executor getAsyncExecutor()
{
...
}
@Override
public void configureTasks(ScheduledTaskRegistrar registrar)
{
...
}
}
@EnableAsync
注解中的proxyTargetClass特性将告訴Spring使用CGLIB庫而不是使用Java接口代理建立含有異步或計劃方法的代理類。通過這種方式,我們可以在自己的bean上建立接口未指定的異步和計劃方法。如果将該特性設定為假,那麼隻有接口指定的方法可以通過計劃或異步的方法執行。新的
@Bean
方法将把排程器暴露為任何其他bean都可以使用的bean。方法getAsyncExecutor将告訴Spring為異步方法執行使用相同的排程器,configureTasks方法将告訴Spring為計劃方法執行使用相同的排程器。
Spring将代理所有對
@Bean
方法的調用,是以它們永遠不會被調用多次。第一次調用
@Bean
方法的結果将被緩存,并在所有将來的調用中使用。這将允許配置中的多個方法使用其他的
@Bean
方法。
Spring Framework通過以代理的方法封裝受影響的bean對
@Async
方法提供支援。當Spring在其他依賴它的bean中注入使用了
@Async
方法的bean時,它實際上注入的是代理,而不是bean自身。然後這些bean将調用代理上的方法。對于普通方法來說,代理隻是将調用委托給了底層的方法。對于标注了
@Async
或
@javax.ejb.Asynchronous
的方法,代理将訓示執行其執行該方法,然後立即傳回。這種工作方式回導緻一個結果:如果bean調用它自己的一個
@Async
方法,該方法不會異步執行,因為this不可以被代理。是以,如果希望以異步的方式調用一個方法,那麼它必須是另一個對象的方法。
建立
@Scheduled
方法與建立
@Async
方法并沒有太大的差別。所有需要做的就是編寫一個完成任務的方法并注解它。關于
@Scheduled
方法需要注意的重要一點是:它們沒有參數。
Bean驗證通過為字段、方法等添加注解的方式,訓示如何在被标注的目标上應用特定的限制。所有保留政策是運作時并且被标注了@javax.validation.Constraint的注解都代表了一個限制注解。該API中包含了幾個預定義的限制注解,我們也可以建立自己的注解并提供對應的javax.validation.ContraintValidator實作,用于處理自定義的注解。ContraintValidator負責評估特定的限制類型。
限制注解可以被添加到字段、方法和方法參數上。添加到字段上時,它表示無論何時在該類的執行個體調用驗證方法,驗證器都應該檢查字段是否滿足限制相容性。添加在JavaBean通路方法上時,它隻是标注底層字段的另一種可選方式而已。在接口方法上添加注解,表示限制應該被應用到方法執行之後的傳回值上。在接口的一個或多個方法參數上添加注解,意味着限制應該在方法執行之前作用于方法參數之上。
Spring Framework将自動為使用Java Bean驗證的、由Spring管理的bean建立代理。它将攔截對添加了注解的方法的調用并進行适當的驗證,檢查使用者是否提供了有效的參數或該實作的傳回值是否有效。
Spring Framework在Bean Validation正式出現很早之前,就已經提供了對對象自動驗證的支援org.springframework.valization.Validator接口根據注解限制制定了驗證對象的工具。
在配置Spring Framework的驗證支援時,需要定義一個同時實作Valization 和Spring Validator的特殊類型bean。
在大多數情況下,我們需要使用LocalValizationFactoryBean,因為它支援擷取底層的Validator,并且支援使用應用程式的其他部分代碼中應用國際化的相同MessageSource和資源封包件。
LocalValidatorFactoryBean将自動檢測到類路徑上的Bean Validation實作,無論是Hibernate Validator還是一些其他的實作,并使用它預設的javax.validation.ValidatorFactory作為支援工廠。不需要建立META-INF/validation.xml檔案。不過,有時類路徑上可能存在多個Bean驗證提供者。在這些情況下,Spring選擇使用哪個提供者是不可預測的,是以如果希望使用指定的提供者的話,應該手動設定提供者類。
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean()
{
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProviderClass(HibernateValidator.class);
return validator;
}
Spring Framework使用了bean後處理器的概念,通過它可以在容器完成啟動過程之前配置、自定義和替換配置中的bean。已配置的org.springframework.beans.factory.config.Bean-PostProcessor實作将在bean被注入到依賴它的其他bean之前執行。
你随時可以建立自己的限制注解,但Bean Validation API中已經提供了幾個内建的注解,它們可以滿足大多數常見的驗證需求。所有這些限制都在javax.validation.constraints包中。
将限制注解用于方法驗證時,必須總是标注在接口上,而不是實作上。
如果希望指定應該在方法執行時應用的驗證組,可以在類上使用@javax.validation.GroupSequence和@ValidateOnExecution。另一方面,通過@Validated可以直接在其中指定驗證組,而無須使用額外的注解,另外它可以為同一個控制器類中的不同MVC控制器方法參數指定不同的組。
如果隻希望驗證特定分組中的限制,那麼可以在@Validated注解中指定這些分組。
@Validated({Default.class, Group.class})
public interface EmployeeService
{
...
}
在Bean驗證中,限制可以繼承另一個限制。當然,這與類的繼承不同,因為注解是不能繼承的。不過,根據慣例,限制注解通常包含一個目标ElementType.ANNOTATION_TYPE。在定位到限制注解時,Validator将決定注解定義上是否标注了任何其他限制。如果是這樣,它将把所有的額外限制和原始限制中定義的邏輯合并成一個複合限制。在這種情況下,限制繼承了它被标注的所有限制。如果出于某些原因需要建立一個不能被繼承的限制,那麼隻需要在定義中忽略ElementType.ANNOTATION_TYPE即可。