不妨試試更快更小更靈活Java開發框架Solon

2022-10-22 06:01:10

@

概述

定義

Solon 官網地址 https://solon.noear.org/

Solon GitHub原始碼地址 https://github.com/noear/solon

Solon for java,一個更現代感的,輕量級應用開發框架,崇尚剋制、簡潔、開放、生態設計理念。最新版本為1.10.7

Solon從專案啟動以來,參考過大量前人的作品。尤其是 Spring Boot、jFinal、Javalin 和 Asp.Net,吸取了諸多優點,且避開很多繁重的設計。歷時多年,核心始終保持 0.1Mb 的身材,超高的跑分,良好而自由的使用體驗。

目前支援jdk8、jdk11、jdk17、jdk19四個大版本,開發客製化方便,可通過組合不同的外掛快速開發不同的需求,開發人員幾乎可使用與SpringBoot相似的開發方式。其Solon Cloud 為一系列分散式開發的介面標準和設定規範,相當於DDD模式裡的防腐層概念,是 Solon 的微服務架構模式開發解決方案。在開發使用上官方也提供其與SpringBoot、SpringCloud、Dubbo的詳細區別,使用時查閱官方檔案即可。

效能

Solon 根據官方提供資料,比傳統的Java應用特別是Spring生態開發的應用啟動快 5 ~ 10 倍,qps 高 2~ 3 倍,執行時記憶體節省 1/3 ~ 1/2,打包可以縮到 1/2 ~ 1/10。因此成為更現代感的應用開發框架,實現更快、更小、更少、更自由!

  • 快:Qps 可達10萬之多
  • 小:核心 0.1Mb,最小 Web 完整開發單位 1Mb(相比Springboot專案包,小到可以乎略不計了)
  • 自由:程式碼操控自由,除了註解模式之外,還可以按需手動;框架選擇自由:可以用 solon-web 這樣的快速開發整合包。也可以按專案需要選擇不同的外掛組裝,比如:為非Solon專案新增solon.boot.jlhttp,0.2Mb 即可讓專案實現 http+mvc 支援。

架構

  • 緣起統一的處理架構想法(俗稱:三源合一):Http、Socket、WebSocket。不同的通訊訊號,進行統一架構處理,且小巧。 對於 Socket 和 WebSocket,在原 訊息+監聽 的模式之外增加了 上下文+處理 模式

  • 關於應用內在的啟動過程(即:應用的生命週期):序列的處理過程(含四個事件擴充套件點 + 兩個函數擴充套件點)

  • 請求的處理過程

  • Ioc & Aop 內部結構

  • 現有家簇成員圖譜

實戰

Solon Web範例

下載官方的helloworld範例 體驗下Solon 輕量和快。此外還可以下載官網提供豐富的配套範例:

專案 地址 說明
solon-examples https://gitee.com/noear/solon-examples 配套"學習/科目學習"進行演示

下載完解壓後匯入Idea中,是個標準的maven專案,pom檔案引入solon的父依賴和核心依賴

<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-web</artifactId>
</dependency>

一個組態檔app.yml,一個啟動類DemoApp,是不是和SpringBoot很相似,Solon 是一個容器型的應用開發框,在main方法中使用Solon.start啟動。app.yml內容如下:

server.port: 8080
solon.app:
  name: demoapp
  group: demo

這裡簡單修改hello方法的返回結果如下,可以直接執行,也可以先通過mvn clean package -DskipTests打包後再使用java -jar demo.jar執行。

幾小行程式碼一個http介面就完成,啟動速度非常快只用3ms,存取http://localhost:8080/hello?name=itxiaoshen 返回正確的結果

Solon Mybatis-Plus範例

環境準備:建立MySQL資料庫test、表appx,並插入測試資料

