对比 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 源码的都能懂

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

相关推荐
xxjiaz26 分钟前
二分查找-LeetCode
java·数据结构·算法·leetcode
nofaluse1 小时前
JavaWeb开发——文件上传
java·spring boot
爱的叹息1 小时前
【java实现+4种变体完整例子】排序算法中【插入排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格
java·算法·排序算法
爱的叹息2 小时前
【java实现+4种变体完整例子】排序算法中【快速排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格
java·算法·排序算法
6v6-博客2 小时前
2024年网站开发语言选择指南:PHP/Java/Node.js/Python如何选型?
java·开发语言·php
Miraitowa_cheems2 小时前
[Java EE] Spring AOP 和 事务
java·java-ee·aop·spring 事务
Goldchenn2 小时前
kafka服务端和springboot中使用
spring boot·分布式·kafka
光头小小强0072 小时前
致远OA——自定义开发rest接口
java·经验分享·spring·tomcat
淬渊阁2 小时前
Hello world program of Go
开发语言·后端·golang
Pandaconda2 小时前
【新人系列】Golang 入门(十五):类型断言
开发语言·后端·面试·golang·go·断言·类型