对比 Spring ,Solon 在实现上又有什么差异?

一. 前言

Solon 是 国产的 Java 企业级应用开发框架 ,算是国内在体系和生态上都比较全面的框架了。

这个框架在 内存占用 \ 启动速度上都很亮眼, 据官方数据 ,整体性能对比 Spring 提高了多倍。

这一篇就来感受一下这个框架 ,并且从源码的角度上 ,来感受一下这个框架的种种,对比一下 Spring ,它又优化了什么。

二. 基础使用

Maven 依赖

xml 复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>org.noear</groupId>
    <artifactId>solon-parent</artifactId>
    <version>3.2.0</version>
  </parent>

  <groupId>org.example</groupId>
  <artifactId>solonSimpleDemo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>solonSimpleDemo</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.noear</groupId>
      <artifactId>solon-web</artifactId>
    </dependency>
  </dependencies>

  <build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
      <plugin>
        <!-- 引入打包插件 -->
        <groupId>org.noear</groupId>
        <artifactId>solon-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

启动类

java 复制代码
import org.noear.solon.Solon;
public class App {
    public static void main(String[] args) {
        Solon.start(App.class, args);
    }
}

Rest 接口

java 复制代码
@Controller
public class HelloController {
    /**
     * 这是直接返回值
     * */
    @Mapping("/")
    public String hello() {
        return "Hello world!";
    }

    /**
     * 这是返回个对象(以json形式)
     * */
    @Mapping("/json")
    public Map hello_json() {
        Map<String,Object> map = new HashMap<>(); //实体也ok
        map.put("message", "Hello world!");

        return map;
    }

}

阶段总结 :

  • 从最简单的使用层面 ,可以看到和Spring区别不大 ,或者说是沿用统一的规范
  • 启动时间确实更快了 ,调用接口等功能也没有太大的问题

三. 深入原理

3.1 Rest 接口的使用

我们知道 ,Spring 的 MVC 功能是基于 Servlet 的 ,其根本的特性是通过 DispatchServlet 接收来自于外部的各种请求。

而 Solon 里面要简单很多 ,其默认核心的依赖是 solon-boot-smarthttp ,而从官方的依赖列表里面 ,Solon 的 HTTP 层面还支持 : jdkhttpjettyundertow.

我们来简单学习一下这四个框架 :

框架 实现原理 主要特性 性能指标(线程模型 & 吞吐) 适用场景
SmartHttp (SmartBoot) 基于 Java NIO 异步非阻塞,事件驱动,利用 Epoll/kqueue - 纯异步 NIO 架构 - 轻量高性能 - 灵活的协议扩展 - 支持全链路异步处理 高效纯异步线程模型 支持大量并发连接 低延迟,高吞吐(百万级 TPS,视环境不同) 高并发微服务、游戏、实时通信
JDK HttpServer Java 自带,基于线程池的阻塞 IO 实现,简单 HTTP 服务器 - 内置 JDK,无需额外依赖 - 简单易用 - 仅支持基础 HTTP 功能 线程池阻塞模型 低并发适用 吞吐和延迟一般,不适合高并发 简单应用、快速开发、测试调试
Jetty 基于 Java NIO 异步非阻塞,Servlet 容器,多线程队列 - 完整 Servlet 规范支持 - 轻量灵活 - 支持 HTTP/2, WebSocket - 丰富扩展和生态整合 异步线程池+请求队列 线程复用良好 性能优异,百万级 TPS(硬件及配置相关) 传统中大型 Web 应用,Servlet 生态
Undertow 基于 Java NIO,借助 XNIO 框架实现轻量异步非阻塞 - 极简轻量 - 支持 Servlet 3.1 异步 API - HTTP/2 支持 - 内置 WebSocket 高效事件驱动异步 线程数少,减少上下文切换 吞吐量高,性能与 Jetty 相似或略优 微服务、轻量 Web 服务及嵌入式服务器

solon HTTP 流程的核心流程处理 :

