Solon2 與 Spring Boot 的區別

帶刺的坐椅發表於2023-03-02

1、與 Springboot 的常用註解比較

Solon 2.2.0 Springboot 2.7.8 說明
@Inject * @Autowired 注入Bean(by type)
@Inject("name") @Qualifier+@Autowired 注入Bean(by name)
@Inject("${name}") @Value("${name}") 注入配置
@Singleton @Scope(“singleton”) 單例(Solon 預設是單例)
@Singleton(false) @Scope(“prototype”) 非單例
@Import @Import + @ComponentScan 配置元件匯入或掃描(一般加在啟動類上)
@PropertySource @PropertySource 配置屬性源(一般加在啟動類上)
@Configuration @Configuration 配置類
@Bean @Bean 配置Bean
@Condition @ConditionalOnClass + @ConditionalOnProperty 配置條件
@Controller @Controller,@RestController 控制器類
@Remoting 遠端控制器類(即 Rpc 服務端)
@Mapping ... @RequestMapping,@GetMapping... 對映
@Param @RequestParam 請求引數
@Header @RequestHeader 請求頭
@Body @RequestBody 請求體
@Cookie @CookieValue 請求Cookie
@Component @Component 普通託管元件
@ProxyComponent @Service,@Dao,@Repository 代理託管元件
@Init * @PostConstruct 元件構造完成並注入後的初始化
@TestPropertySource @TestPropertySource 配置測試屬性源
@TestRollback @TestRollback 執行測試回滾
  • Solon 的 @Inject 算是: Spring 的@Value、@Autowired、@Qualifier 三者的結合,但又不完全等價
  • Solon 的 @Import 同時有匯入和掃描的功能
  • Solon 的 Bean 生命週期:new() - > @Inject -> afterInjection()- > start() -> stop()
  • 注1:Method@Bean,只執行一次(只在 @Configuration 裡有效)
  • 注2:@Inject 的引數注入,只在 Method@Bean 上有效
  • 注3:@Inject 的類注入,只在 @Configuration類 上有效
  • 注4:@Import 只在 主類上 或者 @Configuration類 上有效

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

  • 與 Springboot 相似的體驗,但使用 Context 包裝請求上下文(底層為:Context + Handler 架構)。Helloworld 效果:
@SolonMain
public class App{
    public static void main(String[] args){
        Solon.start(App.class, args);
    }
}

@Controller
public class Demo{
    @Inject("${app.name}")
    String appName;
  
    @Mapping("/")
    public Object home(String name){
        return  appName + ": Hello " + name;  
    }
}
  • 與 Servlet 常見類比較
Solon 2.2.0 Springboot 2.7.8 說明
Context HttpServletRequest + HttpServletResponse 請求上下文
SessionState HttpSession 請求會話狀態類
UploadedFile MultipartFile 檔案上傳接收類
DownloadedFile 檔案下載輸出類
ModelAndView ModelAndView 模型檢視輸出類
  • Solon 適配有:jdkhttp、jlhttp、smarthttp、jetty、undertow、netty、websocket 等各種通訊容器。

3、Solon 不支援建構函式注入與屬性設定注入

  • 不支援的:
@Component
public class Demo{
    private A a;
    private B b;
    
    public Demo(@Inject A a){
        this.a = a;
    } 
    
    public void setB(@Inject B b){
        this.b = b;
    }
}
  • 支援的:
@Component
public class Demo{
    @Inject
    private A a;
    
    @Inject
    private B b;
    
    //@Init
    //public void initDo(){
    //    //Solon 的注入是非同步的。想要對注入的 bean 進行實始化,需要借用 @Init 函式
    //}
}

或者,可以用 @Configuration + @Bean 進行構建。

4、Solon 可以更自由獲取配置

@Component
public class Demo{
    //注入配置
    @Inject("${user.name}")
    private String userName;
    
    //手動獲取配置
    private String userName = Solon.cfg().get("user.name");
}

