• Solon 特性简集,相较于 Springboot 有什么区别?


    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项目地址

  • 相关阅读:
    web--ajax--json
    4.26
    4.25
    4.23
    4.22
    4.20
    4.19
    4.18
    4月问题总结章
    4.17
  • 原文地址:https://www.cnblogs.com/noear/p/14200892.html
Copyright © 2020-2023  润新知