rose手冊

源地址:

https://github.com/XiaoMi/rose/blob/master/ebook/rose-handbook.md

 

rose手冊計劃

  • rose項目源代碼地址:http://code.google.com/p/paoding-rose/

  • 目標:光大rose在國內java行業的使用,降低java入門。

  • 人人網、糯米網釋出的、開源的高效Java web開發框架。在小米米聊服務端再次被驗證和使用。一個從零開始的創業公司,在大家技術背景不一的情況下,rose很簡單快速地傳達到了大家中間。本手冊致力於讓php開發人員也能快速使用上java開發高性能服務。

  • 如果在閱讀過程中有任何疑問,歡迎來信諮詢:[email protected] (或者在 http://www.54chen.com 留言),將會第一時間得到答覆。

  • 本文檔同時提供了pdf和mobi文檔下載: https://github.com/XiaoMi/rose/tree/master/ebook

  • 小米科技 bmw 團隊榮譽出品。(BasicMiliaoWare)

章節計劃

  • rose手冊第一章:入門指引 (chapter_1 or http://www.54chen.com/life/rose-manual-1.html)

  • rose手冊第二章:配置與使用 (chapter_2 or http://www.54chen.com/java-ee/rose-manual-2.html)

  • rose手冊第三章:框架功能參考

    • 3.1 controller層:url對照規則與返回結果規則 (chapter_3_1)
    • 3.2 controller層:攔截器支持 (chapter_3_2)
    • 3.3 controller層:ErrHandler支持 (chapter_3_3)
    • 3.4 controller層:自定義http參數支持 (chapter_3_4)
    • 3.5 controller層:統一的參數驗證辦法 (chapter_3_5)
    • 3.6 controller層:一閃而過的信息,flash支持 (chapter_3_6)
    • 3.7 controller層:門戶必備portal支持 (chapter_3_7)
    • 3.8 controller層:門戶必備pipe支持 (chapter_3_8)
    • 3.9 controller層:上傳文件 (chapter_3_9)
    • 3.A DAO層:DAO的基本配置與使用 (chapter_3_A)
    • 3.B DAO層:DAO進階:SQLParm支持和表達式SQL (chapter_3_B)
    • 3.C DAO層:分表設置 (chapter_3_C)
  • rose手冊第四章:安全

  • rose手冊第五章:FAQ 常見問題

  • 5.1 如何打一個可被rose識別的jar包

  • 5.2 會被認成batch執行的sql返回

  • 5.3 一個良好的大型WEB項目架構實踐

  • 5.4 爲何DAO不被初始化

  • rose手冊第六章:附錄

rose手冊第一章:入門指引

1.1 簡介:

人人網、糯米網釋出的、開源的高效Java web開發框架。在小米米聊服務端再次被驗證和使用。一個從零開始的創業公司,在大家技術背景不一的情況下,rose很簡單快速地傳達到了大家中間。本手冊致力於讓php開發人員也能快速使用上java開發高性能服務。 ###1)rose是什麼?###

  • 基於IoC容器 (使用Spring 2.5.6).
  • 收集最佳實踐,形成規範和慣例,引導按規範慣例,簡便開發.
  • 收集通用功能,形成一些可使用的組件,提高生產效率.
  • 特性的插拔,使用基於組合而非繼承的設計.
  • 提供可擴展的點,保持框架的可擴展性.
  • 注重使用簡易性的同時,注重內部代碼設計和實現.

如果你是一個創業公司在選擇php還是java,同時如果你的團隊有一個人寫過一年java其他人都沒寫過。如果你想選擇一個更加大型的系統框架,請使用rose,它收集了來自人人網、糯米網、小米科技的衆多工程師的經驗,你可以免費擁有這些。

###2)rose能做什麼?###

  • 初級rose用戶:
  • rose可以用來完成一個網站。
  • 中級rose用戶:
  • rose可以用來完成一個大型網站,它提供的jade功能使得你的項目可以快速開發,自然切入連接池;它提供的portal功能,可以將一個網頁分多個線程發起向DB的請求,節省用戶的時間;它提供的pipe功能類似facebook的bigpipe,讓前端加速,與此同時還有portal多線程的優勢。
  • 高級rose用戶:
  • rose可以自由加入spring任何特性,比如定時執行(去TM的crontab);比如攔截器做統一權限控制。可以自由配置主庫從庫,分表規則。配置thrift、zookeeper可以得到牛B的高可用性高性能服務集羣。

1.2 簡明教程:

*下面開始來進入rose框架下的開發。只需要有一個感性的認識即可。下一章裏會專門詳細的手把手教你搭建hello world項目。 ###1)需要些什麼?###

  • 提前學習什麼是maven:簡單地說,maven是個build工具,用一個pom.xml來定義項目的依賴。通過一個命令mvn,可以自由地build compile(知道make吧,類似,或者類似ant)。

  • 也許還需要一個nexus,用來搭建自己的maven倉庫(這些都是門檻啊,知道爲什麼java用的人在全球多,而在中國php的人似乎更多,因爲我們的基礎設施太落後了)。nexus的作用是配合maven工作。(54chen正在向sonatype申請將rose項目push到sonatype的官方庫中,成功後這將省略掉這一步)

  • 然後需要在你的項目的pom文件中添加:

<dependency>  
    <groupId>com.54chen</groupId>  
    <artifactId>paoding-rose</artifactId>  
    <version>1.0</version>  
</dependency>  
<dependency>  
    <groupId>com.54chen</groupId>  
    <artifactId>paoding-rose-jade</artifactId>  
    <version>1.1</version>  
</dependency>  
<dependency>  
    <groupId>com.54chen</groupId>  
    <artifactId>paoding-rose-scanning</artifactId>  
    <version>1.0</version>  
</dependency>  
  • 這是三個最基礎的框架包。

###2)一個controller長什麼樣?###

@Path("/")  
public class TestController {  
    @Get("hello")  
    public String test(){  
        return "@a";  
    }  
}  

下一節預告:rose手冊第二章:配置與使用

rose手冊第二章:配置與使用

2.1 基礎環境

  • 普通的pc機,del 380
  • ubuntu 10.04基本不升級
  • java version "1.6.0_29"
  • eclipse
  • m2clipse
  • 茶一杯

2.2 maven簡介

  • maven是基於項目對象模型(POM),可以通過一小段描述信息來管理項目的構建,報告和文檔的軟件項目管理工具。如果你已經有十次輸入同樣的Ant targets來編譯你的代碼、jar或者war、生成javadocs,你一定會自問,是否有一個重複性更少卻能同樣完成該工作的方法。Maven便提供了這樣一種選擇,將你的注意力從作業層轉移到項目管理層。Maven項目已經能夠知道如何構建和捆綁代碼,運行測試,生成文檔並宿主項目網頁。
  • maven對一個項目進入了固定的默認目錄定義:
  • src/main/java 寫主要的java實現
  • src/main/resources 寫主要的配置文件
  • src/test/java 寫test case
  • src/test/resources 寫test case所需要的配置文件
  • src/main/webapp [war項目特有]web項目的對外目錄
  • src/main/webapp/WEB-INF [war項目特有]web項目配置web.xml目錄

2.3 項目建立

  • 打開eclipse(需要提前安裝好m2clipse插件)
  • new -> other -> maven -> maven project
  • create a simple project
  • next
  • group id:com.54chen
  • artifact id:rose-example
  • packaging: war
  • finished

2.4 基礎配置三步走

###1)點火:基礎的pom文件### 打開2.3建立好的項目,打開pom.xml,添加下面的段落到project中:

	<dependencies>
		<dependency>
			<groupId>com.54chen</groupId>
			<artifactId>paoding-rose-scanning</artifactId>
			<version>1.0</version>
		</dependency>

		<dependency>
			<groupId>com.54chen</groupId>
			<artifactId>paoding-rose</artifactId>
			<version>1.0</version>
		</dependency>

		<dependency>
			<groupId>com.54chen</groupId>
			<artifactId>paoding-rose-portal</artifactId>
			<version>1.0</version>
		</dependency>

		<dependency>
			<groupId>com.54chen</groupId>
			<artifactId>paoding-rose-jade</artifactId>
			<version>1.1</version>
		</dependency>
	</dependencies>

上述是rose環境最基礎的依賴包。再添加一點常見的編譯設置:

	<build>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
				<includes>
					<include>**/*.*</include>
				</includes>
			</resource>
		</resources>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.0.2</version>
				<configuration>
					<webResources>
						<resource>
							<targetPath>WEB-INF</targetPath>
							<filtering>true</filtering>
							<directory>src/main/resources</directory>
							<includes>
								<include>**/*.xml</include>
								<include>**/*.properties</include>
							</includes>
							<targetPath>WEB-INF</targetPath>
						</resource>
					</webResources>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
					<fork>true</fork>
					<verbose>true</verbose>
					<encoding>UTF-8</encoding>
					<compilerArguments>
						<sourcepath>
							${project.basedir}/src/main/java
                        </sourcepath>
					</compilerArguments>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<configuration>
					<!-- 忽略測試 -->
					<skip>true</skip>
				</configuration>
			</plugin>
		</plugins>
	</build>

上述是編譯設置,也是放在project段落裏。

###2)鬆離合:必不可少的web.xml### 在src/main/webapp/WEB-INF文件夾下建立web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<display-name>rose-example</display-name>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
		<welcome-file>index.htm</welcome-file>
		<welcome-file>index.jsp</welcome-file>
		<welcome-file>default.html</welcome-file>
		<welcome-file>default.htm</welcome-file>
		<welcome-file>default.jsp</welcome-file>
	</welcome-file-list>
	
	 
	<context-param>
		<param-name>log4jConfigLocation</param-name>
		<param-value>/WEB-INF/log4j.xml</param-value>
	</context-param>

	<listener>
		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
	</listener>
	<filter>
		<filter-name>roseFilter</filter-name>
		<filter-class>net.paoding.rose.RoseFilter</filter-class>
	</filter> 
	<filter-mapping>
		<filter-name>roseFilter</filter-name>
		<url-pattern>/*</url-pattern>
		<dispatcher>REQUEST</dispatcher>
		<dispatcher>FORWARD</dispatcher>
		<dispatcher>INCLUDE</dispatcher>
	</filter-mapping> 
</web-app>

###3)踩油門:applicationContext.xml### src/main/resources/applicationContext.xml是spring環境的油門,所有包的掃描和啓動都在這裏定義:

<?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:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans  
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
    http://www.springframework.org/schema/context  
    http://www.springframework.org/schema/context/spring-context-2.5.xsd"
	default-lazy-init="true">

	<!-- 自動掃描 -->
	<context:annotation-config />
	<context:component-scan base-package="com.chen">
	</context:component-scan>
	 
</beans>

2.5 hello world

  • 在src/main/java上右鍵 -> new -> package -> name: com.chen
  • 在com.chen上右鍵 -> new -> package -> com.chen.controllers [controllers是rose框架默認的加載controller的package name]
  • 在com.chen.controllers上右鍵 -> new -> class -> HelloController [*Controller是rose框架默認的controller層的class後綴]
  • 打開HelloController這個類
  • 在public class HelloController添加註解@Path("") [Path註解是rose框架提供的標識每個controller的對外訪問時的基礎路徑]
  • 在HelloController中添加方法
/**
 * @author 54chen(陳臻) [[email protected] [email protected]]
 * @since 2012-4-10 上午11:14:46
 */
package com.chen.controllers;

import net.paoding.rose.web.annotation.Path;
import net.paoding.rose.web.annotation.rest.Get;

@Path("")
public class HelloController {

    @Get("")
    public String index() {
        return "@hello world";
    }
}
  • [Get註解是rose框架提供的標識一個http訪問是get還是post或者是其他,並且會將path與get中的字符串連接成一個url]
  • 上述代碼可以從瀏覽器訪問:http://localhost/。
  • 下述代碼可以從瀏覽器訪問:http://localhost/hello/world [注意path與get中的參數]。
/**
 * @author 54chen(陳臻) [[email protected] [email protected]]
 * @since 2012-4-10 上午11:14:46
 */
package com.chen.controllers;

import net.paoding.rose.web.annotation.Path;
import net.paoding.rose.web.annotation.rest.Get;

@Path("/hello/")
public class HelloController {

    @Get("world")
    public String index() {
        return "@hello world";
    }
}

EOF

rose手冊第三章:框架功能參考

3.1 controller層:url對照規則與返回結果規則

###3.1.1) url對照規則——最簡單的例子

先看看怎樣把url和某個方法對應起來。爲了方便說明,現在我們來一起完成一個極簡版的貼吧。

####1)貼吧需要什麼功能?

貼吧中當然會有很多“主帖”(topic),“主帖”下會有很多“跟帖”(comment)。
一般,貼吧中最基本的,會有下面這幾個功能需要我們完成:

  • 顯示主帖列表
  • 顯示單個主帖和它的跟貼
  • 顯示單個跟貼
  • 創建一個主帖
  • 創建一個跟貼

####2)設計 web API

然後讓我們來規劃一個REST風格的 web API :(“GET”和“POST”是指HTTP1.1中的請求方法)

可以發現一個共同點,所有API中,URI部分的第一級都是“/myforum”(但這並不是規定,僅僅爲了演示)。

####3)實現 web API

首先新建一個類,這個類的類名必須以“Controller”結尾:

@Path("myforum")
public class ForumController {
}

注意標註在類(class)上的註解“@Path("myforum")”,這意味着,這個類中定義的所有API的URI,都必須以“myforum”開頭,比如“/myforum/xxx”和“/myforum/yyy”等(但“myforum”不一定是整個URI的第一級,比如“/aaa/myforum/bbb”)。

接着,實現第一個API——“GET http://github.com/myforum/topic”:

@Path("myforum")
public class ForumController {
    @Get("topic")
    public String getTopics() {
        //顯示主帖列表
        return "topiclist";
    }
}

因爲是“GET”方法,所以在該方法上標註“@Get("")”,URI“/myforum/topic”中的“myforum”已經在“@Path("myforum")”中定義過了,所以只剩下“topic”,於是寫“@Get("topic")”。

再看第二個API——“GET http://github.com/myforum/topic/123”。
跟前一個的唯一區別是,後面多了個“/123”,表示主帖id,而這個id當然不是固定的,只有用戶點擊鏈接發來請求時才能知道,腫麼辦?
沒關係,rose支持正則表達式!可以這麼寫:

@Get("topic/{topicId:[0-9]+}")
public String showTopic(@Param("topicId") int topicId) {
    //顯示單個主帖和它的跟貼
    return "topic";
}

與前一個API相比,多了段“/{topicId:[0-9]+}”。正則表達式被大括號"{}"包圍,格式爲“{ paramName : regularExpression }”,只有請求的URI能被正則表達式匹配時,纔會執行這個方法,而被匹配的值將被保存在名爲“topicId”的參數中。

同理,實現第三個API,稍微複雜一點:

@Get("topic/{topicId:[0-9]+}/comment/{commentId:[0-9]+}")
public String showComment(@Param("topicId") int topicId, @Param("commentId") int commentId) {
    //顯示單個跟貼
    return "comment";
}

最後兩個API使用POST方法,其他與前面相同:

@Post("topic")
public String createTopic(){
    //創建一個主帖
    return "topic";
}
@Post("topic/{topicId:[0-9]+}/comment")
public String createComment(@Param("topicId") int topicId){
    //創建一個跟貼
    return "comment";
}

完整的代碼如下(省略了import語句):

@Path("myforum")
public class ForumController {

    @Get("topic")
    public String getTopics() {
        //顯示主帖列表
        return "topiclist";
    }

    @Get("topic/{topicId:[0-9]+}")
    public String showTopic(@Param("topicId") int topicId) {
        //顯示單個主帖和它的跟貼
        return "topic";
    }

    @Get("topic/{topicId:[0-9]+}/comment/{commentId:[0-9]+}")
    public String showComment(@Param("topicId") int topicId, @Param("commentId") int commentId) {
        //顯示單個跟貼
        return "comment";
    }

    @Post("topic")
    public String createTopic(){
        //創建一個主帖
        return "topic";
    }

    @Post("topic/{topicId:[0-9]+}/comment")
    public String createComment(@Param("topicId") int topicId){
        //創建一個跟貼
        return "comment";
    }
}

至此,一個貼吧功能的Controller就編寫完成了。

####4) 更多細節

除了上面例子中的做法(@Path(""),@Get("")和@Post("")),還可以通過包路徑來規劃URI。

比如前面例子中的Controller,在API不變的前提下,還可以這麼做:

1.在controllers路徑下新建一個叫做“myforum”的文件夾。
2.將ForumController從“xxx.controllers”移動到“xxx.controllers.myforum”
並改成下面這樣:

@Path("")
public class ForumController {
    @Get("topic")
    public String getTopics() {
        //顯示主帖列表
        return "topiclist";
    }
    ... ...
}

只是將“@Path("myforum")”改成了“@Path("")”。這樣做的好處是可以讓項目中的代碼組織清晰。

###3.1.2) 返回結果規則

####1) 渲染頁面並返回

web開發中最常規的做法是,運行Servlet中的方法,最後將渲染好的頁面內容返回。下面說說rose是怎麼做的。
上面的貼吧例子中,每個方法的返回值都是一個普通字符串,比如“comment”,意思是,找到web項目中“webapp/views”路徑下名叫“comment”的視圖文件,比如“comment.jsp”,用這個視圖文件來渲染網頁結果並返回。
comment.jsp的代碼如下:

...
<body>
    暱稱:${name}<br>
    回覆內容:${commentContent}
</body>
...

頁面中有兩個變量——name和commentContent,變量的值是在java代碼中設置的,如下:

@Get("topic/{topicId:[0-9]+}/comment/{commentId:[0-9]+}")
public String showComment(Model model, @Param("topicId") int topicId, @Param("commentId") int commentId) {
    //顯示單個跟貼
    model.add("name", "郭德綱");
    model.add("commentContent", "今天來人不少,我很欣慰啊!");
    return "comment";
}

總結一句話,通過rose提供類net.paoding.rose.web.var.Model來設置變量名和變量值,然後在視圖文件中用“${paramName}”的方式得到變量值。
變量的值可以是String,boolean,數字,數組,對象(JavaBean)。

如果是對象,使用方法如下:

javaBean:
public class Bean{
    private String beanValue;
    public String getBeanValue(){...}
    public String setBeanValue(String beanValue){...}
}
==================
controller中的方法:
@Get("test")
public String test(Model model) {
    Bean bean = new Bean();
    bean.setBeanValue("this_is_a_bean");
    model.add("mybean", bean);
    return "test";
}
==================
test.jsp文件:
...
<body>
    bean裏的值:${mybean.beanValue}
</body>
...
==================
輸出爲:
bean裏的值:this_is_a_bean

如果是個數組,可以結合JSTL對數組循環訪問:

controller中的方法:
@Get("test")
public String test(Model model) {
    String[] array = {"111","222","333"};
    model.add("array", array);
    return "test";
}
==================
test.jsp文件:
...
<body>
<c:forEach var="item" items="${array}" varStatus="status"> 
    打印:${item}<br>
</c:forEach>
</body>
...
==================
輸出爲:
111
222
333

####2) 還有幾種規則? rose中,controller方法的返回值有下面幾種規則:

1.返回普通字符串,如上所述,最常用的做法,渲染視圖文件並返回。
2.以“@”開頭的字符串,比如“return "@HelloWorld";”,會將“@”後面的字符串“HelloWorld”作爲結果返回;
3.以“@json:”開頭的字符串,比如:

@Get("json")
public String returnJson(){
    JSONObject jo = new JSONObject();
    return "@json:"+jo.toString();
}

將會返回一個字符串(jo.toString()),並自動將“HttpServletResponse”中的“contentType”設置爲“application/json”。

4.【不推薦使用】以“r:”開頭的字符串,比如“return "r:/aaa";”,等效於調用“javax.servlet.http.HttpServletResponse.sendRedirect("/aaa")”,將執行301跳轉。
5.【不推薦使用】以“a:”開頭的字符串,比如“return "a:/bbb";”,將會攜帶參數再次匹配roseTree,找到controller中某個方法並執行,相當於“javax.servlet.RequestDispatcher.forward(request, response)”。

###3.1.3) 原理

Rose 是一個基於Servlet規範、Spring“規範”的WEB開發框架。

Rose 框架通過在web.xml配置過濾器攔截並處理匹配的web請求,如果一個請求應該由在Rose框架下的類來處理, 該請求將在Rose調用中完成對客戶端響應. 如果一個請求在Rose中沒有找到合適的類來爲他服務,Rose將把該請求移交給web容器的其他組件來處理。

Rose使用過濾器而非Servlet來接收web請求,這有它的合理性以及好處。

Servlet規範以“邊走邊看”的方式來處理請求, 當服務器接收到一個web請求時,並沒有要求在web.xml必須有相應的Servlet組件時才能處理,web請求被一系列Filter過濾時, Filter可以拿到相應的Request和Response對象 ,當Filter認爲自己已經能夠完成整個處理,它將不再調用chain.doNext()來使鏈中下個組件(Filter、Servlet、JSP)進行處理。

使用過濾器的好處是,Rose可以很好地和其他web框架兼容。這在改造遺留系統、對各種uri的支持具有天然優越性。正是使用過濾器,Rose不再要求請求地址具有特殊的後綴。

爲了更好地理解,可以把Rose看成這樣一種特殊的Servlet:它能夠優先處理認定的事情,如無法處理再交給其它Filter、Servlet或JSP來處理。這個剛好是普通Servlet無法做到的 : 如果一個請求以後綴名配置給他處理時候 ,一旦該Servlet處理不了,Servlet規範沒有提供機制使得可以由配置在web.xml的其他正常組件處理 (除404,500等錯誤處理組件之外)。

一個web.xml中可能具有不只一個的Filter,Filter的先後順序對系統具有重要影響,特別的,Rose自己的過濾器的配置順序更是需要講究 。如果一個請求在被Rose處理前,還應該被其它一些過濾器過濾,請把這些過濾器的mapping配置在Rose過濾器之前。

像前面提到過的,RoseFilter的配置,建議按以下配置即可:

        <filter>
                <filter-name>roseFilter</filter-name>
                <filter-class>net.paoding.rose.RoseFilter</filter-class>
        </filter>
        <filter-mapping>
                <filter-name>roseFilter</filter-name>
                <url-pattern>/*</url-pattern>
                <dispatcher>REQUEST</dispatcher>
                <dispatcher>FORWARD</dispatcher>
                <dispatcher>INCLUDE</dispatcher>
        </filter-mapping>

大多數請況下,filter-mapping 應配置在所有Filter Mapping的最後。 不能將 FORWARD、INCLUDE 的 dispatcher 去掉,否則forward、 include的請求Rose框架將攔截不到。

Rose框架內部採用"匹配->執行"兩階段邏輯。Rose內部結構具有一個匹配樹, 這個數據結構可以快速判斷一個請求是否應該由Rose處理並進行, 沒有找到匹配的請求交給過濾器的下一個組件處理。匹配成功的請求將進入”執行“階段。 執行階段需要經過6個步驟處理:“參數解析 -〉 驗證器 -〉 攔截器 -〉 控制器 -〉 視圖渲染 -〉渲染後"的處理鏈。

匹配樹: 匹配樹是一個多叉樹,下面是一個例子:

ROOT

    GET="HomeController#index" package="com.xiaonei.xxx.controllers" 

/about

    GET="HomeController#about" package="com.xiaonei.xxx.controllers" 

/book

    GET="BookController#list" package="com.xiaonei.xxx.controllers.sub" 

    POST="BookController#add" package="com.xiaonei.xxx.controllers.sub" 

/book/

    /book/{id}

        GET="BookController#show" package="com.xiaonei.xxx.controllers.sub" 

/help

    GET="HomeController#help" package="com.xiaonei.xxx.controllers"

ROOT代表這是一個根地址,也就是 http://localhost/ 代表的地址;

ROOT的下級有個GET結點,代表對該地址支持GET訪問,不支持POST等其它訪問,如果進行POST訪問將以405錯誤迴應。

/book代表這是一個/book地址,也就是 http://localhost/book 代表的地址;

/book下級有GET、POST兩個結點,說明它支持GET和POST方法,根據HTTP語義,GET代表瀏覽,POST代表追加(向一個集合中追加一個條目)。

/book下還有/book/地址,這個地址有點特別,它以'/'結尾,但實際它不會被任何地址訪問到,rose對http://localhost/book/的處理會將它等價於 http://localhost/book 。

這個特別的地址的存在完全是匹配樹結構所需導致的,但不對實際匹配有任何壞的影響,所以也沒有任何GET、POST等子結點。

/book/{id}代表是一個/book/123456、/book/654321這樣的地址,當然這可以支持正則表達式的。

大部分情況下,匹配樹的結構和實際的URI結構會一致,也因此匹配樹的深度並不固定,每一箇中間結點或葉子節點都有可能代表一個最終的URI地址,可以處理GET、POST等請求。對於那些匹配樹存在的地址,但沒有GET、POST、DELETE等子結點的,一旦用戶請求了該地址,rose將直接把該請求轉交給web容器處理,如果容器也不能處理它,最終用戶將得到404響應。

匹配過程: Rose以請求的地址作爲處理輸入(不包含Query串,即問號後的字符串)。如果匹配樹中存在對應的地址,且含有對應請求方法(GET、POST、PUT、DELETE)的,則表示匹配成功;如果含有其他方法的,但沒有當前方法的(比如只支持GET,但當前是POST的),則也表示匹配成功,但最後會以405響應出去;如果所給的地址沒有任何支持的方法或者沒有找到匹配地址的,則表示匹配失敗。1.0.1不支持回朔算法,1.0.2將支持部分回朔算法(待發布時再做詳細介紹)。

參數解析: 在調用驗證器、攔截器 控制器之前,Rose完成2個解析:解析匹配樹上動態的參數出實際值,解析控制器方法中參數實際的值。參數可能會解析失敗(例如轉化異常等等 ),此時該參數以默認值進行代替,同時Rose解析失敗和異常記錄起來放到專門的類中,繼續下一個過程而不打斷執行。

3.2 controller層:攔截器支持

3.2.1 攔截器作用

  • 面向切面編程(AOP)方法可以讓一個項目更加關注核心邏輯,常見的一些最佳實踐包括
  • 權限
  • 緩存
  • 錯誤處理
  • 延時加載
  • 調試
  • 持久化
  • 資源池
  • 等等。。。
  • 而此處的攔截器目標是在controller層提供各種在controller執行前、執行後的代碼切入,以達到各種可AOP的目標。
  • 簡單地說,攔截器能幹的事情就是當你的項目寫了一半時發現缺少啥全局要做的事情(比如需要驗證權限),不用擔心,搞一個攔截器就是了。

3.2.2 攔截器例子

public class AccessTrackInterceptor extends ControllerInterceptorAdapter {
    public AccessTrackInterceptor() {
	setPriority(29600);
    }
    @Override
    public Class<? extends Annotation> getRequiredAnnotationClass() {
       	return PriCheckRequired.class; // 這是一個註解,只有標過的controller纔會接受這個攔截器的洗禮。
    }
    @Override
    public Object before(Invocation inv) throws Exception {
       	// TODO ....
	return super.before(inv);
    }

    @Override
    public void afterCompletion(final Invocation inv, Throwable ex) throws Exception {
	// TODO ....
    }
}

需要注意幾點:

  • 攔截器要放在controllers下(高級用法:打在rose-jar包裏,參見5.1)
  • 繼承net.paoding.rose.web.ControllerInterceptorAdapter
  • 按照實現的方法名,在controller執行前、中、後執行:
  • before:在controller執行前執行。
  • after:在controller執行中(後)執行,如果一個返回拋出了異常,則不會進來。
  • afterCompletion:在controller執行後執行,不論是否異常,都會進來。
  • isForAction:定義滿足某條件的纔會被攔截。

3.2.3 攔截器可動的位置細節

  • 上面都講得差不多了,實際上還有不少地方可以攔截的:
  • isForDispatcher:根據響應的情況判斷是否攔截,比如說是正常請求、內部forward、還是include (但是沒用過)
  • setPriority:設置一個數字表示攔截優先級,當有多個攔截器時,要精準控制,數字小的內層,大的在外層,在最外層的before方法最先執行,大家都執行完後它的after才最後執行。
  • round:這纔是真正的controller執行中執行,不過用得很少。
  • getRequiredAnnotationClass:返回一個Annotation class name,表示這個攔截器只對此Annotation標過的controller才生效。常用。

3.2.4 實際應用場景

  • 全站是否登錄判斷相關的邏輯,寫在一個攔截器裏,一次完成後,其他地方不再關心這個代碼,在需要登錄才能做的controller上註解一下,表示需要被執行攔截。
  • 日誌收集的邏輯,在一個攔截器裏進行當前的access log記錄。
  • 權限體系的邏輯,寫在一個攔截器裏,在對應的操作上作註解,攔截器中進行細節的判斷,新加的api也只是需要一次註解就得到了權限的判斷。

[文中所提代碼均在 https://github.com/XiaoMi/rose/tree/master/rose-example]

3.3 controller層:ErrorHandler支持

3.3.1 ErrorHandler的作用

  • 一般來說傳統的編程都會到處去try,特別是java裏,try來try去的(如果你用erlang一定就知道,已經知道的可能性,怎麼能叫異常?都try了還是讓它崩了算了。。。)。
  • 如果打開你的項目,每個java文件中的代碼都有一堆的try,那這時候就是ErrorHandle上陣的時候了。
  • ErrorHanle致力於:統一捕捉和處理各種異常,可區分對待和返回;統一的出錯體驗。
  • 非常類似做web開發時的500統一出錯頁面這樣的東東。

3.3.2 示例

/**
 * @author [email protected]
 * 2010-12-1 
 */

package com.chen.controllers;

import net.paoding.rose.web.ControllerErrorHandler;
import net.paoding.rose.web.Invocation;

public class ErrorHandler implements ControllerErrorHandler {

    public Object onError(Invocation inv, Throwable ex) throws Throwable {

        // TODO logger.error("handle err:", ex);

        return "@error";
    }
}

這是這麼簡單,不用懷疑!

3.3.3 放在哪裏才能生效?

  • 放在controllers目錄下,和controller們在一起(幸福快樂地生活)。
  • 一般來講,ErrorHandler都是用在web項目裏,在最快層起作用。
  • 所有的方法都可以盡情地向處throws Exception了。
  • 不需要再try了。
@Path("")
public class HelloController {
    @Get("")
    public String index2() throws Exception {
        return "@hello world";
    }
}

3.3.3 有用的例子: 不同的異常類型做不用的事情

/**
 * @author [email protected]
 * 2010-12-1 
 */

package com.chen.controllers;

import net.paoding.rose.web.ControllerErrorHandler;
import net.paoding.rose.web.Invocation;

public class ErrorHandler implements ControllerErrorHandler {

    public Object onError(Invocation inv, Throwable ex) throws Throwable {

        // TODO logger.error("handle err:", ex);
        if (ex instanceof RuntimeException) {
            return "@runtime";
        }
        return "@error";
    }
}

文中所提及代碼均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。

3.4 controller層:自定義http參數支持

3.4.1 http參數支持的一些前言

  • 我們把一個controller的類裏的一個方法叫做action,它實際對應用戶看到的一個url。
  • 在action裏可以接收各種各樣的參數,也可以自己定義需要的參數。
  • rose自己定義了一些常見的類型,基本上很有機會會用到自己定義,但是在某些情況下,也是個不錯的選擇:
  • 用來對指定的參數類型的值進行固定的修改和賦值。

3.4.2 看一個例子

ChenBeanResolver.java放在controllers目錄下:

public class ChenBeanResolver implements ParamResolver {

    @Override
    public Object resolve(Invocation inv, ParamMetaData metaData) throws Exception {
        for (String paramName : metaData.getParamNames()) {
            if (paramName != null) {
                Chen chen = new Chen();
                String value1 = inv.getParameter("chen1");
                String value2 = inv.getParameter("chen2");
                chen.setChen1(value1);
                chen.setChen2(value2);
                return chen;
            }
        }
        return null;

    }

    @Override
    public boolean supports(ParamMetaData metaData) {
        return Chen.class == metaData.getParamType();
    }

}
  • 上述代碼的意思:

  • 如果在action裏一個參數的類型是Chen(com.chen.model.Chen),就會走這個resolver,這裏對兩個參數進行了組裝。

  • 用戶如果訪問的參數裏傳入了chen1和chen2的值,則會直接組裝出來一個Chen對象。

  • 配合上述resolver的action代碼爲:

 @Get("/param")
    public String param(Chen chen) throws Exception {
        return "@hello world:" + chen.getChen1() + ":" + chen.getChen2();
    }

3.4.2 rose內置的參數支持

除了上述的自定義resolver外,rose還內置了豐富的resolver,都是大家的經驗總結,使用起來會非常方便,它們是:

  • 所有的基礎java類型,都可以直接使用,rose進行自動轉換,比如在action中的類型爲long id,則id可以轉爲數字,不再需要從string轉爲long。
  • array/map/bean同樣可用,它們的接收參數規則爲:
  • ?id=1,2,3,4 或者 ?id=1&id=2&id=3 對應 @Param("id") int[] idArray
  • ?map:1=paoding&map:2=rose 對應 @Param("map") Map<Integer, String> map
  • POST http://127.0.0.1/user?id=1&name=rose&level.id=3 對應接收代碼:
    @Post
    public String post(User user) {
         return "@" + user.getId() + "; level.id=" + user.getLevel().getId();
    }
  • 代碼中User是一個自定義的bean,有屬性id,name,level等。

文中所提及代碼均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。

3.5 controller層:統一的參數驗證辦法

3.5.1 用來做什麼

  • 我們把的參數驗證辦法叫ParamValidator
  • 一般來說,像比如說驗證http傳來的參數是不是爲空呀啥的(發揮你的想象力)。
  • 好處在於不用再重複地寫if else

3.5.2 怎麼用

  • 來看一個例子,驗證用戶的參數不可爲空(灰常灰常的實用):
public class NotBlankParamValidator implements ParamValidator {

    @Override
    public boolean supports(ParamMetaData metaData) {
        return metaData.getAnnotation(NotBlank.class) != null;
    }

    @Override
    public Object validate(ParamMetaData metaData, Invocation inv, Object target, Errors errors) {
        String paramName = metaData.getParamName();
        String value = inv.getParameter(paramName);
        if (StringUtils.isBlank(value)) {
            return "@參數不能爲空";
        }
        return null;
    }
}

解讀:

  • 放到controllers下
  • 實現ParamValidator
  • 實現supports方法,這個方法用來做判斷是否要驗證當前得到的http參數,一般都用個註解來判斷比較文藝
  • 實現validate方法,這裏是主要邏輯
  • metaData裏放的是參數的原型
  • inv是rose的基礎調用
  • target是這個參數的最後解析結果,參看上一節裏提到的東西
  • errors是這個參數解析時出來的錯誤
  • NotBlank是一個自己定義的annotation

3.5.3 使用時action長什麼樣?

  • 下面的代碼是action中使用時長的樣子:
    @Get("/notBlank")
    public String notBlank(@NotBlank @Param("messages") String messages) throws Exception {
        return "@hello world";
    }

解讀:

  • 當遇到NotBlank註解的參數時,會自動執行參數判斷
  • 如果messages爲空,則會得到“參數不能爲空”的返回

文中所提及代碼均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。

3.6 controller層:一閃而過的信息,flash支持

3.6.1 需求描述

  • 歷史上,做web的需求時,經常遇到一個情況:在A頁面修改/添加/刪除了信息,提交,提示“修改/添加/刪除成功!”。
  • rose的flash(並非你所想象的adobe的flash)建設性地使這一需求在開發過程中簡單快捷化。

3.6.2 使用過程

  • 使用過程會很愉快,在兩個action之間,通過return "r:/xxx"來跳轉(實際是301),只需要在第一個action裏使用flash.put,在第二個action裏使用flash.get即可。
    @Get("/flash1")
    public String flashStep1(Flash flash) {
        flash.add("msg", "修改成功!");
        return "r:/flash2";
    }

    @Get("/flash2")
    public String flashStep2(Invocation inv, Flash flash) {
        inv.addModel("info", flash.get("msg"));
        return "flash";
    }
  • 上述兩個action中,當訪問flash1時,一句flash信息被寫入,快速跳轉到flash2的地址。
  • flash2地址中接收到這個flash信息後寫到model中。
  • 還需要在flash2的模板裏去顯示這個變量。
<%@ page contentType="text/html;charset=UTF-8"%>
提示信息:${info}

3.6.3 注意事項

  • flash功能利用了瀏覽器的cookies功能,如果用戶的環境不能使用cookies將不會有任何效果。

文中所提及代碼均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。

3.7 controller層:門戶必備portal支持

3.7.1 什麼是portal?

*字面意思,做門戶用的。 *簡單來說,把一個網頁分成了N個區域,每個區域由不同的action去執行,多線程並行提高cpu使用率。

3.7.2 使用例子

*要使用portal,必須先在web.xml裏聲明所使用的線程池大小:

	<context-param>
		<param-name>portalExecutorCorePoolSize</param-name>
		<param-value>1024</param-value>
	</context-param>

*然後看示例代碼:

    @Get("/3.7")
    public String portal(Portal portal) {
        portal.addWindow("p1", "/wp1");
        portal.addWindow("p2", "/wp2");
        return "portal";
    }

    @Get("/wp1")
    public String portal1() {
        return "@this is p1";
    }

    @Get("/wp2")
    public String portal2() {
        return "@this is p2";
    }

*然後在第一個action中的portal.jsp中寫到:

<%@ page contentType="text/html;charset=UTF-8"%>
portal演示信息:
<br>
${p1}
<br>
${p2}

*當我們部屬好了之後,訪問http://127.0.0.1/3.7 *將從瀏覽器中得到: *portal演示信息: *this is p1 *this is p2

3.7.3 這樣子做的好處

*更加充分地使用多核cpu。 *更加方便多人協作時對項目進行模塊劃分,搞的時候,按照url一分,一個url一個模塊,所有的頁面都可以切成小的豆腐塊,所以,你懂的。

3.7.4 過去的經典事蹟

文中所提及代碼均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供 。

3.8 controller層:門戶必備pipe支持

3.8.1 什麼是pipe?

  • pipe起源於facebook的工程師對他們網頁提速的方案:將網頁分解爲Pagelets的小塊(在rose叫做window的小塊),然後通過後端多重管道運行,以達到性能的最佳。
  • pipe巧妙使用了http 1.1連接有timeout的機制,充分使用一次http連接來傳遞數據。
  • pipe可使用戶在大多數瀏覽器中感受到延遲減少了一半。

3.8.2 與facebook的bigpipe相比rose pipe如何?

  • fb並未在開源項目中公佈過使用方法
  • bigpipe神似是php+js搞定的
  • rose pipe可以自由選擇線程池大小,完全出自上一節的portal的基礎
  • 完全實現bigpipe功能,天然的對業務開發者透明

3.8.3 看實例

HelloController.java

    @Get("/3.8")
    public String pipe(Pipe pipe) {
        pipe.addWindow("p1", "/wp1");
        pipe.addWindow("p2", "/wp2");
        return "pipe";
    }
  • 長得是不是很像上一節裏提供的action?
  • 不同在於jsp文件中:
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ taglib uri="http://paoding.net/rose/pipe" prefix="rosepipe"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>portal/pipe演示信息</title> 
<script type='text/javascript' src='/js/rosepipe.js'></script>
</head>
<body>

portal/pipe演示信息:
<br>
<div id="p1"></div>
<br>
<div id="p2"></div>

</body>
</html>
<rosepipe:write>${p1}</rosepipe:write>
<rosepipe:write>${p2}</rosepipe:write>
  • 當使用jsp文件時,需要在尾部使用rosepipe:write標籤
  • 如果是使用vm文件,可以不寫這個標籤

3.8.4 總結

  • 上述代碼中p1 p2兩個window會同時在多個線程中執行,如果是portal,那會多個線程執行完成一起返回,而pipe則會用js反寫的方式,一個線程一個線程地返回給用戶。
  • pipe是個好物件
  • 使用時jsp一定不要忘記尾部的標籤
  • 使用時web.xml一定不要忘記聲明使用的線程池大小
  • 久經考驗

文中所提及代碼均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。

3.9 controller層:上傳文件

3.9.1 其實很簡單

  • 添加依賴包:commons-io.jar
  • html中使用 enctype="multipart/form-data",method="POST"
  • 直接看後端代碼吧。
    @Post("/doUpload")
    public String doUpload(@Param("file") MultipartFile file) {
        return "@ upload ok!" + file.getOriginalFilename();
    }

3.9.2 其他

  • 可以同時接收所有的文件
// 不聲明@Param
// files可以是一個數組或者List
public String upload(MultipartFile[] files) {
    return "@ok-" + Arrays.toString(files);
}

3.A DAO層:DAO的基本配置與使用

本章開始進入對DB層的支持,同進也是日常開發用得最多的章節。

3.A.1 什麼是jade?

  • jade大概是java access data layer的意思吧,具體的來由,在章節寫到末尾的時候,我再找qieqie和liaohan大俠們寫一寫編年史。
  • 用jade的好處在於,儘可能減少重複的從db把數據對bean進行裝配的過程,統一入口,隔離業務邏輯,方便review。
  • jade是在spring完成的數據層的良好實踐總結,無縫接入rose中,可以算得上是rose親密無間的好模塊。

3.A.2 引入基礎配置

  • 要開始使用jade,一定要先引用jade的基礎包:

pom.xml

		<dependency>
			<groupId>com.54chen</groupId>
			<artifactId>paoding-rose-jade</artifactId>
			<version>1.1</version>
		</dependency>
  • 除了需要jade的包外,還需要引入數據源連接池的jar,這裏使用了dbcp,當然了mysql-connector也是必不可少的,還是在pom.xml中添加:
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
                        <version>1.2.2</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.10</version>
		</dependency>
  • 在pom中引入了依賴之後,需要定義一個數據源,這裏先不考慮多個數據源的情況。

在war項目的applicationContext.xml中增加數據源定義:

	 <!-- 數據源配置 dbcp -->
	<bean id="jade.dataSource.com.chen.dao" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url"
			value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&amp;characterEncoding=utf-8"></property>
		<property name="username" value="test"></property>
		<property name="password" value="test"></property>
		<!-- 運行判斷連接超時任務的時間間隔,單位爲毫秒,默認爲-1,即不執行任務。 -->
		<property name="timeBetweenEvictionRunsMillis" value="3600000"></property>
		<!-- 連接的超時時間,默認爲半小時。 -->
		<property name="minEvictableIdleTimeMillis" value="3600000"></property>
	</bean> 
  • 這裏假設了mysql已經安裝在本地了,用戶名爲test,密碼爲test。
  • jade約定了bean的id爲jade.dataSource.classPackageName。
  • jade約定了這個bean的有效範圍爲classPackageName所有的DAO。
  • jade約定了除非有專門的定義,所有的子目錄也受bean上的classpackageName所影響。

3.A.3 第一個讀取數據庫的實例

  • 先需要準備一下數據庫:
create table test (int id, varchar(200) msg);
insert into test values(111,'adfafafasdf');
  • 然後準備簡練的DAO聲明:
@DAO
public interface TestDAO {
    @SQL("select id,msg from test limit 1")
    public Test getTest();
}

  • Test是一個class,裏面有標準的getter和setter。

  • 然後從一個類去調用它:

@Service
public class TestService {

    @Autowired
    private TestDAO testDAO;

    public Test getTest() {
        return testDAO.getTest();
    }
}
  • 當然也可以直接用controller就調用它顯示了,當然這不是一個大型項目良好的架構,架構問題在最後的章節裏講述。

3.A.4 查看演示代碼

3.B DAO層:DAO進階:SQLParm支持和表達式SQL

3.B.1 SQLParam介紹:DAO方法傳遞參數

  • SQLParam作爲DAO支持中的參數傳遞使者,可以傳遞一個常見的變量,也可以是一個自定義的對象。
  • 比如:
    @SQL("insert into test (id,msg) values (:t.id,:t.msg)")
    public void insertTest(@SQLParam("t") Test test);
  • 上列中Test對象通過t傳遞到sql執行中去,並且可以分別使用其中的屬性。這感覺是不是很自然?

  • 當然,如果是一個int、long、String等自在不言中。

  • 當是list時,會有自動的batch操作,將sql拆爲多條sql執行。這個小技巧會在後面的章節裏講。平時很少用到。

3.B.2 ReturnGeneratedKeys介紹:返回剛剛插入的ID號

  • 特別是使用mysql開發的廣大勞苦大衆,常常會使用到auto_increament的字段。
  • 當一條insert語句在執行的時候,我們常常會去需要拿它的當前的自增id是多少。
    @ReturnGeneratedKeys
    @SQL("insert into test (id,msg) values (:t.id,:t.msg)")
    public int insertTest(@SQLParam("t") Test test);
  • 如上述代碼所示,只需要加上一個@ReturnGeneratedKeys即可返回當前的id

3.B.2 表達式的支持

  • 多變的業務需求決定了我們的sql是複雜的,需要有條件地執行。

  • 如果每種條件都去寫DAO中的SQL,那DAO的變得很大。

  • 常常會有動態產生sql的需求。

  • jade支持一些常規的表達式。

  • 語法一:常見的變量賦值

  • 冒號(:)表示這是一個變量,比如上面的例子裏的 :t.id,它會被一個值替換。

  • 語法二:字符串連接

  • 連續的井號(##) 表示後面的變量作字符串連接

  • 如下例中的partition變量,還請不要誤解,分表不是這樣做的,下一章會介紹標準的分表設置。

    @SQL("SELECT user_id, device_token FROM test_##(:partition) LIMIT :limit")
    public List<Test> getTests(@SQLParam("partition") int partition, @SQLParam("limit") int limit);
  • 語法三:條件選擇
  • 井號if(#if{})用於表示當條件滿足時sql拼接。
    @SQL("SELECT user_id, device_token FROM test_##(:partition) #if(:user>0){ where user_id=:user } LIMIT :limit")
    public List<Test> getTestsIf(@SQLParam("partition") int partition, @SQLParam("limit") int limit, @SQLParam("user") int user); 
  • 其他語法:還有for循環,實際使用少。
  • 典型地,一般的select in查詢,可以直接傳入list,例如下例中的ids變量:
    @SQL("SELECT user_id, device_token FROM test_##(:partition) where user_id in(:ids)")
    public List<Test> getTestsByIds(@SQLParam("partition") int partition, @SQLParam("ids") List<Integer> ids);

3.C DAO層:分表設置

歡迎順利進入本章,如果您的企業需要這一節的內容,那麼說明用戶量很有前途,如果使用了本節的內容,不防向[email protected]發信一封以表謝意,我們會很高興收到各種反饋。

3.C.1 mysql分表的常規做法

以下是個人從業經驗中的分表規則:

  • 按照 id % 100 分爲一百份
  • 按照 id % 16 分爲十六份
  • 按照 id/10 % 10 分爲十份
  • 按照 id%10 分爲十份

以上分表規則特別在mysql中使用機會比較多,各有優勢,沒有對錯,只有最好與最不好用。

3.C.2 使用分表第一步:添加新的依賴

要使用分表,需要添加新的依賴,由bmw提供的bmwutils。

<dependency>
    <groupId>com.54chen</groupId>
    <artifactId>bmwutils</artifactId>
    <version>0.0.2</version>
</dependency>

3.C.3 使用分表第二步:設置applicationContext.xml分表規則

在開寫代碼之前,需要告訴DAO是哪個表需要分表,按照什麼規則分,分多少份。

     <!-- 以下配置爲分表設置 -->
     <bean id="jade.routerInterpreter" class="com.xiaomi.common.service.dal.routing.RewriteSQLInterpreter">
             <property name="routingConfigurator" ref="jade.routingConfigurator" />
     </bean>
     <bean id="jade.routingConfigurator" class="com.xiaomi.common.service.dal.routing.RoutingConfigurator">
             <property name="partitions">
                   <list> 
                        <value>hash:test:id:test_{0}:100</value> 
                   </list>
             </property>
     </bean>
  • 此處配置中,partitions參數爲一個list,可以對多個table進行定義。
  • hash:test🆔test_{0}:100 表示:使用hash這種辦法,將test這個表,按照id的值,分成100份,每份的表名爲test_x

3.C.4 使用分表第三步:bmwutils支持的分表辦法

  • (hash)上例中的hash: 最常用的 id % 100 就是這種辦法。該辦法會把傳入的值先進行轉爲數字後與定義的份數進行取模(%)。
  • (direct)最直接的一種:用的少一些,沒有什麼規則,直接根據第四個正則,與第三位傳入的值進行替換。假設有個人名錶,按照字母分表可以用。name_A,name_B,name_C...
  • (round)輪循:根據設置,按照調用sql的情況,輪流使用各個表。
  • (range)範圍:一般用來做日期範圍的分表,比如說微博類的,可變值爲一個時間,當時間傳入時,第三位支持log_{yyyy} log_{yyyy_MM}等時間格式的替換,可輕鬆做到按周、月、年分表。
  • (xm-hash)小米hash:一種古怪的辦法,按照傳入值的十位進行取模的分表方案。
  • (xm-str-hash)小米字符串hash:將字符串按照固定算法變成long之後,再按照小米hash邏輯處理。
  • (hex-hash)16進制分表:固定256份以內,傳入的值按照16進制轉換後按hash求模。

3.C.5 使用分表第四步:寫DAO代碼@ShardBy

    @SQL("SELECT user_id, device_token FROM test where user_id =:id")
    public List<Test> getTestsById(@ShardBy @SQLParam("id") int id);

與不分表的dao相比,只多了一個shardBy,標識按照這個參數值分表。

rose手冊第四章: 安全

  • 在controller中,如果你不想將一個方法公開對外,那就不要將其標識爲public,因爲public的方法會被rose認爲是一個要公開的action。

rose手冊第五章:FAQ 常見問題

5.1 如何打一個可被rose識別的jar包

如果你使用maven,在pom中添加如果定義之後,你打出來的jar包就會被rose掃描到並且引入到上下文環境中:

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<configuration>
					<archive>
						<manifestEntries>
							<Rose>*</Rose>
						</manifestEntries>
					</archive>
				</configuration>
			</plugin>
  • 用途:
  • 比如說你做了一個攔截器,在多個項目裏需要相同的邏輯,那只需要把這個攔截器做在一個jar包裏,聲明是rose支持的包即可被使用。

5.2 會被認成batch執行的sql返回

如果你的sql在執行update insert delete,並且dao的第一個參數是list類的多個值,那這條sql會被拆成多條sql依次執行,執行的結果會以各條sql的返回組成的數組返回。

5.3 一個良好的大型WEB項目架構實踐

我們一般會把項目規定爲:controller/service/biz/dao層,不能跨層調用,只在service層允許同時調用子層多個方法。

5.4 爲何DAO不被初始化

相當多的初次使用DAO的朋友,會將XXXDAO寫作XXXDao,注意大小寫,只有DAO全大寫時的class,纔會被jade認識並掃描入環境中。

rose手冊第六章:附錄

附錄裏會有qieqie同學親筆的rose編年史和發展規劃。

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