Solon 特性簡集,相較於 Springboot 有什么區別?


Solon 是一個輕量級的Java基礎開發框架。借簽了很多前人的成果,吸收了很多新理念。內核僅0.1m大小,超高的Qps跑分,體驗類似Springboot。Solon 強調:克制 + 簡潔 + 開放的原則;力求:更小、更快、更自由的體驗。

所謂更小:

內核0.1m,最小Web開發單位0.2m(相比Springboot項目包,小到可以乎略不計了)。

具用戶反映,某些項目切換到Solon后,可以縮減到原來10%的包大小。

所謂更快:

本機helloworld測試,啟動最快可達0.1s,Qps可達12萬之多。可參考:《helloworld_wrk_test》。

所謂更自由:

  • 代碼操控自由:
// 除了注解模式之外,還可以按需手動
//
//手動獲取配置(Props 為 Properties 增強版)
Props db = Solon.cfg().getProp("db");

//手動獲取容器里的Bean
UserService userService = Aop.get(UserService.class);

//手動監聽http post請求
Solon.global().post("/user/update", x-> userService.updateById(x.paramMap()));

//手動添加個RPC服務
Solon.global().add("/rpc/", HelloService.class, true);

//手動獲取一個RPC服務消費端
HelloService helloService = Nami.builder().create(HelloService.class);

//手動為容器添加組件
Aop.wrapAndPut(DemoService.class);
  • 框架選擇自由:

可以用 solon-web 這樣的快速開發集成包。也可以按項目需要選擇不同的插件組裝,比如:為非 Solon 項目添加 solon.boot.jlhttp,0.2m即可讓項目實現 http+rpc 開發;還可以用 MVC 開發 Socket 應用。

特性簡集:

1、與 Springboot 的常用注解比較

Solon 1.5.1 Springboot 2.3.3 說明
@Inject * @Autowired 注入Bean(by type)
@Inject("name") @Qualifier+@Autowired 注入Bean(by name)
@Inject("${name}") @Value("${name}") 注入配置
@Component @Component 托管組件
@Singleton @Scope(“singleton”) 單例(Solon 默認是單例)
@Singleton(false) @Scope(“prototype”) 非單例
@Import @Import + @ComponentScan 導入或掃描組件
@Init * @PostConstruct 構造完成並注入后的初始化
@Configuration @Configuration 配置類
@Bean @Bean 配置組件
@Mapping @RequestMapping,@GetMapping... 映射
@Param @RequestParam 請求參數
@Controller @Controller,@RestController 控制器類
@Service @Service 服務類
@Dao @Dao 數據訪問類
  • Solon 的 @Inject 算是: Spring 的@Value、@Autowired、@Qualifier 三者的結合,但又不完全等價
  • Solon 的 @Import 同時有導入和掃描的功能
  • Solon 托管的 Bean 初始化順序:new() - > @Inject - > @Init
  • 注1:@Inject 的參數注入,只在 Method@Bean 上有效
  • 注2:@Inject 的類型注入,只在 @Configuration 類上有效

2、重要的區別,Solon 不是基於 Servlet 的開發框架

  • 與 Springboot 相似的體驗,但使用 Context 包裝請求上下文。Helloworld 效果如下:
@Controller
public class App{
    public static void main(String[] args){
        Solon.start(App.class, args);
    }
    
    @Inject("${app.name}")
    String appName;
  
    @Mapping("/")
    public Object home(Context c, @Param(defaultValue="noear") String name){
        return  appName + ": Hello " + name;  
    }
}

3、與 Springboot 相似的事務支持 @Tran

  • 采用 Springboot 相同的事件傳播機制及隔離級別
@Controller
public class DemoController{
    @Db
    BaseMapper<UserModel> userService;
    
    @Tran
    @Mapping("/user/update")
    public void udpUser(long user_id, UserModel user){
        userService.updateById(user);
    }
}

4、與 Springboot 不同的較驗方案 @Valid

  • Solon 的方案更側重較驗參數(及批量較驗),且強調可見性(即與處理函數在一起)
