Solon 特性簡集,相較於 Springboot 有什麼區別?
阿新 • • 發佈:2020-12-28
Solon 是一個類似Springboot的微型開發框架,也是一個不基於Servlet的開發框架。專案從2018年啟動以來,參考過大量前人作品;歷時兩年,3500多次的commit;核心保持0.1m的身材,超高的Web跑分,良好的使用體驗。
Solon 強調:剋制 + 簡潔 + 開放的原則;力求:更小、更快、更自由的體驗。
所謂更小:
核心0.1m,最小Web開發單位0.2m(相比Springboot專案包,小到可以乎略不計了)。
具使用者反映,某些專案切換到Solon後,可以縮減到原來10%的包大小。
所謂更快:
本機helloworld測試,啟動最快可達0.09s,Qps可達12萬之多。可參考:《helloworld_wrk_test
所謂更自由:
- 程式碼操控自由:
// 除了注入模式之外,還可以按需手動 // //手動獲取配置 String userName = Solon.cfg().get("user.name"); Properties dbcfg = Solon.cfg().getProp("db"); //手動獲取容器裡的Bean UserService userService = Aop.get(UserService.class); //手動監聽http post請求 Solon.global().post("/user/update", x-> userService.updateById(x.paramMap()));
- 框架選擇自由:
可以用solon-web這樣的快速開發整合包。也可以按專案需要選擇不同的外掛組裝,比如:為非Solon專案新增solon.boot.jlhttp,0.2m即可讓專案實現http+rpc開發;還可以用MVC開發Socket應用。
特性簡集:
1、與Springboot的常用註解比較
Solon 1.2.12 | 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”) | 非單例 |
@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 託管的 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開發模式
//[服務端]
@Mapping(value = "/demoe/rpc", method = MethodType.SOCKET)
@Component(remoting = true)
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開發模式(基於上例擴充套件)
//[服務端]
@Mapping(value = "/demoe/rpc", method = MethodType.SOCKET)
@Component(remoting = true)
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更簡潔(Solon 也可以用Feign)
//[定義介面],一般情況下不需要加任何註解
//
public interface UserService {
UserModel getUser(Integer userId);
}
//[服務端] Component.remoting = true,即為元件開啟遠端服務
//
@Mappin("user")
@Component(remoting = true)
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專案地址
- gitee: https://gitee.com/noear/solon
- github: https://github.com/noear/solon