5、Solon 的 @Component 與 @ProxyComponent 是有區別的

  • @Component 註解的元件,不會被動態代理
    • 不支援攔截處理
    • 支援函式被註解提取
    • 支援形態提取
  • @ProxyComponent 註解的元件,會被動態代理。由 solon.proxy 提供能力實現
    • 支援攔截處理

各有分工,算有是“剋制”的體現。

6、與 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);
    }
}

7、與 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";
    }
    
    @Mapping("/valid/test3")
    public String test3(@Validated UserModel user) {
        return "OK";
    }
}

8、基於標籤管理的快取支援 @Cache,與 Springboot 略有不同

  • 支援Key的快取管理。同時增加了基於標籤的快取管理,避免不必要的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);
    }
}

9、相似的 @Bean 設計

  • 相似的特性。且,需與 @Configuration 協同使用
//
// 一個資料主從庫的示例
//
@Configuration
public class Config {
    @Bean(name = "db1", typed = true)
    public DataSource db1(@Inject("${test.db1}") HikariDataSource dataSource) {
        return dataSource;
    }

    @Bean("db2")
    public DataSource db2(@Inject("${test.db2}") HikariDataSource dataSource) {
        return dataSource;
    }
}
  • 使用 @Bean(typed=true) 做為某種型別的預設Bean

10、支援資料渲染(或輸出格式化)的自我控制支援

  • 定製特定場景的控制器基類,負責統一格式化輸出
//示例:定製統一輸出控制基類,並統一開啟驗證
//
@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));
            }
        }
    }
}

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

  • 支援 Servlet 請求與響應物件注入
@Mapping("/demo/")
@Controller
public class DemoController {
    @Mapping("hello")
    public void hello(HttpServletRequest req, HttpServletResponse res){
    }
}
  • 支援 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,我把你過濾了");
    }
}

12、為服務開發而生的 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);
    }
}

13、專屬 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(name="local", path="/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";
    }
}

14、Solon 的加強版 Spi 擴充套件機制 - 具備可程式設計性

  • 新建模組,並實現Plugin介面(以增加 @ProxyComponent 註解支援為例)
public class XPluginImp implements Plugin {
    @Override
    public void start(AopContext context) {
        context.beanBuilderAdd(ProxyComponent.class, (clz, bw, anno) -> {
            BeanProxy.binding(bw);
        });
    }
}
  • 增加配置檔案
src/main/resources/META-INF/solon/solon.aspect.properties
  • 增加配置內容,打包釋出即可
solon.plugin=org.noear.solon.aspect.XPluginImp

15、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);

16、Aop 擴充套件,掃描一次 + 註冊處理(也是啟動快的原因之一)

  • 註冊‘構建器’處理。以註冊 @Controller 構建器為例:
Solon.context().beanBuilderAdd(Controller.class, (clz, bw, anno) -> {
    //內部實現,可參考專案原始碼 //構建器,可以獲取型別並進行加工
    new HandlerLoader(bw).load(Solon.global());
});

//效果
@Controller
public class DemoController{
}
  • 註冊'注入器'處理。以註冊 @Inject 注入器為例:
Solon.context().beanInjectorAdd(Inject.class, ((fwT, anno) -> {
    //內部實現,可參考專案原始碼 //注入器,可以根據目標生成需要的資料並賦值
    beanInject(fwT, anno.value(), anno.autoRefreshed());
}));

//效果
@Controller
public class DemoController{
    @Inject
    UserService userService;
}
  • 註冊'攔截器'處理。以註冊 @Tran 攔截器為例:
//攔截器,可以獲取執行動作鏈
Solon.context().beanAroundAdd(Tran.class, new TranInterceptor(), 120);

//效果
@ProxyComponent
public class UserService{
    @Tran
    public void addUser(User user){
    }
}
  • 註冊'提取器'處理。以註冊 @CloudJob 提取器為例:
//內部實現,可參考專案原始碼 //提取器,可以提取被註解的函式
Solon.context().beanExtractorAdd(CloudJob.class, CloudJobExtractor.instance);

//效果 //提取器只對元件有效
@Component
public class Job{
    @CloudJob
    public void statUserJob(){
    }
}

相關文章