@Valid  
@Controller
public class DemoController {

    @NoRepeatSubmit
    @NotNull({"name", "icon", "mobile"})
    @Mapping("/valid")
    public String test(String name, String icon, @Pattern("13\\d{9}") String mobile) {
        return "OK";
    }

    @Whitelist
    @Mapping("/valid/test2")
    public String test2() {
        return "OK";
    }
}

5、基於標簽管理的緩存支持 @Cache,與 Springboot 略有不同

  • 基於標簽管理,避免不必要的KEY沖突
@Controller
public class DemoController{
    @Db
    BaseMapper<UserModel> userService;
    
    @CacheRemove(tags = "user_${user_id}")
    @Mapping("/user/update")
    public void udpUser(int user_id, UserModel user){
        userService.updateById(user);
    }
    
    @Cache(tags = "user_${user_id}")
    public UserModel getUser(int user_id){
        return userService.selectById(user_id);
    }
}

6、具備語義特性的 Bean 定義,實現更多可能性

  • 通過語義特性,為 Bean 增加特性描述;從而實現一些附加的能力
//
// 一個數據主從庫的示例
//
@Configuration
public class Config {
    //申明 db2 是 db1 為的從庫
    @Bean(value = "db1", attrs = { "slaves=db2" })
    public DataSource db1(@Inject("${test.db1}") HikariDataSource dataSource) {
        return dataSource;
    }

    @Bean("db2")
    public DataSource db2(@Inject("${test.db2}") HikariDataSource dataSource) {
        return dataSource;
    }
}

7、支持數據渲染(或輸出格式化)的自我控制支持

  • 定制特定場景的控制器基類,負責統一格式化輸出
//示例:定制統一輸出控制基類,並統一開啟驗證
//
@Valid
public class ControllerBase implements Render {
    @Override
    public void render(Object obj, Context ctx) throws Throwable {
        if (obj == null) {
            return;
        }

        if (obj instanceof String) {
            ctx.output((String) obj);
        } else {
            if (obj instanceof ONode) {
                ctx.outputAsJson(((ONode) obj).toJson());
            } else {
                if (obj instanceof UapiCode) {
                    //此處是重點,把一些特別的類型進行標准化轉換
                    //
                    UapiCode err = (UapiCode) obj;
                    obj = Result.failure(err.getCode(), UapiCodes.getDescription(err));
                }

                if (obj instanceof Throwable) {
                    //此處是重點,把異常進行標准化轉換
                    //
                    Throwable err = (Throwable) obj;
                    obj = Result.failure(err.getMessage());
                }

                ctx.outputAsJson(ONode.stringify(obj));
            }
        }
    }
}

8、不基於 Servlet,卻很有 Servlet 親和度。當使用 servlet 相關的組件時(也支持jsp + tld)

  • 支持 ServletContainerInitializer 配置
@Configuration
public class DemoConfiguration implements ServletContainerInitializer{
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        //...
    }
}
  • 支持 Servlet api 注解
@WebFilter("/demo/*")
public class DemoFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
        res.getWriter().write("Hello,我把你過濾了");
    }
}

9、為服務開發而生的 SockeD 組件,實現 http, socket, websocket 相同的信號處理。

  • 支持 MVC+RPC 開發模式
//[服務端]
@Socket
@Mapping("/demoe/rpc")
@Remoting
public class HelloRpcServiceImpl implements HelloRpcService {
    public String hello(String name) {
        return "name=" + name;
    }
}

//[客戶端] 
var rpc = SocketD.create("tcp://localhost:28080", HelloRpcService.class);
System.out.println("RPC result: " + rpc.hello("noear"));
  • 支持單鏈接雙向 RPC 開發模式(基於上例擴展)
