服务
服务层承载核心业务逻辑,与请求/响应解析无关。ACE 通过编译期宏 @Service 在构建阶段自动生成 Bean 注册代码,运行时直接查表——无反射、无字符串扫描。
基本用法
cangjie
package task_api.service
import ace_framework.*
import task_api.repository.TaskRepository
import task_api.dto.*
@Service
public class TaskService {
@Log var log: Logger
@Inject var repo: TaskRepository
public func listAll(): Array<TaskView> {
repo.findAll().map { t => TaskView.from(t) }.toArray()
}
public func get(id: Int64): ?TaskView {
repo.findById(id).map { t => TaskView.from(t) }
}
@Timed
public func create(title: String, priority: Int64, owner: String): TaskView {
log.info("creating task: ${title}")
let task = repo.save(Task(title: title, priority: priority, owner: owner))
TaskView.from(task)
}
@Transactional
public func transferOwner(fromId: Int64, toId: Int64, taskId: Int64): Unit {
repo.updateOwner(taskId, toId)
repo.logTransfer(fromId, toId, taskId)
}
}@Service 无额外参数时,注册为 Singleton(默认作用域)。
Bean 作用域
| 装饰器 | 作用域 | 说明 |
|---|---|---|
@Service | Singleton | 整个应用共享一个实例(默认) |
@Prototype | Prototype | 每次 resolve<T>() 创建新实例 |
@Service["request"] | Request | 同一 HTTP 请求内共享;请求结束自动销毁 |
cangjie
// 每次 resolve 新实例
@Prototype
public class ReportBuilder {
var rows: ArrayList<Row> = ArrayList<Row>()
// ...
}
// 请求内共享(可安全存储当前用户等请求级数据)
@Service["request"]
public class CurrentUser {
var id: Int64 = 0
var roles: Array<String> = []
}依赖注入 @Inject
字段注入
cangjie
@Service
public class OrderService {
@Inject var userSvc: UserService
@Inject var inventory: InventoryService
@Inject var repo: OrderRepository
}接口注入与多实现区分
当同一接口有多个实现时,用 @Primary 标记默认实现,用 @Qualifier 按名称区分:
cangjie
// 声明接口
public interface NotificationSender {
func send(to: String, msg: String): Unit
}
@Service
@Primary
public class EmailSender <: NotificationSender { ... }
@Service
@Qualifier["sms"]
public class SmsSender <: NotificationSender { ... }
// 注入时
@Service
public class AlertService {
@Inject var sender: NotificationSender // 注入 @Primary -> EmailSender
@Inject @Qualifier["sms"] var smsSender: NotificationSender // 注入 SmsSender
}生命周期钩子
@PostConstruct — 初始化钩子
在 Bean 完成依赖注入后、第一次被使用前执行。适合预热缓存、建立连接池等操作:
cangjie
@Service
public class CacheWarmupService {
@Inject var repo: ProductRepository
var hotProducts: Array<Product> = []
@PostConstruct
public func warmup(): Unit {
hotProducts = repo.findTopN(100).toArray()
}
}@PreDestroy — 停机钩子
应用正常关闭时调用,适合释放资源:
cangjie
@Service
public class ConnectionPoolService {
var pool: ConnectionPool = ConnectionPool()
@PreDestroy
public func shutdown(): Unit {
pool.close()
}
}AOP 宏组合使用
@Service 类的方法可叠加多种 AOP 宏,编译期生成增强代理,运行时无额外开销:
cangjie
@Service
public class ProductService {
@Log var log: Logger
@Inject var repo: ProductRepository
@Cacheable[ttl: 300_000] // 缓存 5 分钟
@Timed // 记录执行耗时
public func getById(id: Int64): ?ProductView {
repo.findById(id).map { p => ProductView.from(p) }
}
@Retry[3] // 失败最多重试 3 次
@CircuitBreaker[5] // 5 次失败后熔断
public func fetchExternal(sku: String): ExternalInfo { ... }
@Async // 异步执行,返回 Future<T>
public func sendReport(email: String): Unit {
log.info("sending report to ${email}")
// 耗时操作...
}
@Scheduled[ms: 60_000] // 每分钟执行
public func cleanup(): Unit {
repo.deleteExpired()
}
@Transactional
@Timed
public func batchCreate(items: Array<CreateReq>): Array<ProductView> {
items.map { req => repo.save(Product.from(req)) }
.map { p => ProductView.from(p) }
.toArray()
}
}常用 AOP 宏一览:
| 宏 | 说明 |
|---|---|
@Log | 注入结构化 Logger,自动绑定类名 |
@Timed | 记录方法执行耗时到 metrics |
@Cacheable[ttl: ms] | 方法结果缓存,ttl 单位毫秒 |
@Async | 方法异步执行,调用方立即返回 |
@Scheduled[ms: n] | 定时任务,每 n 毫秒执行一次 |
@Scheduled["cron"] | 按 cron 表达式调度 |
@Retry[n] | 失败自动重试,最多 n 次 |
@CircuitBreaker[n] | n 次失败后触发熔断保护 |
@EventListener | 监听应用事件总线消息 |
@Transactional | 开启数据库事务,异常自动回滚 |
@RequiresNew | 挂起当前事务,开启独立新事务 |
@Around["name"] | 挂载自定义拦截器 |
完整示例
cangjie
package task_api.service
import ace_framework.*
import task_api.repository.TaskRepository
import task_api.dto.*
@Service
public class TaskService {
@Log var log: Logger
@Inject var repo: TaskRepository
@Timed
public func create(title: String, priority: Int64, owner: String): TaskView {
log.info("create task", "title" => title, "owner" => owner)
let task = repo.save(Task(
title: title,
priority: priority,
owner: owner,
status: "pending"
))
TaskView.from(task)
}
@Cacheable[ttl: 10_000]
public func get(id: Int64): ?TaskView {
repo.findById(id).map { t => TaskView.from(t) }
}
public func page(page: Int64, size: Int64): PageResult<TaskView> {
let offset = (page - 1) * size
let items = repo.findPage(offset, size).map { t => TaskView.from(t) }.toArray()
let total = repo.count()
PageResult(items: items, total: total, page: page, size: size)
}
@Transactional
public func updateStatus(id: Int64, status: String): TaskView {
let task = repo.findById(id) ?? throw NotFoundException("task not found")
repo.save(task.withStatus(status))
|> TaskView.from
}
@Transactional
public func delete(id: Int64): Unit {
repo.deleteById(id)
log.info("task deleted", "id" => id)
}
@PostConstruct
public func init(): Unit {
log.info("TaskService initialized")
}
@PreDestroy
public func destroy(): Unit {
log.info("TaskService shutting down")
}
}Singleton 字段安全
@Service(Singleton)的实例在整个应用中只有一份,不要在字段中存储请求级状态(如当前用户、请求 ID)。如需请求级共享状态,请使用 @Service["request"] 作用域,或通过 ctx.state 传递。
接口优先
推荐为 Service 提取接口,便于测试时 Mock 替换:
cangjie
public interface TaskService {
func create(title: String, priority: Int64, owner: String): TaskView
func get(id: Int64): ?TaskView
}
@Service
public class TaskServiceImpl <: TaskService { ... }测试时注入 MockTaskService 即可,无需启动完整容器。
@Scheduled 与 Singleton
@Scheduled 方法挂在 Singleton Bean 上,整个应用只会注册一个定时任务实例。 避免在 @Scheduled 方法内访问 request scope Bean,请求上下文在定时任务中不存在,访问会抛出 NoActiveRequestException。