實際上,Controller是很薄的一層,它僅僅負責接收參數(shù),封裝參數(shù),調(diào)用不同的service。所以我們可以考慮先從它入手,簡化代碼。
我們現(xiàn)有的controller做法是,不同的業(yè)務調(diào)用不同的servlet,通過參數(shù)的不同,調(diào)用不同的方法,比如,有下面一個form
http://.
1
2
3
4
|
<formaction=”UserServlet?command=login”>
<inputname=”name”/>
<inputname=”password”/>
</form>
|
如果我們在這個表單中輸入用戶名密碼,最終后臺會將這個請求提交到UserServlet中,然后根據(jù)command=login,調(diào)用UserServlet中的login方法。在login方法中,會有這樣的一段代碼:
http://.
1
2
3
4
5
6
7
8
|
Stringname=request.getAttribute(“name”);
Stringpassword=request.getAttribute(“password”);
Booleanresult=userService.hasUser(name,password);
if(result){
....
}else{
....
}
|
我們可以發(fā)現(xiàn),基本上這個servlet中,所有的方法幾乎都有著從request中獲取參數(shù)的這么一個過程,而且同一個servlet中,需要獲取的參數(shù)大部分都是重疊的(比如UserServlet中,幾乎所有的方法都需要獲取name和password的值,才能進行近一步操作),既然每一個方法都有這么一個需求,為什么不考慮將這一過程抽象出來呢?
首先,我們可以設計一個叫AaronDispatcher的類,它負責截取了所有的對項目的訪問的http請求。
比如,我們上面的請求叫UserServlet?command=login,同時傳遞三個參數(shù)name和password(以及上面的command) 。AaronDispatcher巨牛叉,它直接把這個請求截取了,并進行分析,首先它的名字叫UserServlet,調(diào)用的方法叫l(wèi)ogin。為了不引發(fā)歧義,我們更改前臺的請求地址,改為發(fā)送到UserAction?command=login。
然后我們可以重新設計UserServlet,創(chuàng)建全新的UserAction。(現(xiàn)已加入豪華午餐)
http://.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
publicUserAction{
Stringname;
Stringpassword;
Stringnewpassword;//updatePassword這個方法需要
Stringoldpassword;
......//所有的UserAction從前端獲取的參數(shù)
publiclogin(){
...
}
publiclogout(){
...
}
publicupdatePassword(){
...
}
......//所有UserAction需要提供的方法
}//UserAction結(jié)束
|
(眼疾手快的人也許可以注意到一點:這個類不再需要接受HttpServletRequest及HttpServletResponse作為參數(shù)了)
每當我們有一個發(fā)送到UserAction的請求,AaronDispatcher就幫我們new一個新的UserAction實例,同時將請求中的參數(shù)賦給UserServlet中的屬性。具體的底層做法,類似于下面這樣:(實際上會復雜很多,不會直接new對象,而是使用反射來創(chuàng)建對象并賦值屬性)
http://.
1
2
3
4
5
6
|
UserActionuserAction=newUserAction();
userAction.setName(request.getAttrbute(“name”));
userAction.setPassword(request.getAttrbute(“password”));
userAction.setNewpassword(request.getAttrbute(“newpassword”));
userAction.setOldpassword(request.getAttrbute(“oldpassword”));
......
|
如果我們需要http://.登陸功能,直接調(diào)用userAction.login()就可以了(至于name和password,直接可以在方法內(nèi)部獲取當前對象屬性)。所有的方法中,從request中獲取參數(shù)并進行封裝的這么一個過程,全部都被巨牛叉的AaronDispatcher做了,是不是減少了很多重復的代碼量?!
可能會有疑慮,所有的請求,無論什么方法,都進行一次屬性全賦值,如果前臺沒有傳入這個屬性,不就為空了嘛?但是要知道,如果我們調(diào)用login這個功能,newpassword和oldpassword固然會因為前臺沒有相應的屬性值傳入而設為null,但是,但是,在login方法中,我們根本就不會用到這兩個參數(shù)??!所以即使為空也不會有錯的!
甚至我們可以做的再牛叉一點,AaronDispatcher會去讀取一段配置文件,配置文件中指定了什么樣的請求調(diào)用什么養(yǎng)的類以及相應的方法,這樣我們就可以徹底解耦最前方的Controller了!
但是AaronDispatcher是怎么做到無論什么類,當中有什么屬性,我們都不需要事先知道,我們都可以接收前端參數(shù),給他們的屬性賦值呢?(答案是通過反射)
現(xiàn)在,我們已經(jīng)成功的重新發(fā)明輪子了!
因為以上這個偉大的想法已經(jīng)有被別人搶在前面實現(xiàn)了,就是zm的Struts2,毋庸置疑,Struts2的核心功能就是這么簡單。
在Struts2中,每一個處理類被稱之為Action,而Struts2也正是通過xml配置文件,實現(xiàn)了無需要修改代碼,通過修改配置文件,就可以修改Controller。
Struts2發(fā)展到今天已然是一個功能齊全的龐然大物了。
正如一開始所說,MVC框架只不過幫助我們封裝了請求參數(shù),分發(fā)了請求而已。Controller是非常薄的一層,而我們的業(yè)務邏輯都是由BLL層提供的Service對象實現(xiàn)。
首先講述一下為什么會有所謂BLL(Business Logic Layer)和DAL(Dataaccess Layer)了。在一個項目中,無論是查詢用戶的用戶名,還是查詢庫存數(shù)量,這些數(shù)據(jù)終歸是要保存到數(shù)據(jù)庫的,而這些對數(shù)據(jù)庫的操作將會mb的頻繁,如果我們不將這些對數(shù)據(jù)庫表的操作獨立出來,如果在多個方法中存在著對一個用戶記錄的查詢,我們不得不把這段代碼copy、paste無數(shù)次,既然這樣,我們?yōu)槭裁床幌裆厦婺菢?,將這種可能會多次遇到操作抽象出來呢?于是就有了所謂的DAL了,這樣,無論在什么地方,需要用到數(shù)據(jù)庫查詢相關的工作的時候,僅僅需要這么做:
http://.
1
2
|
Useruser=userDaoImp.getUserById(userId);
......
|
這么做有一個好處:減少了因為持久化方案的更換而導致的代碼修改帶來的工作。
持久化是一個非常gd大氣的專業(yè)術語,說的更專業(yè)一點,就是將內(nèi)存中的數(shù)據(jù)保存到硬盤中。在我們的項目中,用戶進行了注冊,我們需要將用戶注冊的用戶名密碼保存起來,以便下次用戶登陸的時候我們能夠知道,這個用戶名的用戶是合法注冊過的。
通常持久化的方案就是將數(shù)據(jù)保存到數(shù)據(jù)庫中,但是我相信如果我不愿意使用數(shù)據(jù)庫,而直接將用戶名密碼明文保存到文本文件中,也沒有人會從技術上反對吧(實際上這種事情在中國互聯(lián)網(wǎng)的發(fā)展歷史中還真發(fā)生過。。。),如果我真的選擇這么做,我所需要做的工作就是僅僅修改DAL中的實現(xiàn),將對數(shù)據(jù)庫的操作改為對本地文件的操作,而無須修改調(diào)用持久化方法的方法。
業(yè)務層負責業(yè)務的處理(接收上層傳過來的信息進行處理),當處理完之后,將處理的結(jié)果利用DAL的對象進行“保存到硬盤”。而DAL具體是怎么實現(xiàn)的,wq不會影響到已實現(xiàn)的業(yè)務。
很明顯的,為了做到上面這一點,DAL中的方法要盡量的“單純”,不包含任何的業(yè)務上的邏輯,僅僅是將內(nèi)存中的數(shù)據(jù)(一般就是某個對象)保存到硬盤的“實現(xiàn)”,以及從硬盤讀取的數(shù)據(jù)提取到內(nèi)存的“實現(xiàn)”。
已經(jīng)很明顯了,三層架構(gòu)不是從來都有的,只不過是在無數(shù)次痛苦的經(jīng)歷過后先烈們總結(jié)出來的一套證明可以在某一方面減少因變動而帶來的額外工作量。說它經(jīng)典,也只不過是因為它實現(xiàn)了展示、業(yè)務、持久化這三個必不可少卻又相對對立的需求的切割(不過確實有的項目中,展示不是必選的)。
所以基本上所有的復雜架構(gòu)也只不過是在此基礎上的進一步分割,曾經(jīng)做過一個巨復雜SaaS項目,為了減少某些不定因素的變動而帶來的代碼上的改動,架構(gòu)師將BLL分成了兩層,在原有的BLL之上又增加了一層core business layer。這樣MVC框架只需要調(diào)用core business的業(yè)務而無須自己在重復組裝比較底層的業(yè)務邏輯了。
如果有更復雜些的項目的話,就需要通過分割子項目及更復雜的層級關系來解決了。
這個時候我們或許應該講述BLL了,不過在此之前,我們可以再多想一步,能不能修改DAL中的東西,讓我們使用起來更簡單?
一般來說,數(shù)據(jù)庫中的表對應著java中的類,表中的一行記錄對應著一個entity對象,表中的字段對應著對象中的屬性,我以前一直覺得很神奇,這就是傳說中的ORM。這當中還有很多更復雜的東西,比如多表級聯(lián)的結(jié)果映射為對象,在這里我們先忽略這些復雜的情況。
有了上面的知識,我們可以發(fā)現(xiàn), 如果我們選擇關系型數(shù)據(jù)庫作為持久化方案,我們的DAL其實也很“單純”,他們所做的也不過是將對象屬性通過sql存儲到數(shù)據(jù)庫、將通過sql獲取的數(shù)據(jù)封裝為對象。
同樣的我們可以寫一個巨牛叉框架(好吧,這次不是寫一個巨牛叉的類了),它會自動根據(jù)我們entity的名字,去數(shù)據(jù)庫尋找相應的表,當我們調(diào)用insert,delete,update,select等方法的時候,它會自動幫助我們根據(jù)要求及參數(shù)拼接sql,然后去數(shù)據(jù)庫查詢/修改記錄,如果是查詢,則把查詢出來的記錄集封裝成對象,保存在list中。這樣,我們就可以在DAL中簡單的定義一些entity就可以了。