这是 Spring MVC 的核心流程 :

阶段总结 :

Spring 默认带的 Tomcat 有问题吗 ? 一点问题没有 , Tomcat 的性能是够的 , 这一点已经有大量案例了 。

但是从上面流程可以对比出来 ,Spring 的 Web 处理太完备了 ,以至于一个简单的 HTTP 请求链过长 ,其中可能多做了50% 的无意义操作.

Solon 的处理很原生 ,主要在最底层的框架上面做了一些必要的封装。简单请求里面 ,从SmartHttp 透传请求 ,到业务方接收到请求 ,整体的处理栈差不多在10层左右

3.2 容器的启动 ( = ApplicationContext)

Solon 的启动是从 Solon.classstart 方法开始。重点是在 SolonApp 中 ,其中主要可以分为3步 :

  • S1 : 创建一个 SolonApp 对象用于加载容器等信息
  • S2 : SolonApp.init(initialize) 进行内部的初始化 ,主要是配置
    • 配置就像Spring里面自动装配的内容 ,也就是类似于 Spring-mybatis-starter 这样,用来导入插件
    • 比如 Solon 里面就是 mybatis-sqlhelper-solon-plugin
  • S3 : SolonApp.run 加载插件和 Bean

Plugin 的扫描处理 :

java 复制代码
protected void pluginScan(List<ClassLoader> classLoaders) {
    for (ClassLoader classLoader : classLoaders) {
        //扫描配置
        PluginUtil.scanPlugins(classLoader, pluginExcludeds, plugins::add);
    }

    //扫描主配置
    PluginUtil.findPlugins(AppClassLoader.global(), this, pluginExcludeds, plugins::add);

    //插件排序
    Collections.sort(plugins);
}

Run 方法中对 Bean 的扫描起点

java 复制代码
//2.1.通过注解导入bean(一般是些配置器)
beanImportTry();

//2.2.通过源扫描bean
if (source() != null && enableScanning()) {
    context().beanScan(source());
}

3.2 Bean 的管理 (= IOC)

Bean 真正扫描的起点在于 :AppContext # beanScan 部分 :

阶段总结 :

3.3 AOP 部分

这部分有点意思 ,Solon 对 AOP 进行了很直接的简化 ,简化为了两个注解 ,使用上反而会复杂点 :

  • @Addition : 局部对 Context 进行过滤 , 基于 Filter 进行代理
    • 更多是 "前置""后置" 逻辑
    • 无法通过它来控制目标方法的执行和修改返回值
    • 用于快速加挂"额外操作(Addition) "
  • @Around : 局部对 Bean 进行拦截 ,基于 MethodInterceptor
    • 以在执行前后插入逻辑,还可以决定是否执行目标方法、修改返回值或参数

所以 ,其实 @Around 更像之前 Spring 的 AOP 流程。由于 Solon 相当于省略了整个 AOP 的自动化处理 ,更接近于对象代理 ,所以配置起来会更复杂点 :

java 复制代码
// S1 : 准备一个注解 
@Target({ElementType.METHOD, ElementType.TYPE}) //支持加在类或方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface AopDemo {
}

// S2 : 准备拦截器
@Slf4j
public class LogInterceptor  implements Interceptor {
    @Override
    public Object doIntercept(Invocation inv) throws Throwable {
        System.out.println("拦截器执行了");
        return inv.invoke();
    }
}

// S3 : 启动类里面注入
public static void main(String[] args) {
    Solon.start(App.class, args, app->{
        app.context().beanInterceptorAdd(AopDemo.class, new LogInterceptor());
    });
}

源码层面 :

核心也是分为两个部分 :

  • S1 : 创建和注入Bean的时候 ,提供的是一个 BeanInvocationHandler 代理对象
  • S2 : 外部调用 AOP 方法时候 ,首先调用的是代理对象 ,再由代理对象调用到方法里面
