claude
我来分析一下这个 Colleen 框架的代码,指出潜在问题并提供插件系统的设计方案。
现有代码的问题和改进点
1. 线程安全问题
// 当前代码
private val shutdownHooks = mutableListOf<Runnable>()
// 问题:多线程环境下 mutableList 不安全
// 建议改为:
private val shutdownHooks = CopyOnWriteArrayList<Runnable>()2. 路由注册的线程安全
Router 类明确标注了"Not thread-safe",但在实际使用中可能需要动态添加路由。建议要么:
添加同步机制
或者在文档中明确禁止运行时动态注册
3. 异常处理的类型安全问题
@Suppress("UNCHECKED_CAST")
(handler as ErrorHandler<Exception>)(e, ctx)这个强制转换存在潜在的类型不安全。可以改进泛型设计。
4. 配置修改时机
fun listen(port: Int = 8080, host: String = "127.0.0.1", callback: Runnable = Runnable {}) {
if (host != config.server.host) config.server.host = host
if (port != config.server.port) config.server.port = port
// ...
}在 listen 中修改配置可能导致混淆,建议要么在构造时固定配置,要么提供明确的配置方法。
5. 资源清理不完整
shutdown() 方法中没有清理 serviceContainer 和其他资源,可能导致内存泄漏。
6. Mount 路径处理
// TODO: 直接相加?
this.addRoute(it.method, basePath + it.path, handler)路径拼接应该使用 PathUtils.normalize() 或专门的路径合并方法。
插件系统设计方案
方案一:事件驱动的插件系统(推荐 ⭐)
这是我最推荐的方案,平衡了灵活性、可扩展性和简洁性。
// ========================================================================
// 插件核心接口
// ========================================================================
/**
* 插件生命周期钩子
*/
interface Plugin {
/**
* 插件名称(唯一标识)
*/
val name: String
/**
* 插件初始化,在应用启动前调用
*/
fun install(app: Colleen) {}
/**
* 应用启动后调用
*/
fun onStarted(app: Colleen) {}
/**
* 应用关闭前调用
*/
fun onShutdown(app: Colleen) {}
}
/**
* 应用事件类型
*/
sealed class ApplicationEvent {
data class BeforeRequest(val ctx: Context) : ApplicationEvent()
data class AfterRequest(val ctx: Context) : ApplicationEvent()
data class RouteRegistered(val method: String, val path: String) : ApplicationEvent()
data class ErrorOccurred(val error: Throwable, val ctx: Context) : ApplicationEvent()
data class ServerStarting(val host: String, val port: Int) : ApplicationEvent()
data class ServerStarted(val host: String, val port: Int) : ApplicationEvent()
data class ServerStopping() : ApplicationEvent()
}
/**
* 事件监听器
*/
fun interface EventListener {
fun onEvent(event: ApplicationEvent)
}
// ========================================================================
// Colleen 类扩展
// ========================================================================
class Colleen {
// ... 现有代码 ...
/**
* 已安装的插件
*/
private val plugins = mutableMapOf<String, Plugin>()
/**
* 事件监听器
*/
private val eventListeners = ConcurrentHashMap<KClass<out ApplicationEvent>,
MutableList<EventListener>>()
/**
* 安装插件
*/
fun install(plugin: Plugin) {
if (plugins.containsKey(plugin.name)) {
throw IllegalStateException("Plugin '${plugin.name}' already installed")
}
plugins[plugin.name] = plugin
plugin.install(this)
}
/**
* 批量安装插件
*/
fun install(vararg plugins: Plugin) {
plugins.forEach { install(it) }
}
/**
* 订阅事件
*/
inline fun <reified T : ApplicationEvent> on(noinline listener: (T) -> Unit) {
val listeners = eventListeners.getOrPut(T::class) {
Collections.synchronizedList(mutableListOf())
}
listeners.add { event ->
if (event is T) listener(event)
}
}
/**
* 发布事件(内部使用)
*/
internal fun emit(event: ApplicationEvent) {
eventListeners[event::class]?.forEach { listener ->
try {
listener.onEvent(event)
} catch (e: Exception) {
logger.error("Event listener failed for ${event::class.simpleName}", e)
}
}
}
// 修改 listen 方法
fun listen(port: Int = 8080, host: String = "127.0.0.1", callback: Runnable = Runnable {}) {
// ... 现有代码 ...
emit(ApplicationEvent.ServerStarting(host, port))
try {
server.start(createHttpHandler())
// 通知插件应用已启动
plugins.values.forEach { it.onStarted(this) }
emit(ApplicationEvent.ServerStarted(host, port))
callback.run()
} catch (e: Exception) {
// ... 错误处理 ...
}
}
// 修改 handleRequest
internal fun handleRequest(ctx: Context) {
emit(ApplicationEvent.BeforeRequest(ctx))
try {
router.handleRequest(ctx)
ctx.response.materialize(ctx)
emit(ApplicationEvent.AfterRequest(ctx))
} catch (e: Exception) {
emit(ApplicationEvent.ErrorOccurred(e, ctx))
// ... 现有错误处理 ...
}
}
// 修改 shutdown
private fun shutdown() {
if (!shuttingDown.compareAndSet(false, true)) return
emit(ApplicationEvent.ServerStopping())
// 通知插件关闭
plugins.values.reversed().forEach {
try {
it.onShutdown(this)
} catch (e: Exception) {
logger.error("Plugin shutdown failed: ${it.name}", e)
}
}
// ... 现有关闭逻辑 ...
}
}
// ========================================================================
// 示例插件实现
// ========================================================================
/**
* CORS 插件
*/
class CorsPlugin(
private val allowedOrigins: List<String> = listOf("*"),
private val allowedMethods: List<String> = listOf("GET", "POST", "PUT", "DELETE"),
private val allowedHeaders: List<String> = listOf("*")
) : Plugin {
override val name = "cors"
override fun install(app: Colleen) {
app.use { ctx, next ->
ctx.header("Access-Control-Allow-Origin", allowedOrigins.joinToString(","))
ctx.header("Access-Control-Allow-Methods", allowedMethods.joinToString(","))
ctx.header("Access-Control-Allow-Headers", allowedHeaders.joinToString(","))
if (ctx.method == "OPTIONS") {
ctx.status(204)
} else {
next()
}
}
}
}
/**
* 日志插件
*/
class LoggingPlugin(
private val logLevel: String = "INFO"
) : Plugin {
override val name = "logging"
override fun install(app: Colleen) {
app.on<ApplicationEvent.BeforeRequest> { event ->
val ctx = event.ctx
logger.info("→ ${ctx.method} ${ctx.path}")
}
app.on<ApplicationEvent.AfterRequest> { event ->
val ctx = event.ctx
logger.info("← ${ctx.method} ${ctx.path} - ${ctx.response.status}")
}
}
}
/**
* 健康检查插件
*/
class HealthCheckPlugin(
private val endpoint: String = "/health"
) : Plugin {
override val name = "health-check"
override fun install(app: Colleen) {
app.get(endpoint) {
mapOf("status" to "healthy", "timestamp" to System.currentTimeMillis())
}
}
}
/**
* 请求限流插件
*/
class RateLimitPlugin(
private val maxRequests: Int = 100,
private val windowMs: Long = 60_000
) : Plugin {
override val name = "rate-limit"
private val requestCounts = ConcurrentHashMap<String, AtomicInteger>()
private val windowStarts = ConcurrentHashMap<String, Long>()
override fun install(app: Colleen) {
app.use { ctx, next ->
val clientId = ctx.ip()
val now = System.currentTimeMillis()
val windowStart = windowStarts.getOrPut(clientId) { now }
if (now - windowStart > windowMs) {
windowStarts[clientId] = now
requestCounts[clientId] = AtomicInteger(0)
}
val count = requestCounts.getOrPut(clientId) { AtomicInteger(0) }
if (count.incrementAndGet() > maxRequests) {
throw HttpException(429, "Too Many Requests")
}
next()
}
}
}
// ========================================================================
// 使用示例
// ========================================================================
fun main() {
val app = Colleen()
// 安装插件
app.install(
CorsPlugin(),
LoggingPlugin(),
HealthCheckPlugin(),
RateLimitPlugin(maxRequests = 100, windowMs = 60_000)
)
// 监听自定义事件
app.on<ApplicationEvent.RouteRegistered> { event ->
println("Route registered: ${event.method} ${event.path}")
}
app.get("/") { "Hello, World!" }
app.listen(8080)
}方案二:扩展点模式
适合需要更细粒度控制的场景。
/**
* 扩展点接口
*/
interface Extension<T> {
fun extend(target: T)
}
/**
* 路由扩展点
*/
interface RouteExtension : Extension<Router> {
override fun extend(target: Router)
}
/**
* 配置扩展点
*/
interface ConfigExtension : Extension<Config> {
override fun extend(target: Config)
}
class Colleen {
private val routeExtensions = mutableListOf<RouteExtension>()
private val configExtensions = mutableListOf<ConfigExtension>()
fun extend(extension: RouteExtension) {
routeExtensions.add(extension)
extension.extend(router)
}
fun extend(extension: ConfigExtension) {
configExtensions.add(extension)
extension.extend(config)
}
}
// 使用示例
class SwaggerExtension : RouteExtension {
override fun extend(target: Router) {
target.addRoute("GET", "/swagger-ui") { ctx ->
// Swagger UI 实现
}
}
}方案三:构建器模式插件
适合复杂配置场景。
abstract class PluginBuilder<T : Plugin> {
abstract fun build(): T
}
interface Plugin {
fun configure(app: Colleen)
}
class AuthPluginBuilder : PluginBuilder<AuthPlugin>() {
var jwtSecret: String = ""
var tokenExpiration: Long = 3600
var excludePaths: List<String> = emptyList()
override fun build() = AuthPlugin(jwtSecret, tokenExpiration, excludePaths)
}
fun Colleen.auth(block: AuthPluginBuilder.() -> Unit) {
val plugin = AuthPluginBuilder().apply(block).build()
install(plugin)
}
// 使用
app.auth {
jwtSecret = "my-secret"
tokenExpiration = 7200
excludePaths = listOf("/login", "/register")
}推荐方案总结
我最推荐方案一(事件驱动),原因:
灵活性高:插件可以监听多种事件,实现复杂功能
解耦性好:插件之间、插件与核心之间通过事件通信
易于扩展:新增功能只需添加新事件类型
符合直觉:开发者容易理解和使用
类似成熟框架:Koa.js、Express.js 都采用类似设计
实施建议:
先实现基础的 Plugin 接口和事件系统
提供几个官方插件作为示例
逐步添加更多事件类型
考虑插件的依赖管理和加载顺序
需要我详细实现某个部分吗?