像SpringMVC一樣在Android上做Web開發
一部分Android開發者看到這個標題時可能有點疑惑,SpringMVC不是用來做JavaWeb開發的嗎?難道被移植到Android上來了?答案是否定的,因為SpringMVC是基於Servlet的,在Android上開發一個支援Servlet的容器(Tomcat、JBoss)可不簡單,所以我們是在Android上開發了一套全新的WebServer + WebFramework。
AndServer2.0基於編譯時註解實現了SpringMVC的大部分註解Api,其Request的分發流程也基本和SpringMVC一致,與SpringMVC最大的不同是SpringMVC基於執行時註解,並且SpringMVC提供的功能更多更強大。不過AndServer提供的功能在Android上來做服務端開發是完全足夠的。
看到這裡讀者朋友應該知道了,AndServer2.0是使用註解開發Web程式的,為了有個更直觀的瞭解,我們先看一個模擬使用者登入的Http Api:
@RestController public class UserController { @PostMapping("/login") public String login(@RequestParam("account") String account, @RequestParam("password") String password) { if(...) { return "Successful"; } return "Failed"; } } 複製程式碼
假設服務端的Address是 192.168.1.11
,監聽的埠是 8080
,那麼通過 http://192.168.1.11:8080/login
就可以訪問該登入Http Api了。
感興趣的讀者可以幫我們做一下Code Review:
ofollow,noindex">github.com/yanzhenjie/…下文將依次介紹以下三點:
- 系統層架構
- 應用層架構
- 使用示例
1. 系統層架構
我們都知道Http是根據Http協議使用Socket做了連線屬性、資料格式、互動邏輯方面的包裝,我們來 模擬 一段服務端啟動Server的程式碼:
public void startServer(String address, int port) { InetAddress inetAddress = InetAddress.getByName(); ServerSocket serverSocket = new ServerSocket(8080, 512, inetAddress); while (true) { Socket socket = serverSocket.accept(); HttpConnection connection = HttpParser.parse(socket); HttpThead thread = new HttpThread(connection); thread.start(); } } 複製程式碼
ServerSocket
監聽了某個埠,當有 Socket
連線上來的時候去把這個 Socket
解析為 HttpConnection
,解析過程是按照Http協議擬定的格式,從 Socket
的 InputStream
讀取一些資料後,用 Request
和 Response
包裝 Socket
和未讀取的流(比如標記下次讀取流的起點),下文會再提到。
接著 HttpParser
用 HttpConnection
包裝了 Request
和 Reponse
返回,可想而知,作為服務端程式, HttpConnection
至少包涵了 Request
和 Response
物件:
public class HttpConnection { private Request mRequest; private Response mResponse; ... } 複製程式碼
緊接著啟動了一個執行緒去處理當前連線,其實也就是處理當前 Request
,用 Response
寫出資料,怎麼處理這個 Request
是一個WebFramework的核心,作為Http服務端程式,應該能提供Html檔案、JS檔案、Java Method(Http Api)等讓客戶端訪問,因此得有一個管理員來負責請求和資源的匹配,所以有一個叫做 HttpDispatcher
的類來決定這個 Request
應該發給哪個資源去處理:
public class HttpDispatcher { public void dispath(Request request, Response response) { ... } } 複製程式碼
在 HttpThead
裡面,當執行緒被喚起時只需要負責呼叫 HttpDispatcher#diaptch()
即可,到這裡就比較清晰了,只需要 HttpDispatcher
把當前 Request
派發到對應的Html File或者Java Method處理就可以了,具體的處理就屬於HttpFramework的事,我們下文再講。
這就是一個簡單的WebServer的藍圖,我們根據設想畫出了系統層架構圖:

系統層執行時流程圖:

