共计 7370 个字符,预计需要花费 19 分钟才能阅读完成。
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