Solon

与 Spring Boot 的区别

</> markdown

1、与 Springboot 的常用注解比较

Solon 2.5.8Springboot 2.7.16 / 3.1.4说明
@Inject *@Autowired注入Bean(by type)
@Inject("name")@Qualifier+@Autowired注入Bean(by name)
@Inject("${name}")@Value("${name}") + @ConfigurationProperties(prefix="name")注入配置
@Singleton@Scope(“singleton”)单例(Solon 默认是单例)
@Singleton(false)@Scope(“prototype”)非单例
@Import@Import + @ComponentScan导入组件(一般加在启动类上)
@Import@PropertySource导入属性源(一般加在启动类上)
@Configuration@Configuration配置类
@Bean@Bean配置Bean
@Condition@ConditionalOnClass + @ConditionalOnProperty配置条件
@Component@Component,@Service,@Dao,@Repository托管组件
@Import@TestPropertySource导入测试属性源
@Rollback@TestRollback执行测试回滚
LifecycleBeanInitializingBean + DisposableBean组件初始化和销毁
Solon 2.5.8Java EE / Jakarta
LifecycleBean::start
或 @Init 注解
@PostConstruct构造完成并注入后的初始化
LifecycleBean::stop@PreDestroy组件销毁
  • 注1:Method@Bean,只执行一次(只在 @Configuration 里有效)
  • 注2:@Inject 的参数注入,只在 Method@Bean 上有效
  • 注3:@Inject 的类注入,只在 @Configuration类 上有效
  • 注4:@Import 只在 主类上 或者 @Configuration类 上有效

2、与 Springboot 的动态代理差别

Solon 只对组件的 public 函数进行代理

3、与 Spring Mvc 的比较

  • 注解方面的区别
Solon 2.5.8Springboot 2.7.16 / 3.1.4说明
@Controller@Controller,@RestController控制器类
@Remoting 远程控制器类(即 Rpc 服务端)
@Mapping@RequestMapping,@GetMapping...映射(并不完全对等)
@Param@RequestParam请求参数(并不完全对等)
@Header@RequestHeader请求头
@Body@RequestBody请求体(并不完全对等)
@Cookie@CookieValue请求小饼
@Path@PathVariable请求路径变量
  • 请求参数的区别

Solon 不支持 ?user[name]=1&ip[0]=a&ip[1]=b 这种风格的参数注入(它算是 spring mvc 特有的了)。复杂格式建议采用 json

  • 映射的区别

Solon 的 @Mapping 函数不支持多路径的映射,且只限 public 函数。可以通过网关,为一批 action 添加不同的地址前缀。

  • 控制器继承的区别

Solon 控制器继承时,支持基类的公有 @Mapping 函数

  • 网关

Solon 有个 Gateway 类,相当于二级路由器。可以给一批控制器安排不同的地址和关口策略。

4、重要的区别,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.5.8Springboot 2.7.16 / 3.1.4说明
ContextHttpServletRequest + HttpServletResponse请求上下文
SessionStateHttpSession请求会话状态类
UploadedFileMultipartFile文件上传接收类
DownloadedFile 文件下载输出类
ModelAndViewModelAndView模型视图输出类
  • Solon 适配有:jdkhttp、jlhttp、smarthttp、jetty、undertow、netty、websocket 等各种通讯容器。

5、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 implements LifecycleBean{
    @Inject
    private A a;
    
    @Inject
    private B b;
    
    //@Override
    //public void start(){
    //    //Solon 的注入是异步的。想要对注入的 bean 进行实始化,需要借用 LifecycleBean
    //}
}

或者,可以用 @Configuration + @Bean 进行构建。

6、Solon 可以更自由获取配置

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

7、Solon 配置注给结构体

@Inject("${user.config}")  
@Configuration
public class UserConfig{
    public String name;
    public List<String> tags;
    ...
}

//别处可以注入复用
@Inject
UserConfig userConfig;

8、与 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);
    }
}

9、与 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";
    }
}

10、基于标签管理的缓存支持 @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);
    }
}

11、相似的 @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

12、支持数据渲染(或输出格式化)的自我控制支持

  • 定制特定场景的控制器基类,负责统一格式化输出
//示例:定制统一输出控制基类,并统一开启验证
//
@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.render(obj);
            }
        }
    }
}

13、不基于 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,我把你过滤了");
    }
}

14、专属 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";
    }
}

15、Solon 的加强版 Spi 扩展机制 - 具备可编程性

  • 新建模块,并实现Plugin接口(以增加 @AuthLogined 注解支持为例)
public class XPluginImpl implements Plugin {
    @Override
    public void start(AppContext context) {
        context.beanInterceptorAdd(AuthLogined.class, new LoginedInterceptor());
    }
}
  • 增加配置文件
src/main/resources/META-INF/solon/solon.auth.properties
  • 增加配置内容,打包发布即可
solon.plugin=org.noear.solon.auth.integration.XPluginImp

16、Solon 内部的事件总线 EventBus 的妙用

  • 通过事件总线收集异常
//[收集异常](不建议业务使用)
EventBus.publish(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.publish(builder);

17、Ioc/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().beanInterceptorAdd(Tran.class, new TranInterceptor(), 120);

//效果
@Component
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(){
    }
}