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<UserModel> 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<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); } }
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<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,我把你过滤了"); } }
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(){ } }