CREATE TABLE `appx` (
  `app_id` INT NOT NULL AUTO_INCREMENT COMMENT '應用ID',
  `app_key` VARCHAR(40) DEFAULT NULL COMMENT '應用存取KEY',
  `akey` VARCHAR(40) DEFAULT NULL COMMENT '(用於取代app id 形成的唯一key) //一般用於推廣註冊之類',
  `ugroup_id` INT DEFAULT '0' COMMENT '加入的使用者組ID',
  `agroup_id` INT DEFAULT NULL COMMENT '加入的應用組ID',
  `name` VARCHAR(50) DEFAULT NULL COMMENT '應用名稱',
  `note` VARCHAR(50) DEFAULT NULL COMMENT '應用備註',
  `ar_is_setting` INT NOT NULL DEFAULT '0' COMMENT '是否開放設定',
  `ar_is_examine` INT NOT NULL DEFAULT '0' COMMENT '是否稽核中(0: 沒稽核 ;1:稽核中)',
  `ar_examine_ver` INT NOT NULL DEFAULT '0' COMMENT '稽核 中的版本號',
  `log_fulltime` DATETIME DEFAULT NULL,
  PRIMARY KEY (`app_id`),
  UNIQUE KEY `IX_akey` (`akey`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='應用表';

INSERT appx(app_key,akey,ugroup_id,agroup_id,NAME,note,ar_is_setting,ar_is_examine,ar_examine_ver,log_fulltime) 
VALUES('asdfghjk','aaaaabbbbb',100,1001,'抖音','時尚短視訊',0,1,1,NOW());
INSERT appx(app_key,akey,ugroup_id,agroup_id,NAME,note,ar_is_setting,ar_is_examine,ar_examine_ver,log_fulltime) 
VALUES('sdfsdf','ccccdddd',102,1002,'招行','儲蓄',0,1,1,NOW());
INSERT appx(app_key,akey,ugroup_id,agroup_id,NAME,note,ar_is_setting,ar_is_examine,ar_examine_ver,log_fulltime) 
VALUES('34543','eeeegggg',103,1003,'有道詞典','翻譯',0,1,1,NOW());

新增mybatis-plus和mysql相關依賴如下:

        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>mybatis-plus-extension-solon-plugin</artifactId>
        </dependency>

        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>4.0.3</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>

app.yml檔案增加資料來源和mybatis-plus的設定

test.db1:
    schema: rock
    jdbcUrl: jdbc:mysql://192.168.40.100:3308/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true
    driverClassName: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456

##預設
mybatis.db1:
    typeAliases:    #支援包名 或 類名(.class 結尾)
        - "demo4031.model"
    mappers:        #支援包名 或 類名(.class 結尾)或 xml(.xml結尾 或 *.xml 結尾)
        - "demo4031.dso.mapper"
#        - "demo4031/dso/*.xml"
    configuration:
        cacheEnabled: false
        mapUnderscoreToCamelCase: true
        logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
    globalConfig:
        banner: false
        metaObjectHandler: "demo4031.dso.MetaObjectHandlerImpl"
        dbConfig:
            logicDeleteField: "deleted"
            logicDeleteValue: "2"

mapper介面和mapper.xml檔案與Spring整合Mybatis基本相同

@Mapper
public interface AppxMapper {
    AppxModel appx_get();
    Page<AppxModel> appx_get_page(Page<AppxModel> page);
    AppxModel appx_get2(int app_id);
    void appx_add();
    Integer appx_add2(int v1);

    @Select("SELECT * FROM INFORMATION_SCHEMA.TABLES")
    List<DbTable> listTables();
}

再新增業務的Service和實現類,最後新增PlusController控制器實現

@Mapping("/plus/")
@Controller
public class PlusController {
    @Inject
    AppServicePlus appServicePlus;

    @Mapping("test")
    public AppxModel test() {
        return appServicePlus.getById(2);
    }
}

新增mybatis-plus分頁的PageController控制器實現

@Mapping("/page/")
@Controller
public class PageController {
    @Db
    AppxMapper appxMapper;

    @Mapping("test")
    public Object test() throws Throwable {
        Page<AppxModel> page = new Page<>(2, 2);
        return appxMapper.appx_get_page(page);
    }
}

啟動程式後紀錄檔輸出如下

存取http://localhost:8080/plus/test,返回正確的結果

存取http://localhost:8080/page/test ,返回正確的分頁結果

Solon WebSocket範例

引入依賴

        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>nami</artifactId>
        </dependency>
        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>solon.socketd.client.websocket</artifactId>
        </dependency>

簡單幾行程式碼就實現WebSocket的伺服器端程式設計

@ServerEndpoint(value = "/ws/demo/{id}")
public class WebSocket implements Listener {
    @Override
    public void onOpen(Session session) {
        //path var
        String id = session.param("id");
        //query var
        String token = session.param("token");
        /*此處可以做籤權;對談的二次組織等...*/
    }

    @Override
    public void onMessage(Session session, Message message) throws IOException {
        //message.setHandled(true); //設為true,則不進入mvc路由
        session.send("你發了:" + message.bodyAsString());
    }
}

然後通過一個debug.htm通過javascript實現WebSocket收發功能,App啟動類開啟enableWebSocket

public class App {
    public static void main(String[] args) {
        //
        // 啟動Solon,並開啟WebSocket監聽;同時新增/路徑跳轉
        //
        Solon.start(App.class, args, app -> app.enableWebSocket(true)).get("/", c -> {
            c.redirect("/debug.htm");
        });
    }
}

啟動App後紀錄檔輸出如下

存取http://localhost:8080/ 輸入傳送資訊後伺服器端列印收到的輸入資訊

Solon Remoting RPC範例

RPC的實現分為3個模組,RPC提供者的實現、公共模組、服務消費者,公共模組存放資料模型和介面,可以同時提供給提供者和消費者參照。

服務提供者新增solon-rpc依賴

        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>solon-rpc</artifactId>
        </dependency>

服務提供者通過@Remoting註解實現RPC服務,程式碼如下

@Mapping("/user/")
@Remoting
public class UserServiceImpl implements UserService {
    @Override
    public UserModel getUser(Integer userId) {
        UserModel model = new UserModel();
        model.setId(userId);
        model.setName("user-" + userId);
        return model;
    }
}

服務消費者新增如下依賴

        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>solon-rpc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>solon.cloud</artifactId>
        </dependency>

app.yml組態檔設定服務發現的地址,也即是服務提供者提供的地址

server.port: 8080

solon.app:
  name: demoapp
  group: demo

solon.cloud.local:
  discovery:
    service:
      local:
        - "http://localhost:8081"

通過@NamiClient註解實現RPC遠端方法的呼叫

@Controller
public class UserController {
    //使用負載
    @NamiClient(name = "local",path = "/user/")
    UserService userService;

    @Mapping("test")
    public UserModel test() {
        UserModel user = userService.getUser(100);
        System.out.println(user);
        return user;
    }
}

啟動服務提供者和服務消費者

存取服務消費者測試Controller的測試介面,http://localhost:8080/test ,返回正確結果

Solon Cloud Nacos範例

引入依賴

        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>nacos-solon-cloud-plugin</artifactId>
        </dependency>

先準備好Nacos Server,這裡就直接使用前面文章已部署好的Nacos,建立好nacos的test名稱空間,為了演示讀取nacos的設定,在test下建立一個組為demo的test.properties,並新增db1.url的鍵值對。

然後在服務註冊端的本地app.yml組態檔新增相關nacos的設定資訊

server.port: 7112
solon.app:
  namespace: test
  group: demo
  name: helloapi    #發現服務使用的應用名(在Demo,將被NimaClient參照)
solon.cloud.nacos:
  server: 192.168.50.95:8848   #nacos服務地址
  username: nacos             #nacos連結賬號
  password: nacos             #nacos連結密碼

宣告HelloService介面,服務註冊方實現介面,伺服器端的工作就完成了

@Mapping("/rpc/")@Remotingpublic class HelloServiceRemoteImp implements HelloService {    @Override    public String hello() {        return "remote: hello";    }}

作為服務發現的使用者端本地app.yml組態檔新增相關nacos的設定資訊如下

solon.app:  namespace: test  group: demo       #設定服務使用的預設組  name: helloapp    #發現服務使用的應用名solon.cloud.nacos:  server: 192.168.50.95:8848   #nacos服務地址  username: nacos             #nacos連結賬號  password: nacos             #nacos連結密碼  config:    load: "test.properties"

測試的使用者端中也是通過註解@NamiClient注入HelloService介面,新增一個測試controller控制器演示

@Controllerpublic class TestController {    //這是遠端的    @NamiClient    HelloService helloService;    @Mapping("/test")    public String test() throws Exception {        helloService.hello();        String temp = helloService.hello();        System.out.println("helloService return"+temp);        return temp + "," + Solon.cfg().get("db1.url");    }}

已啟動服務註冊serverApp和服務發現ClientApp

檢視Nacos服務管理可以看下服務名已經正常註冊了

存取測試地址http://localhost:8080/test,可以看到成功呼叫服務註冊方的方法,也列印從Nacos設定中心的設定項值,至此,已經實現基於Nacos的設定、服務註冊和發現的基本功能。

**本人部落格網站 **IT小神 www.itxiaoshen.com