You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

编译时初始化工厂:如何优化基于名称返回实现的工厂实例管理

实现编译时初始化的模块工厂

针对你想把模块工厂的初始化从运行时bootstrap移到编译时的需求,在Scala里有几种实用的方案,我给你详细拆解下:

方案1:Scala 3 用 inline + 编译时反射(推荐)

这个方案利用Scala 3的inline和编译时API,自动在编译阶段收集所有标记过的模块实现,生成对应的moduleMap,完全不需要手动维护映射关系。

步骤与代码示例

  1. 定义注册模块的注解和基础特质
import scala.annotation.StaticAnnotation

// 用于标记模块实现的注解,指定模块名称
class RegisterModule(name: String) extends StaticAnnotation

// 模块的基础特质
trait Module

// 自定义异常类
class ModuleNotFoundException(message: String) extends Exception(message)
  1. 编写你的模块实现,用注解标记
// 标记这个实现对应名称"module1"
class ModuleImpl extends Module @RegisterModule("module1")

// 另一个模块实现,标记对应"module2"
class AnotherModuleImpl extends Module @RegisterModule("module2")
  1. 实现编译时工厂
import scala.compiletime.*
import scala.quoted.*

object ModuleFactory {
  // 编译时生成的模块映射,运行时直接使用
  inline val moduleMap: Map[String, Module] = ${collectModules}

  // 编译时扫描并收集所有带@RegisterModule注解的Module实现
  private def collectModules(using Quotes): Expr[Map[String, Module]] = {
    import quotes.reflect.*

    // 遍历当前编译环境中的所有类,筛选符合条件的模块
    val modules = Symbol.requiredModule("").declarations.collect {
      case cls if cls.isClassDef && cls.annotations.exists(_.annotationType <:< TypeRepr.of[RegisterModule]) =>
        val annotation = cls.annotations.find(_.annotationType <:< TypeRepr.of[RegisterModule]).get
        // 提取注解中的模块名称
        val nameExpr = annotation.args.head.asExpr[String]
        // 获取模块实例的表达式(确保类实现了Module特质)
        val instanceExpr = Expr.summon[Module](using TypeRepr.of[Module].substituteParams(Nil, List(cls.typeRef)))
          .getOrElse(report.errorAndAbort(s"${cls.name} 未实现Module特质,无法注册"))
        (nameExpr, instanceExpr)
    }

    // 构建Map的编译时表达式
    Expr.ofMap(modules.toMap)
  }

  def getModule(moduleName: String): Module = moduleMap.get(moduleName) match {
    case Some(m) => m
    case _ => throw new ModuleNotFoundException(s"$moduleName - 模块未找到")
  }
}

核心优势:编译时自动扫描所有标记的模块,不需要手动更新moduleMap,所有实例在编译阶段就已确定,运行时直接读取,完全避免了运行时初始化的开销。

方案2:Scala 2 用宏注解

如果你的项目还在使用Scala 2,可以借助宏注解来实现编译时生成moduleMap,需要依赖scalamacros-paradise插件。

步骤与代码示例

  1. 添加宏依赖(build.sbt)
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)
  1. 定义宏注解和基础类
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

// 用于生成工厂的宏注解
@compileTimeOnly("请启用macro paradise插件来展开此注解")
class GenerateModuleFactory extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro ModuleFactoryMacro.generate
}

// 宏实现逻辑
object ModuleFactoryMacro {
  def generate(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    // 扫描所有实现Module且带RegisterModule注解的类
    val moduleEntries = c.classSymbol.tpe.decls.collect {
      case cls: ClassSymbol if cls.isConcrete && cls.toType <:< typeOf[Module] =>
        cls.annotations.collectFirst {
          case annot if annot.tree.tpe =:= typeOf[RegisterModule] =>
            val moduleName = annot.tree.children.tail.head.asInstanceOf[Literal].value.value.asInstanceOf[String]
            val instance = q"new ${cls.toType}"
            (moduleName, instance)
        }
    }.flatten.toMap

    // 生成工厂对象的代码
    annottees match {
      case q"object $obj extends ..$parents { ..$body }" :: Nil =>
        q"""
          object $obj extends ..$parents {
            private val moduleMap = Map(..${moduleEntries.map { case (k, v) => q"$k -> $v" }})
            def getModule(moduleName: String): Module = moduleMap.get(moduleName) match {
              case Some(m) => m
              case _ => throw new ModuleNotFoundException(s"$$moduleName - 模块未找到")
            }
            ..$body
          }
        """
      case _ => c.abort(c.enclosingPosition, "此注解仅能应用于object")
    }
  }
}

// 模块标记注解与基础特质
class RegisterModule(name: String) extends StaticAnnotation
trait Module
class ModuleNotFoundException(message: String) extends Exception(message)
  1. 使用宏注解生成工厂
// 用注解自动生成工厂逻辑
@GenerateModuleFactory
object ModuleFactory

// 模块实现
class ModuleImpl extends Module @RegisterModule("module1")
class AnotherModuleImpl extends Module @RegisterModule("module2")

核心优势:Scala 2环境下的替代方案,同样实现编译时生成映射,无需手动维护moduleMap。

方案3:用Scala枚举(适合模块固定的场景)

如果你的模块数量固定、提前已知,用Scala枚举是最简洁的方案,枚举的所有值在编译时就已确定,天然满足编译时初始化的需求。

代码示例

// 模块基础特质
trait Module
class ModuleImpl extends Module
class AnotherModuleImpl extends Module

// 自定义异常
class ModuleNotFoundException(message: String) extends Exception(message)

// 枚举定义,每个case对应一个模块实例
enum ModuleEnum(val moduleName: String, val instance: Module):
  case Module1 extends ModuleEnum("module1", new ModuleImpl)
  case Module2 extends ModuleEnum("module2", new AnotherModuleImpl)

// 工厂实现
object ModuleFactory {
  // 编译时生成的映射
  private val moduleMap: Map[String, Module] = ModuleEnum.values.map(e => e.moduleName -> e.instance).toMap

  def getModule(moduleName: String): Module = moduleMap.get(moduleName) match {
    case Some(m) => m
    case _ => throw new ModuleNotFoundException(s"$moduleName - 模块未找到")
  }
}

核心优势:代码简洁易懂,完全不需要反射或宏,适合模块数量少且固定的场景。


内容的提问来源于stack exchange,提问作者user2607928

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:12:21