//[服務端]
@Socket
@Mapping("/demoe/rpc")
@Remoting
public class HelloRpcServiceImpl implements HelloRpcService {
    public String hello(String name) {
        //
        //[服務端] 調用 [客戶端] 的 rpc,從而形成單鏈接雙向RPC
        //
        NameRpcService rpc = SocketD.create(Context.current(), NameRpcService.class);
        name = rpc.name(name);
        
        
        return "name=" + name;
    }
}
  • 支持消息發送+監聽開發模式
//[服務端]
@ServerEndpoint
public class ServerListener implements Listener {
    @Override
    public void onMessage(Session session, Message message) {
        if(message.flag() == MessageFlag.heartbeat){
            System.out.println("服務端:我收到心跳");
        }else {
            System.out.println("服務端:我收到:" + message);
            //session.send(Message.wrapResponse(message, "我收到了"));
        }
    }
}

//[客戶端]
var session = SocketD.createSession("tcp://localhost:28080");
session.send("noear");
//session.sendAndCallback("noear", (rst)->{});   //發送並異步回調
//var rst = session.sendAndResponse("noear");   //發送並等待響應

System.out.println(rst);
  • 支持消息訂閱開發模式
//[客戶端]
@ClientEndpoint(uri = "tcp://localhost:28080")
public class ClientListener implements Listener {
    @Override
    public void onMessage(Session session, Message message) {
        //之后,就等着收消息
        System.out.println("客戶端2:我收到了:" + message);
    }
}

10、專屬 RPC 客戶端組件:Nami

  • 類似於 Springboot + Feign 的關系,但 Nami 更簡潔且支持 socket 通道( Solon 也可以用 Feign )
//[定義接口],一般情況下不需要加任何注解
//
public interface UserService {
    UserModel getUser(Integer userId);
}

//[服務端] @Remoting,即為遠程組件
//
@Mappin("user")
@Remoting
public class UserServiceImpl implements UserService{
    public UserModel getUser(Integer userId){
        return ...;
    }
}


//[消費端]
//
@Mapping("demo")
@Controller
public class DemoController {

    //直接指定服務端地址
    @NamiClient("http://localhost:8080/user/")
    UserService userService;

    //使用負載
    @NamiClient("local:/user/")
    UserService userService2;

    @Mapping("test")
    public void test() {
        UserModel user = userService.getUser(12);
        System.out.println(user);

        user = userService2.getUser(23);
        System.out.println(user);
    }
}

/**
 * 定義一個負載器(可以對接發現服務)
 * */
@Component("local")
public class RpcUpstream implements LoadBalance {
    @Override
    public String getServer() {
        return "http://localhost:8080";
    }
}

11、Solon 的加強版SPI擴展機制 - 以增加注解為例

  • 1.新建個模塊,實現Plugin接口(以增加@Service注解支持為例)
public class XPluginImp implements Plugin {
    @Override
    public void start(SolonApp app) {
        Aop.context().beanBuilderAdd(Service.class, (clz, bw, anno) -> {
            bw.proxySet(BeanProxyImp.global());

            Aop.context().beanRegister(bw, "", true);
        });
    }
}
  • 2.增加配置文件
src/main/resources/META-INF/solon/solon.extend.aspect.properties
  • 3.增加配置內容,打包發布即可
solon.plugin=org.noear.solon.extend.aspect.XPluginImp

12、Solon內部的事件總線EventBus的妙用

  • 通過事件總線收集異常
//[收集異常]
EventBus.push(err);

//[訂閱異常]
EventBus.subscribe(Throwable.class,(event)->{
            event.printStackTrace();
        });
//或通過SolonApp訂閱
app.onEvent(Throwable.class, (err)->{
            err.printStackTrace();
        });
//或通過組件訂閱        
@Component
public class ErrorListener implements EventListener<Throwable> {
    @Override
    public void onEvent(Throwable err) {
        err.printStackTrace();
    }
}        
        
  • 通過事件總線擴展配置對象
//
// 插件開發時,較常見
//
SqlManagerBuilder builder = new SqlManagerBuilder(ds);
EventBus.push(builder);

附:Solon項目地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM