在(zài)Android上(shàng)做服務端開發/Web開發/SpringMVC開發 - 新聞資訊 - 雲南小程序開發|雲南軟件開發|雲南網站建設-昆明融晨信息技術有限公司

159-8711-8523

雲南網建設/小程序開發/軟件開發

知識

不(bù)管是(shì)網站,軟件還是(shì)小程序,都要(yào / yāo)直接或間接能爲(wéi / wèi)您産生價值,我們在(zài)追求其視覺表現的(de)同時(shí),更側重于(yú)功能的(de)便捷,營銷的(de)便利,運營的(de)高效,讓網站成爲(wéi / wèi)營銷工具,讓軟件能切實提升企業内部管理水平和(hé / huò)效率。優秀的(de)程序爲(wéi / wèi)後期升級提供便捷的(de)支持!

您當前位置>首頁 » 新聞資訊 » 網站建設 >

在(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. 系統層架構
  2. 應用層架構
  3. 使用示例

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)。

接着HttpParserHttpConnection包裝了(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

相關案例查看更多