在(zài)Android上(shàng)做服務端開發/Web開發/SpringMVC開發
發表時(shí)間:2018-9-26
發布人(rén):融晨科技
浏覽次數:69
一部分Android開發者看到(dào)這(zhè)個(gè)标題時(shí)可能有點疑惑,SpringMVC不(bù)是(shì)用來(lái)做JavaWeb開發的(de)嗎?難道(dào)被移植到(dào)Android上(shàng)來(lái)了(le/liǎo)?答案是(shì)否定的(de),因爲(wéi / wèi)SpringMVC是(shì)基于(yú)Servlet的(de),在(zài)Android上(shàng)開發一個(gè)支持Servlet的(de)容器(Tomcat、JBoss)可不(bù)簡單,所以(yǐ)我們是(shì)在(zài)Android上(shàng)開發了(le/liǎo)一套全新的(de)WebServer + WebFramework。
AndServer2.0基于(yú)編譯時(shí)注解實現了(le/liǎo)SpringMVC的(de)大(dà)部分注解Api,其Request的(de)分發流程也(yě)基本和(hé / huò)SpringMVC一緻,與SpringMVC最大(dà)的(de)不(bù)同是(shì)SpringMVC基于(yú)運行時(shí)注解,并且SpringMVC提供的(de)功能更多更強大(dà)。不(bù)過AndServer提供的(de)功能在(zài)Android上(shàng)來(lái)做服務端開發是(shì)完全足夠的(de)。
看到(dào)這(zhè)裏讀者朋友應該知道(dào)了(le/liǎo),AndServer2.0是(shì)使用注解開發Web程序的(de),爲(wéi / wèi)了(le/liǎo)有個(gè)更直觀的(de)了(le/liǎo)解,我們先看一個(gè)模拟用戶登錄的(de)Http Api:
@RestController
public class UserController {
@PostMapping("/login")
public String login(@RequestParam("account") String account,
@RequestParam("password") String password) {
if(...) {
return "Successful";
}
return "Failed";
}
}
假設服務端的(de)Address是(shì)192.168.1.11
,監聽的(de)端口是(shì)8080
,那麽通過http://192.168.1.11:8080/login
就(jiù)可以(yǐ)訪問該登錄Http Api了(le/liǎo)。
感興趣的(de)讀者可以(yǐ)幫我們做一下Code Review:
https://github.com/yanzhenjie/AndServer
下文将依次介紹以(yǐ)下三點:
- 系統層架構
- 應用層架構
- 使用示例
1. 系統層架構
我們都知道(dào)Http是(shì)根據Http協議使用Socket做了(le/liǎo)連接屬性、數據格式、交互邏輯方面的(de)包裝,我們來(lái)模拟一段服務端啓動Server的(de)代碼:
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
監聽了(le/liǎo)某個(gè)端口,當有Socket
連接上(shàng)來(lái)的(de)時(shí)候去把這(zhè)個(gè)Socket
解析爲(wéi / wèi)HttpConnection
,解析過程是(shì)按照Http協議拟定的(de)格式,從Socket
的(de)InputStream
讀取一些數據後,用Request
和(hé / huò)Response
包裝Socket
和(hé / huò)未讀取的(de)流(比如标記下次讀取流的(de)起點),下文會再提到(dào)。
接着HttpParser
用HttpConnection
包裝了(le/liǎo)Request
和(hé / huò)Reponse
返回,可想而(ér)知,作爲(wéi / wèi)服務端程序,HttpConnection
至少包涵了(le/liǎo)Request
和(hé / huò)Response
對象:
public class HttpConnection {
private Request mRequest;
private Response mResponse;
...
}
緊接着啓動了(le/liǎo)一個(gè)線程去處理當前連接,其實也(yě)就(jiù)是(shì)處理當前Request
,用Response
寫出(chū)數據,怎麽處理這(zhè)個(gè)Request
是(shì)一個(gè)WebFramework的(de)核心,作爲(wéi / wèi)Http服務端程序,應該能提供Html文件、JS文件、Java Method(Http Api)等讓客戶端訪問,因此得有一個(gè)管理員來(lái)負責請求和(hé / huò)資源的(de)匹配,所以(yǐ)有一個(gè)叫做HttpDispatcher
的(de)類來(lái)決定這(zhè)個(gè)Request
應該發給哪個(gè)資源去處理:
public class HttpDispatcher {
public void dispath(Request request, Response response) {
...
}
}
在(zài)HttpThead
裏面,當線程被喚起時(shí)隻需要(yào / yāo)負責調用HttpDispatcher#diaptch()
即可,到(dào)這(zhè)裏就(jiù)比較清晰了(le/liǎo),隻需要(yào / yāo)HttpDispatcher
把當前Request
派發到(dào)對應的(de)Html File或者Java Method處理就(jiù)可以(yǐ)了(le/liǎo),具體的(de)處理就(jiù)屬于(yú)HttpFramework的(de)事,我們下文再講。
這(zhè)就(jiù)是(shì)一個(gè)簡單的(de)WebServer的(de)藍圖,我們根據設想畫出(chū)了(le/liǎo)系統層架構圖:
系統層運行時(shí)流程圖:
上(shàng)圖中,Handler
表示處理請求的(de)操作手柄,可能是(shì)Html File或者Java Method。值得高興的(de)一點是(shì),在(zài)我們叠代了(le/liǎo)幾個(gè)版本後,發現Apache組織提供了(le/liǎo)上(shàng)述藍圖中的(de)HttpParser
層,因此爲(wéi / wèi)了(le/liǎo)穩定性和(hé / huò)節省人(rén)力我們已經替換該層爲(wéi / wèi)Apache的(de)實現。
2. 應用層架構
應用層就(jiù)是(shì)上(shàng)文中提到(dào)的(de)WebFramework,也(yě)就(jiù)是(shì)上(shàng)一個(gè)小節流程圖的(de)Framework
層,包括了(le/liǎo)Session的(de)處理、Cookie的(de)處理、Cache的(de)處理等。
接着上(shàng)文,HttpDispatcher
需要(yào / yāo)把當前Request
派發到(dào)對應的(de)Html File或者Java Method處理,而(ér)Handler
代表了(le/liǎo)Html File或者Java Method,因爲(wéi / wèi)此二者區别極大(dà),用一個(gè)類來(lái)表示它們顯然有些不(bù)合理,于(yú)是(shì)我們想到(dào)了(le/liǎo)使用Adapter
模式,所以(yǐ)有了(le/liǎo)一個(gè)抽象類RequestHandler
:
public abstract class RequestHandler {
public abstract void handle(Request request, Response response);
}
RequestHandler
可以(yǐ)表示任何文件或者Java Method,HttpDispatcher
的(de)作用是(shì)分發請求到(dào)各個(gè)資源,所以(yǐ)HttpDispatcher
不(bù)應該來(lái)分析某個(gè)RequestHandler
具體是(shì)什麽東西,它應該直接調用RequestHandler
來(lái)處理請求,因爲(wéi / wèi)Html File或者Java Method對應的(de)RequestHandler
在(zài)實現上(shàng)顯然大(dà)有不(bù)同,所以(yǐ)這(zhè)裏适用Adapter
模式,于(yú)是(shì)我們用HandlerAdapter
去做RequestHandler
的(de)适配:
public class HandlerAdapter {
public RequestHandler getHandler(Request request) {
...
}
...
}
HandlerAdapter
除了(le/liǎo)能獲取RequestHandler
之(zhī)外,還需要(yào / yāo)做一些描述性的(de)工作,好讓HttpDispatcher
知道(dào)當前适配的(de)RequestHandler
是(shì)可以(yǐ)處理正要(yào / yāo)分發的(de)這(zhè)個(gè)Request
的(de)。
因爲(wéi / wèi)Html File和(hé / huò)Java Method的(de)返回值又是(shì)大(dà)相徑庭,因爲(wéi / wèi)返回值是(shì)輸出(chū)到(dào)客戶端展示的(de),所以(yǐ)我們把返回值抽象爲(wéi / wèi)View
:
public class View {
public Object output() {
...
}
...
}
如上(shàng)所以(yǐ),output()
方法就(jiù)是(shì)獲取Handler
輸出(chū)的(de)内容,還有其他(tā)方法是(shì)對這(zhè)個(gè)輸出(chū)的(de)描述,這(zhè)裏不(bù)例舉。
因爲(wéi / wèi)View
是(shì)返回值,沒有具體的(de)交互了(le/liǎo),所以(yǐ)不(bù)适用Adapter
模式了(le/liǎo),因此我們必須有一個(gè)處理返回值的(de)機制,把處理返回值的(de)機制叫做ViewResolver
:
public class ViewResolver {
public void resolver(View view, Request request, Response response) {
...
}
}
在(zài)ViewResolver
中根據輸出(chū)内容的(de)類型不(bù)同,處理方式也(yě)不(bù)同,最終把輸出(chū)内容通過Response
對象寫出(chū)去,底層是(shì)使用上(shàng)文中提到(dào)的(de)被Response
包裝的(de)Socket
寫出(chū)。
這(zhè)就(jiù)是(shì)一個(gè)簡單的(de)WebFramework的(de)藍圖,我們根據設想畫出(chū)了(le/liǎo)應用層架構圖:
應用層運行時(shí)流程圖:
上(shàng)圖中,Interceptor
表示對請求的(de)攔截器,比如可以(yǐ)做一些不(bù)允許沒登錄或者沒權限的(de)請求進入的(de)工作。ExceptionResolver
表示全局異常處理器,比如某個(gè)Api發生了(le/liǎo)異常,會轉到(dào)ExceptionResolver
中處理,而(ér)不(bù)至于(yú)當前請求不(bù)響應或者響應了(le/liǎo)不(bù)想被客戶端看到(dào)的(de)消息。
另外需要(yào / yāo)補充的(de)是(shì),上(shàng)文中提到(dào)的(de)都是(shì)粗略的(de)設計,中間還有一些細節,例如Session的(de)處理、Cookie的(de)處理、緩存的(de)處理等都未提到(dào),其中任何一個(gè)知識點單獨拿出(chū)來(lái)都可以(yǐ)寫一篇文章,由于(yú)篇幅關系這(zhè)裏不(bù)做詳細介紹。
架構設計和(hé / huò)流程到(dào)此就(jiù)都介紹完了(le/liǎo),有興趣的(de)開發者也(yě)可以(yǐ)自己實現一下。
3. 使用示例
AndServer對于(yú)方便使用的(de)理念是(shì):隻需要(yào / yāo)添加注解即可,不(bù)需要(yào / yāo)再做額外的(de)配置。所以(yǐ)除了(le/liǎo)像文章開頭那樣用注解寫好Api之(zhī)外,隻需要(yào / yāo)指定監聽端口啓動服務器就(jiù)可以(yǐ)了(le/liǎo)。
與讀者做個(gè)約定,下文中服務器Address都是(shì)
192.168.1.11
,監聽的(de)端口是(shì)8080
。
3.1. 網站部署示例
我們先來(lái)部署一個(gè)位于(yú)Assets中/web
下的(de)網站:
@Website
public class InternalWebsite extends AssetsWebsite {
public InternalWebsite() {
super("/web");
}
}
因此SD的(de)文件可以(yǐ)删除也(yě)可以(yǐ)增加,所以(yǐ)很方便做一些文件的(de)熱插拔,部署SD卡的(de)網站:
@Website
public class InternalWebsite extends StorageWebsite {
public InternalWebsite() {
super("/sdcard/AndServer/web");
}
}
如上(shàng)所示,開發者隻需要(yào / yāo)将網站所在(zài)的(de)路徑告訴AndServer
,并添加Website
注解即可,該網站的(de)Html、CSS、JS、其它文件都可以(yǐ)被訪問,例如/web
目錄下有一個(gè)index.html
文件,那麽訪問地(dì / de)址就(jiù)是(shì)http://192.168.1.11:8080/
或者http://192.168.1.11:8080/index.html
。
3.2. Http Api開發示例
在(zài)文章開頭我們看了(le/liǎo)一個(gè)模拟用戶的(de)Http Api,下面我們增加一個(gè)模拟獲取用戶信息的(de)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;
}
}
于(yú)是(shì)我們得到(dào)兩個(gè)地(dì / de)址:
POST http://192.168.1.11:8080/user/login
GET http://192.168.1.11:8080/user/info/uid_001
接下來(lái)什麽配置都不(bù)用做,隻需要(yào / yāo)啓動服務器,這(zhè)幾個(gè)地(dì / de)址就(jiù)可以(yǐ)用了(le/liǎo)。
3.3. 啓動服務器
AndServer.serverBuilder()
.inetAddress(NetUtils.getLocalIPAddress())
.port(8080)
.timeout(10, TimeUnit.SECONDS)
.build()
.start();
如上(shàng)所示隻需要(yào / yāo)指定要(yào / yāo)監聽的(de)服務端地(dì / de)址和(hé / huò)端口,啓動服務器就(jiù)可以(yǐ)訪問上(shàng)面的(de)所有地(dì / de)址了(le/liǎo),不(bù)需要(yào / yāo)再做其他(tā)配置。
由于(yú)篇幅關系,本文到(dào)此結束,介紹的(de)比較粗略,有興趣的(de)開發者可以(yǐ)看看項目使用文檔:
https://www.yanzhenjie.com/AndServer