• Solon2 与 Spring Boot 的区别


    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 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 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> 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(){
        }
    }
    
  • 相关阅读:
    ZCMU--5162: 奖牌颁发
    python-(2)变量、基础数据类型、注释和简单用户交互
    弘玑Cyclone2022年产品发布会:人人可用的数字化工作平台——弘玑工作易
    关系数据库是个啥
    关于Spring的两三事:万物之始—BeanDefinition
    绿色先行——建行江门市分行支助力补齐“三农”金融服务短板
    9.0、C语言——初识指针
    看一下链表结构
    MySQL使用GROUP BY分组,根据分组值对应,为null的分组展示为0或自定义数值
    防火墙的技术(NAT NAT地址池 升级版本 ) 第二一课
  • 原文地址:https://www.cnblogs.com/noear/p/17170438.html