进阶功能
AOP 切面编程
使用 @Aspect 定义切面,通过 @Before、@After、@Around 拦截方法调用:
AopExample.kt
import top.wcpe.yourplugin.ioc.annotation.*
import top.wcpe.yourplugin.ioc.bean.MethodInvocation
interface OrderService {
fun placeOrder(orderId: String): String
}
@Service
class OrderServiceImpl : OrderService {
override fun placeOrder(orderId: String): String {
println("下单: $orderId")
return "OK"
}
}
@Aspect
class LoggingAspect {
@Before("execution(OrderServiceImpl.placeOrder)")
fun beforeOrder() {
println("准备下单...")
}
@After("execution(OrderServiceImpl.placeOrder)")
fun afterOrder() {
println("下单完成")
}
@Around("execution(OrderServiceImpl.placeOrder)")
fun aroundOrder(invocation: MethodInvocation): Any? {
val start = System.currentTimeMillis()
val result = invocation.proceed()
println("耗时: ${System.currentTimeMillis() - start}ms")
return result
}
}
切点表达式支持:
| 格式 | 说明 |
|---|---|
execution(类名.方法名) | 精确匹配 |
execution(*.方法名) | 匹配所有类的指定方法 |
execution(包名..*.方法名) | 匹配包下所有类的指定方法 |
execution(类名.*) | 匹配类的所有方法 |
AOP 限制
AOP 代理基于 JDK 动态代理,目标 Bean 必须实现接口才能被代理。@Aspect 类会自动注册为组件,无需额外标记 @Component。
条件装配
根据运行时条件决定是否注册 Bean:
ConditionalBeans.kt
import top.wcpe.yourplugin.ioc.annotation.*
// 仅当 ClassPath 中存在 Redis 客户端时注册
@Service
@ConditionalOnClass("redis.clients.jedis.Jedis")
class RedisCache : Cache {
override fun get(key: String): String? = TODO()
}
// 当没有其他 Cache 实现时,使用内存缓存作为兜底
@Service
@ConditionalOnMissingBean(Cache::class)
class InMemoryCache : Cache {
override fun get(key: String): String? = TODO()
}
// 当系统属性 feature.audit=true 时启用审计
@Service
@ConditionalOnProperty(name = "feature.audit", havingValue = "true")
class AuditService
// 自定义条件
class ProductionCondition : Condition {
override fun matches(context: ConditionContext): Boolean {
return System.getProperty("env") == "production"
}
}
@Service
@Conditional(ProductionCondition::class)
class ProductionOnlyService
条件评估分两阶段:
- 扫描时:
@ConditionalOnClass、@ConditionalOnMissingClass、@ConditionalOnProperty、@Conditional - 注册后:
@ConditionalOnBean、@ConditionalOnMissingBean(依赖已注册的 Bean 信息)
信息
同一个类上可以叠加多个条件注解,所有条件之间为 AND 关系,全部满足才注册。
线程作用域与可刷新作用域
ThreadAndRefreshScope.kt
import top.wcpe.yourplugin.ioc.annotation.*
import top.wcpe.yourplugin.ioc.bean.BeanContainer
// 每个线程持有独立实例
@Service
@ThreadScope
class RequestContext {
var userId: String = ""
}
// 可刷新作用域,支持运行时重建
@Service
@RefreshScope
class DynamicConfig {
var maxRetries: Int = 3
}
// 使用
fun example() {
// 刷新所有 refresh 作用域的 Bean
BeanContainer.refreshScope()
// 刷新指定 Bean
BeanContainer.refreshScope("dynamicConfig")
// 清理当前线程的 ThreadScope 缓存
BeanContainer.getThreadScope()?.clearCurrentThread()
}
代码说明:
@ThreadScope:线程级作用域,每个线程持有独立的 Bean 实例,适合请求上下文等场景@RefreshScope:可刷新作用域,调用refreshScope()后下次获取会重新创建实例,适合动态配置
@Configuration + @Bean
通过 @Configuration 类中的 @Bean 方法声明 Bean,适合需要自定义创建逻辑的场景:
DatabaseConfig.kt
import top.wcpe.yourplugin.ioc.annotation.*
interface DataSource {
fun url(): String
}
class MysqlDataSource(private val jdbcUrl: String) : DataSource {
override fun url(): String = jdbcUrl
}
@Configuration
class DatabaseConfig {
@Bean
fun dataSource(@Named("jdbcUrl") url: String): DataSource = MysqlDataSource(url)
@Primary
@Bean("mainCache")
fun mainCache(): CacheService = RedisCacheService()
@ConditionalOnProperty(name = "cache.local.enabled", havingValue = "true")
@Bean
fun localCache(): CacheService = LocalCacheService()
}
代码说明:
@Configuration:标记配置类,配置类本身也会被注册为 singleton Bean@Bean:方法返回值作为 Bean 实例,value为空时名称默认为方法名- 方法参数自动从容器中解析注入,支持
@Named限定和@Lazy延迟注入 @Bean产物支持@PostConstruct/@PostEnable/@PreDestroy生命周期回调和@Value/@Inject字段注入
@PropertySource 配置文件
在 @Configuration 类上使用 @PropertySource 指定配置文件,配合 @Value 注入属性值:
AppConfig.kt
import top.wcpe.yourplugin.ioc.annotation.*
// app.properties:
// app.name=MyPlugin
// app.version=2.0
@PropertySource("app.properties")
@Configuration
class AppConfig {
@Bean
fun appInfo(): AppInfo = AppInfo()
}
class AppInfo {
@Value("\${app.name:DefaultApp}")
var name: String = ""
@Value("\${app.version:1.0}")
var version: String = ""
}
代码说明:
@PropertySource:指定 classpath 相对路径,支持.properties和简单的.yml格式(仅扁平key: value)@Value("\${key:default}"):从配置文件或系统属性中读取值,冒号后为默认值- 属性查找优先级:已加载配置文件 > 系统属性
BeanPostProcessor 扩展
BeanPostProcessor 允许在 Bean 初始化前后对实例进行自定义处理:
AuditPostProcessor.kt
import top.wcpe.yourplugin.ioc.annotation.Component
import top.wcpe.yourplugin.ioc.bean.BeanPostProcessor
@Component
class AuditPostProcessor : BeanPostProcessor {
override fun postProcessAfterInitialization(bean: Any, beanName: String): Any {
println("Bean 初始化完成: $beanName")
return bean
}
}
代码说明:
postProcessBeforeInitialization:在@PostConstruct之前调用postProcessAfterInitialization:在@PostConstruct之后、AOP 代理之前调用- 实现此接口的 Bean 会被自动发现并注册,可以返回原始实例或包装后的代理
@DependsOn 初始化顺序
使用 @DependsOn 显式声明 Bean 初始化顺序依赖,确保指定的 Bean 先初始化:
DependsOnExample.kt
import top.wcpe.yourplugin.ioc.annotation.*
@Component
class DatabaseConnection {
@PostConstruct
fun connect() { println("数据库已连接") }
}
@DependsOn("databaseConnection")
@Component
class UserDao {
@Inject
lateinit var db: DatabaseConnection
}
代码说明:
@DependsOn("databaseConnection"):确保DatabaseConnection在UserDao之前初始化- 容器会按拓扑排序确保依赖的 Bean 先初始化
- 可用于类级别和
@Bean方法级别
可选注入
使用 @Inject(required = false) 标记可选依赖,当依赖不存在时不抛异常:
OptionalInject.kt
import top.wcpe.yourplugin.ioc.annotation.*
@Component
class PluginFeature {
// 如果 AnalyticsService 没有注册,字段保持 null,不抛异常
@Inject(required = false)
var analytics: AnalyticsService? = null
fun isAnalyticsEnabled(): Boolean = analytics != null
}
代码说明:
required = true(默认):注入失败时抛出IllegalStateExceptionrequired = false:注入失败时字段保持null,输出 warning 日志