java 复制代码
// S1 : 创建代理对象 
// C- AppContext
if (tryProxy) {
    //是否需要自动代理
    enableProxy = enableProxy || beanInterceptorHas(bw.clz());

    if (enableProxy) {
        ProxyBinder.global().binding(bw);
    }
}

// S2 : 调用代理的方法
C- BeanInvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
    // 判断是否存在自定义处理器,如果没有则调用默认的执行流程  
    if (this.handler == null) {  
         // 允许访问私有方法  
         method.setAccessible(true);

         // 通过上下文获取对应类的对应方法的 MethodWrap(或类似封装)  
         // bw.context() 表示当前的上下文环境  
         // bw.rawClz() 获取原始类(目标类)  
         // methodGet 方法根据类和方法反射信息获取相应的包装MethodWrap对象  
         // invokeByAspect 表示通过AOP切面逻辑来执行这个方法,传入 this.bean 作为目标实例,args 作为参数 
         Object result = this.bw.context()  
                              .methodGet(this.bw.rawClz(), method)  
                              .invokeByAspect(this.bean, args);  

         return result;  
    }
}

Spring AOP 做了什么 ?

  • 这是之前 Spring AOP 的主流程梳理 ,流程相对而言长了太多

总结

时间精力有限 ,整体代码没有太深入的看了。整体使用下来 ,如果项目比较小 ,资源也紧张 ,Solon 是一个很值得考虑的框架。

  • 使用上 : 不适合改造旧项目 ,但是新项目使用没什么压力 ,文档丰富,使用不需要太大的难度
  • 深度问题 : 由于对整个流程做了极大的简化 ,所以出问题基本上可以通过读源码解决
  • 性能和内存占用现在看到的会有明显的提升 ,但是改造大的生产项目太难了 ,没办法进行比较

个人的一点思路 :

Spring 是全面可靠的 ,这点无容置疑 , 所以生产级的大项目 ,相信还是会以 Spring 为主流。

但是个人的一些小 Demo ,和一些资源更少的场景 ,就可以考虑使用 Solon了 ,不用自己造轮子 ,也不会浪费 Spring 的性能。

关于设计的思想 :

核心源码大致走了一遍 ,Solon 本身设计上是有一些明确的思路的 :

  • AOP 更偏向于显示 的声明(没试过能不能通过包扫描的方式)
    • 其实写了这么多年代码 ,Spring AOP 的使用率并不高,更多的是Spring自己在用 ,业务上用到也主要是日志级别的处理
  • Spring 的容器确实过于复杂了 ,各种 ListenerAwarePostProcessor , 很全面 ,但是也太难了、
    • 容器的处理 Solon 只有两个核心 : Plugin (组件) + Bean 加载
  • IOC 层面也简单很多 ,这里就不细说了 ,看过 Spring 源码的都能懂

最后的最后 ❤️❤️❤️👇👇👇

相关推荐
鼠鼠我捏,要死了捏2 小时前
深入解析Java NIO多路复用原理与性能优化实践指南
java·性能优化·nio
ningqw2 小时前
SpringBoot 常用跨域处理方案
java·后端·springboot
你的人类朋友2 小时前
vi编辑器命令常用操作整理(持续更新)
后端
superlls2 小时前
(Redis)主从哨兵模式与集群模式
java·开发语言·redis
胡gh2 小时前
简单又复杂,难道只能说一个有箭头一个没箭头?这种问题该怎么回答?
javascript·后端·面试
一只叫煤球的猫3 小时前
看到同事设计的表结构我人麻了!聊聊怎么更好去设计数据库表
后端·mysql·面试
uzong3 小时前
技术人如何对客做好沟通(上篇)
后端
叫我阿柒啊4 小时前
Java全栈工程师面试实战:从基础到微服务的深度解析
java·redis·微服务·node.js·vue3·全栈开发·电商平台
颜如玉4 小时前
Redis scan高位进位加法机制浅析
redis·后端·开源
Moment4 小时前
毕业一年了,分享一下我的四个开源项目!😊😊😊
前端·后端·开源