Java Web 开发者大概都经历过这种场景——项目跑在 Tomcat 上,某天想换成 Undertow 试试性能,结果发现框架和容器之间有一堆隐式耦合,改完配置还要改代码。Solon 的做法干脆利落:把服务器做成插件,业务代码完全不动,换容器只换一个依赖。
内核 0.3MB,服务器全是插件
Solon 的内核只保留框架最基础的机制——IoC容器、生命周期、插件加载。HTTP 服务器不在内核里,而是以 solon.boot.xxx 插件的形式存在。目前官方提供了 10+ 种服务器插件:
| 插件依赖 | 底层服务器 | 适用场景 |
|---|---|---|
solon.boot.jlhttp |
JlHttp | 超轻量嵌入、微服务侧车 |
solon.boot.jetty |
Jetty | 中等负载、需JSP支持 |
solon.boot.undertow |
Undertow | 高并发、Servlet兼容 |
solon.boot.tomcat |
Tomcat | 传统Java Web、Servlet全特性 |
solon.boot.nettyhttp |
Netty | 极高并发、非Servlet场景 |
solon.boot.smarthttp |
SmartHttp | 轻量高性能 |
solon.boot.websocket |
内置WebSocket | WebSocket优先场景 |
solon.boot.smarthead |
SmartHead | 轻量REST |
solon.boot.rsocket |
RSocket | 双向流通信 |
内核不依赖任何一个服务器实现。这意味着你的 Controller、Service、Filter 写完之后,底层跑的是 JlHttp 还是 Undertow,对业务代码完全透明。
业务代码与容器解耦的实际效果
先看一段最普通的 Solon 业务代码:
@Controller
public class UserController {
@Inject
private UserService userService;
@Get
@Mapping("/user/{id}")
public User getUser(@Path long id) {
return userService.findById(id);
}
@Post
@Mapping("/user")
public User createUser(@Body User user) {
return userService.save(user);
}
}
这段代码没有任何服务器相关的 import,没有 HttpServletRequest,没有 Servlet API。它只依赖 Solon 的通用注解。现在的问题是——它跑在哪个服务器上?
答案是:取决于你 pom.xml 里引入了哪个 boot 插件。
切换服务器:只改一行依赖
下面演示从 JlHttp(超轻量)切换到 Undertow(高并发)的完整过程。业务代码零改动。
第一步:用 JlHttp 启动(适合开发和小工具)
<!-- pom.xml -->
<dependencies>
<!-- Solon 内核 -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon</artifactId>
<version>3.0.5</version>
</dependency>
<!-- 轻量服务器插件 -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon.boot.jlhttp</artifactId>
<version>3.0.5</version>
</dependency>
</dependencies>
启动类:
public class App {
public static void main(String[] args) {
Solon.start(App.class, args);
}
}
运行 mvn solon:run 或直接 java -jar,日志里会看到 JlHttp 启动信息,默认端口 8080。
第二步:切换到 Undertow(适合生产高并发)
只改依赖,不动任何 Java 代码:
<!-- pom.xml -->
<dependencies>
<!-- Solon 内核(不变) -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon</artifactId>
<version>3.0.5</version>
</dependency>
<!-- 换成 Undertow 服务器插件 -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon.boot.undertow</artifactId>
<version>3.0.5</version>
</dependency>
</dependencies>
再次启动,日志变成 Undertow 的启动信息。Controller、Service、Filter 全部照旧运行。
第三步:如果需要 Servlet 特性支持
Undertow 插件本身兼容 Servlet API,但如果你需要完整的 Servlet 规范(比如 JSP、Servlet Filter 链),再加一个 web 插件:
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon.web.servlet</artifactId>
<version>3.0.5</version>
</dependency>
插件加载机制:内核怎么找到服务器
Solon 启动时扫描 classpath 上所有 solon.boot.xxx 插件,通过 SPI 机制发现 SolonPlugin 接口的实现类。每个 boot 插件都实现了一个 start(SolonApp app) 方法,负责初始化自己的服务器并绑定到 Solon 的路由分发器上。
简化后的加载流程:
Solon.start()
├── 扫描 classpath 所有 SolonPlugin 实现
├── 按优先级排序(boot 插件优先级最高)
├── 依次调用 plugin.start(app)
│ ├── solon.boot.undertow 插件初始化 Undertow Server
│ ├── 将 HTTP 请求转发给 Solon 内核路由
│ └── 监听端口,开始接收请求
└── 路由分发到 @Controller 方法
内核的路由分发器是统一的,不管哪个服务器插件接收请求,最终都走到同一个路由表。这就是解耦的关键——服务器插件只负责"接收请求并转交内核",不参与业务处理。
端口与配置:各插件通用
不管用哪个服务器插件,配置方式一致:
# app.yml - 通用配置,所有服务器插件都认
server:
port: 8080
host: 0.0.0.0
# Undertow 专属配置(其他插件会忽略不属于自己的项)
server.undertow:
ioThreads: 8
workerThreads: 256
bufferSize: 8192
Solon 的配置体系也是插件化的——每个服务器插件只读取自己命名空间下的配置,互不干扰。你可以在同一个 app.yml 里为不同服务器写配置,切换插件后无需改配置文件。
选型建议与注意事项
实际选型时,几个关键判断维度:
- 包体积敏感(CLI 工具、嵌入式场景):选
solon.boot.jlhttp或solon.boot.smarthttp,打包后整体可以控制在几 MB。 - 需要 Servlet 兼容(迁移老项目、用 JSP):选
solon.boot.tomcat或solon.boot.undertow+solon.web.servlet。 - 纯 REST 高并发(微服务网关、API 服务):选
solon.boot.undertow或solon.boot.nettyhttp,非 Servlet 模式下吞吐更高。 - WebSocket 为主(实时推送、IM):选
solon.boot.websocket或 Netty 插件。
需要注意的边界:
- 不要混用多个 boot 插件——一个项目只引入一个
solon.boot.xxx,否则启动时会出现端口冲突或路由重复绑定。 - Servlet API 依赖要显式声明——如果你的业务代码用了
HttpServletRequest,必须搭配对应的solon.web.servlet插件,否则轻量服务器插件不提供 Servlet 对象。 - 静态文件处理——轻量插件(JlHttp、SmartHttp)内置了简单的静态文件服务,Undertow/Tomcat 则依赖 Servlet 容器的静态资源机制,行为有细微差异。
快速验证清单
换服务器插件后,跑一遍这个清单确认没有遗漏:
# 1. 确认只有一个 boot 插件依赖
grep "solon.boot" pom.xml | wc -l
# 期望输出: 1(或0,如果用父POM管理)
# 2. 启动后检查日志里的服务器标识
mvn solon:run 2>&1 | grep -i "server start"
# 应看到对应服务器名称(Undertow/JlHttp/Netty...)
# 3. 端口是否正确监听
curl http://localhost:8080/user/1
# 业务接口正常返回
# 4. 如果用了 Servlet API,确认 web 插件存在
grep "solon.web.servlet" pom.xml
# 有输出则 OK,无输出则检查代码是否依赖了 Servlet 对象
Solon 把服务器做成可插拔的依赖项,内核只管路由和业务调度。这种设计让"换服务器"从一项工程重构变成了改一行 Maven 依赖的事。对于需要在不同部署环境(开发轻量、生产高并发)之间灵活切换的团队,这个模式值得认真考虑。