上圖中, Handler
表示處理請求的操作手柄,可能是Html File或者Java Method。值得高興的一點是,在我們迭代了幾個版本後,發現Apache組織提供了上述藍圖中的 HttpParser
層,因此為了穩定性和節省人力我們已經替換該層為Apache的實現。
2. 應用層架構
應用層就是上文中提到的WebFramework,也就是上一個小節流程圖的 Framework
層,包括了Session的處理、Cookie的處理、Cache的處理等。
接著上文, HttpDispatcher
需要把當前 Request
派發到對應的Html File或者Java Method處理,而 Handler
代表了Html File或者Java Method,因為此二者區別極大,用一個類來表示它們顯然有些不合理,於是我們想到了使用 Adapter
模式,所以有了一個抽象類 RequestHandler
:
public abstract class RequestHandler { public abstract void handle(Request request, Response response); } 複製程式碼
RequestHandler
可以表示任何檔案或者Java Method, HttpDispatcher
的作用是分發請求到各個資源,所以 HttpDispatcher
不應該來分析某個 RequestHandler
具體是什麼東西,它應該直接呼叫 RequestHandler
來處理請求,因為Html File或者Java Method對應的 RequestHandler
在實現上顯然大有不同,所以這裡適用 Adapter
模式,於是我們用 HandlerAdapter
去做 RequestHandler
的適配:
public class HandlerAdapter { public RequestHandler getHandler(Request request) { ... } ... } 複製程式碼
HandlerAdapter
除了能獲取 RequestHandler
之外,還需要做一些描述性的工作,好讓 HttpDispatcher
知道當前適配的 RequestHandler
是可以處理正要分發的這個 Request
的。
因為Html File和Java Method的返回值又是大相徑庭,因為返回值是輸出到客戶端展示的,所以我們把返回值抽象為 View
:
public class View { public Object output() { ... } ... } 複製程式碼
如上所以, output()
方法就是獲取 Handler
輸出的內容,還有其他方法是對這個輸出的描述,這裡不例舉。
因為 View
是返回值,沒有具體的互動了,所以不適用 Adapter
模式了,因此我們必須有一個處理返回值的機制,把處理返回值的機制叫做 ViewResolver
:
public class ViewResolver { public void resolver(View view, Request request, Response response) { ... } } 複製程式碼
在 ViewResolver
中根據輸出內容的型別不同,處理方式也不同,最終把輸出內容通過 Response
物件寫出去,底層是使用上文中提到的被 Response
包裝的 Socket
寫出。
這就是一個簡單的WebFramework的藍圖,我們根據設想畫出了應用層架構圖:

應用層執行時流程圖:

上圖中, Interceptor
表示對請求的攔截器,比如可以做一些不允許沒登入或者沒許可權的請求進入的工作。 ExceptionResolver
表示全域性異常處理器,比如某個Api發生了異常,會轉到 ExceptionResolver
中處理,而不至於當前請求不響應或者響應了不想被客戶端看到的訊息。
另外需要補充的是,上文中提到的都是粗略的設計,中間還有一些細節,例如Session的處理、Cookie的處理、快取的處理等都未提到,其中任何一個知識點單獨拿出來都可以寫一篇文章,由於篇幅關係這裡不做詳細介紹。
架構設計和流程到此就都介紹完了,有興趣的開發者也可以自己實現一下。
3. 使用示例
AndServer對於方便使用的理念是:只需要添加註解即可,不需要再做額外的配置。所以除了像文章開頭那樣用註解寫好Api之外,只需要指定監聽埠啟動伺服器就可以了。
與讀者做個約定,下文中伺服器Address都是 192.168.1.11
,監聽的埠是 8080
。
3.1. 網站部署示例
我們先來部署一個位於Assets中 /web
下的網站:
@Website public class InternalWebsite extends AssetsWebsite { public InternalWebsite() { super("/web"); } } 複製程式碼
因此SD的檔案可以刪除也可以增加,所以很方便做一些檔案的熱插拔,部署SD卡的網站:
@Website public class InternalWebsite extends StorageWebsite { public InternalWebsite() { super("/sdcard/AndServer/web"); } } 複製程式碼
如上所示,開發者只需要將網站所在的路徑告訴 AndServer
,並新增 Website
註解即可,該網站的Html、CSS、JS、其它檔案都可以被訪問,例如 /web
目錄下有一個 index.html
檔案,那麼訪問地址就是 http://192.168.1.11:8080/
或者 http://192.168.1.11:8080/index.html
。
3.2. Http Api開發示例
在文章開頭我們看了一個模擬使用者的Http Api,下面我們增加一個模擬獲取使用者資訊的Api:
@RequestMapping("/user") @RestController public class UserController { @PostMapping("/login") public String login(@RequestParam("account") String account, @RequestParam("password") String password) { if(...) { return "Successful"; } return "Failed"; } @GetMapping("/info/{userId}") public User info(@PathVariable("userId") String userId) { User user = ...; return user; } } 複製程式碼
於是我們得到兩個地址:
POST http://192.168.1.11:8080/user/login GEThttp://192.168.1.11:8080/user/info/uid_001 複製程式碼
接下來什麼配置都不用做,只需要啟動伺服器,這幾個地址就可以用了。
3.3. 啟動伺服器
AndServer.serverBuilder() .inetAddress(NetUtils.getLocalIPAddress()) .port(8080) .timeout(10, TimeUnit.SECONDS) .build() .start(); 複製程式碼
如上所示只需要指定要監聽的服務端地址和埠,啟動伺服器就可以訪問上面的所有地址了,不需要再做其他配置。
由於篇幅關係,本文到此結束,介紹的比較粗略,有興趣的開發者可以看看專案使用文件:
www.yanzhenjie.com/AndServer