关于springboot: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("13d{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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理