SpringBoot+Kotlin必看:用错lateinit的5种典型场景与正确姿势(附AOP避坑指南)

Kotlin与SpringBoot:解决@Transactional与@Autowired联用时的lateinit初始化陷阱 本文深入分析了Kotlin与SpringBoot中@Transactional与@Autowired联用时的lateinit初始化陷阱,揭示了Kotlin默认final类与Spring AOP代理机制的冲突原因。通过实战案例展示了open声明、构造函数注入等解决方案,并提供了团队协作规范和性能优化建议,帮助开发者避免常见陷阱。 阅读详情

SpringBoot+Kotlin必看:用错lateinit的5种典型场景与正确姿势(附AOP避坑指南)

最近在几个Kotlin+SpringBoot项目做代码评审,发现一个挺有意思的现象:不少开发者,尤其是从Java转过来的,对Kotlin的lateinit有种莫名的偏爱,觉得它解决了“非空声明就得初始化”的麻烦,用起来特别顺手。但恰恰是这种“顺手”,埋下了不少运行时炸弹。我见过最典型的一个案例,一个核心交易服务在预发环境跑得好好的,一上生产,凌晨的定时任务就炸了,报的就是那个经典的lateinit property xxx has not been initialized。团队排查了大半夜,最后发现是@Transactional和lateinit在AOP代理下的一个微妙冲突。

这让我意识到,lateinit在Spring生态里,远不止是“延迟初始化”那么简单。它和Spring的依赖注入、AOP代理、Bean生命周期、甚至是测试框架,都有着千丝万缕的耦合。用对了,代码简洁优雅;用错了,就是隐蔽的运行时异常。这篇文章,我就结合自己踩过的坑和看到的案例,系统梳理一下lateinit在SpringBoot+Kotlin项目中最容易出错的五种场景,并给出经过实战检验的“正确姿势”。我们的目标不是不用lateinit,而是聪明地、安全地使用它。

1. 场景一:AOP代理(如@Transactional)与类的final性

这是最经典、最高频的踩坑点。原始文章和网络搜索里反复提到的@Transactional + @Autowired报错,根源就在这里。

问题本质:CGLIB代理与Kotlin的final类

Spring实现AOP(比如事务管理@Transactional、缓存@Cacheable、异步@Async)的常用方式之一,是为目标Bean创建一个子类代理(CGLIB)。这个代理会重写你的方法,加入事务控制等逻辑。这里有个关键前提:你的类必须能被继承,即不能是final的。

而Kotlin为了推崇不可变设计和函数式风格,默认将所有类都声明为final。当你给一个Kotlin类加上@Transactional时,Spring试图创建代理,却发现这个类是final的,无法继承。于是,Spring会退而求其次,创建一个基于接口的代理(如果实现了接口),或者在某些配置下,直接创建一个非代理的原始Bean实例。问题就出在这里。

当Spring无法创建CGLIB代理时,它可能会绕过正常的依赖注入生命周期,或者以一种非预期的方式初始化Bean。这时,@Autowired标注的lateinit属性就可能没有被正确注入,仍然处于未初始化状态。当你调用一个方法时,访问这个属性就会触发UninitializedPropertyAccessException。

错误示例:

@Service
@Transactional // 这里触发了AOP代理需求
class OrderService {
    @Autowired
    private lateinit var orderRepository: OrderRepository // 危险!

    fun placeOrder(order: Order) {
        // 运行时可能抛出:lateinit property orderRepository has not been initialized
        orderRepository.save(order)
    }
}

上面的代码在单元测试或某些简单启动时可能正常,但在完整的Spring容器中,尤其是当@Transactional生效时,极易出错。

解决方案对比与选择

网上常见的方案是给类和所有需要代理的方法都加上open关键字。这确实能解决问题,但不够优雅,也违背了Kotlin默认final的初衷。

方案具体做法优点缺点推荐指数
1. 添加 open 关键字在类和方法前加 open。直接,能快速解决问题。1. 污染代码,每个方法都要加。
2. 破坏了Kotlin的“默认final”设计理念。
3. 容易遗漏,维护成本高。
⭐⭐
2. 使用接口+JDK动态代理定义一个接口,让Service实现它。符合Spring最佳实践,代理基于接口更清晰。1. 需要额外定义接口。
2. 对于已有大量类改造麻烦。
⭐⭐⭐
3. 构造函数注入 (推荐)放弃属性注入,使用主构造函数。根本性解决,无需关心代理机制。代码不可变,更安全。1. 对于有大量依赖的类,构造函数参数列表可能较长。⭐⭐⭐⭐⭐
4. 调整Spring配置在application.properties中设置spring.aop.proxy-target-class=false。强制使用JDK动态代理(基于接口)。1. 全局影响,可能影响其他需要CGLIB的AOP场景。
2. 要求所有相关类都必须实现接口。
⭐⭐

我最推荐的是方案3:构造函数注入。 这是Spring官方和Kotlin官方都推崇的方式。Kotlin对构造函数注入有天然的良好支持,语法非常简洁。

正确姿势:

@Service
@Transactional // 现在可以安全使用了
class OrderService(
    private val orderRepository: OrderRepository, // 通过主构造函数注入
    private val paymentClient: PaymentClient
) {
    fun placeOrder(order: Order) {
        // orderRepository 在对象构造时即被初始化,绝对安全
        orderRepository.save(order)
        // ... 其他业务逻辑
    }
}

注意:使用构造函数注入时,Spring会自动将匹配类型的Bean注入进来。如果只有一个构造函数,@Autowired注解甚至可以省略。这种方式保证了依赖在对象创建时就已经完备,彻底消除了lateinit的未初始化风险,也让你的Bean变成了不可变(val)的,更符合函数式风格。

如果因为某些原因(比如框架限制或遗留代码)必须使用属性注入,那么请务必记得为类和被AOP代理的方法加上open。同时,可以考虑使用Kotlin的all-open插件来自动化这个过程。

2. 场景二:单元测试与集成测试中的初始化时机

测试环境是lateinit问题的另一个重灾区。很多开发者在写测试时,会发现明明在生产环境跑得好好的@Autowired lateinit属性,在测试类里就是null。

问题根源:测试框架的上下文加载

@SpringBootTest会启动一个Spring应用上下文来运行测试。但这个上下文的加载、Bean的创建和注入顺序,与常规应用启动可能略有不同。特别是当你混合使用@MockBean、@SpyBean,或者测试类继承结构比较复杂时,lateinit属性的初始化可能不会在你期望的时间点发生。

错误示例:

@SpringBootTest
class OrderServiceTest {
    @Autowired
    private lateinit var orderService: OrderService // 可能未初始化

    @MockBean
    private lateinit var mockRepository: OrderRepository

    @Test
    fun `test place order`() {
        // 如果orderService因为某些原因(如循环依赖、AOP问题)未能正确注入,
        // 这一行就会直接崩溃
        orderService.placeOrder(Order())
    }
}

还有一种情况是在@BeforeEach或@BeforeAll方法中访问lateinit属性。如果Spring的依赖注入发生在这些生命周期方法之后,同样会报错。

解决方案:安全的测试属性初始化

  1. 首选构造函数注入(同样适用):在测试类中也使用构造函数注入,这是最可靠的方式。Spring Test框架支持对测试类的构造函数进行注入。

    @SpringBootTest
    class OrderServiceTest(
        private val orderService: OrderService, // 注入真实Bean
        @MockBean private val mockRepository: OrderRepository // MockBean也可以配合
    ) {
        @Test
        fun `test place order`() {
            // orderService 已确定初始化
            `when`(mockRepository.save(any())).thenReturn(Order(id = 1L))
            val result = orderService.placeOrder(Order())
            // ... 断言
        }
    }
    
  2. 使用 @TestConstructor(autowireMode = AutowireMode.ALL):这是一个非常实用的注解,它可以告诉Spring Test支持工具,对测试类的所有构造函数参数进行自动装配。

    @SpringBootTest
    @TestConstructor(autowireMode = AutowireMode.ALL) // 关键注解
    class OrderServiceTest(
        private val orderService: OrderService,
        @MockBean private val mockRepository: OrderRepository
    ) {
        // ... 测试方法
    }
    

    需要在build.gradle.kts或pom.xml中确保引入了spring-boot-test-autoconfigure依赖。

  3. 惰性初始化与断言:如果必须用lateinit,一个务实的做法是在每个测试方法开始或@BeforeEach中,使用Kotlin标准库的isInitialized进行检查,或者用断言来尽早暴露问题。

    @SpringBootTest
    class OrderServiceTest {
        @Autowired
        private lateinit var orderService: OrderService
    
        @BeforeEach
        fun setUp() {
            // 确保属性已初始化,否则快速失败
            assert(this::orderService.isInitialized) {
                "orderService was not autowired. Check test context configuration."
            }
        }
        // ... 测试方法
    }
    

    这虽然增加了样板代码,但至少能让错误更早、更清晰地暴露出来。

提示:对于纯粹的单元测试(不启动Spring容器),你根本不应该使用@Autowired。直接手动构造被测对象,并通过构造函数传入Mock依赖即可。lateinit在这种场景下是完全不必要的。

3. 场景三:继承与父类中的lateinit属性

当lateinit属性定义在父类中,而子类由Spring管理时,情况会变得复杂。父类的lateinit属性可能被子类继承,但Spring的注入是针对具体子类Bean的。如果处理不当,父类的lateinit属性可能永远没有机会被初始化。

错误示例:

open abstract class BaseService {
    @Autowired
    protected lateinit var commonConfig: CommonConfig // 在父类中声明

    open fun init() {
        // 一些基于commonConfig的初始化逻辑
        println(commonConfig.appName) // 风险点!
    }
}

@Service
class MyConcreteService : BaseService() {
    @PostConstruct
    fun myInit() {
        init() // 调用父类方法,此时commonConfig可能还未注入!
    }
}

在上面的代码中,MyConcreteService的Bean创建过程是:Spring实例化MyConcreteService -> 调用其构造函数(隐含调用父类构造函数)-> 进行依赖注入(设置commonConfig)-> 执行@PostConstruct方法myInit()。问题在于,父类BaseService的init()方法在myInit()中被调用时,Spring可能还没有完成对commonConfig的注入(尽管从生命周期上看@PostConstruct在注入之后,但父类方法的调用时机仍需小心)。

解决方案:明确生命周期与依赖

  1. 避免在父类构造函数或初始化块中访问lateinit属性:这是铁律。父类的构造函数在子类Bean属性注入之前运行。
  2. 使用@PostConstruct进行初始化:将父类中需要依赖的初始化逻辑,封装到一个用@PostConstruct标注的方法中。确保这个方法在依赖注入完成后被调用。
    open abstract class BaseService {
        @Autowired
        protected lateinit var commonConfig: CommonConfig
    
        @PostConstruct // 确保在依赖注入后执行
        protected open fun initialize() {
            // 安全的初始化逻辑
            println("App name: ${commonConfig.appName}")
        }
    }
    
    @Service
    class MyConcreteService : BaseService() {
        // 不需要显式调用,Spring会处理父类的@PostConstruct
        // 也可以选择重写initialize方法,但记得调用super.initialize()
    }
    
    Spring会保证一个Bean的所有@PostConstruct方法(包括继承链上的)都在依赖注入完成后执行。
  3. 考虑将共享依赖作为构造函数参数:如果设计允许,可以将公共依赖通过子类的构造函数传递到父类。这需要修改父类为开放构造函数。
    open abstract class BaseService(protected val commonConfig: CommonConfig) {
        // commonConfig 作为构造函数参数,初始化时机绝对明确
        init {
            println("App name: ${commonConfig.appName}") // 安全
        }
    }
    
    @Service
    class MyConcreteService(
        commonConfig: CommonConfig // 子类注入,传递给父类
    ) : BaseService(commonConfig) {
        // ...
    }
    
    这种方式彻底消除了lateinit,是最清晰、最安全的做法。

4. 场景四:Bean的作用域(Scope)与非单例Bean

lateinit的另一个隐含假设是:属性一旦被初始化,在其整个生命周期内都是可用的。这对于Spring默认的singleton作用域Bean是成立的。但是,如果你将Bean的作用域设置为prototype、request、session等,情况就不同了。

对于prototype作用域的Bean,每次请求都会创建一个新实例。Spring容器负责创建和依赖注入,但如果你在Bean的构造函数中(在注入发生前)就尝试访问一个lateinit属性,就会出问题。更隐蔽的是,如果你的prototype Bean被另一个Bean通过ObjectFactory或Provider延迟获取,初始化时机可能更加微妙。

错误示例:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
class PrototypeProcessor {
    @Autowired
    private lateinit var expensiveService: ExpensiveService

    fun process(data: String): String {
        // 假设expensiveService的初始化非常耗时
        return expensiveService.transform(data)
    }
}

@Service
class OrchestratorService {
    @Autowired
    private lateinit var processorProvider: ObjectFactory<PrototypeProcessor>

    fun batchProcess(items: List<String>) {
        items.parallelStream().forEach { item ->
            val processor = processorProvider.getObject() // 每次获取新的prototype实例
            // 在多线程环境下,新实例的expensiveService注入可能尚未完成?
            processor.process(item)
        }
    }
}

在上面的并行处理中,每个新创建的PrototypeProcessor实例都需要注入expensiveService。如果注入过程本身有耗时或依赖其他尚未准备好的Bean,就可能出现一个线程获取到的实例其lateinit属性还未被初始化的竞态条件。

解决方案:作用域感知与替代方案

  1. 对非单例Bean保持警惕:仔细评估是否真的需要prototype等作用域。很多时候,无状态的singleton配合线程安全的设计是更好的选择。
  2. 确保初始化在安全时机完成:对于prototype Bean,避免在构造函数中访问lateinit属性。将依赖的使用推迟到业务方法中,并相信Spring的注入在方法被调用前已经完成(通常这是成立的,除非有复杂的循环依赖)。
  3. 使用Provider或ObjectFactory进行延迟查找(针对依赖本身):如果你依赖的Bean本身也是prototype或者有复杂的初始化逻辑,可以考虑注入Provider,在需要时才获取实例。
    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    class PrototypeProcessor(
        private val expensiveServiceProvider: Provider<ExpensiveService>
    ) {
        fun process(data: String): String {
            // 在方法内部获取依赖,确保每次都是最新的
            val service = expensiveServiceProvider.get()
            return service.transform(data)
        }
    }
    
    但这会将依赖的获取时机从Bean创建推迟到方法调用,需要权衡。
  4. 回归根本:构造函数注入:是的,又是它。对于prototype Bean,使用构造函数注入能最清晰地表达其依赖关系,Spring在创建实例时会立即解决这些依赖,避免了lateinit在生命周期上的模糊性。
    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    class PrototypeProcessor(
        private val expensiveService: ExpensiveService // 构造时即确定
    ) {
        fun process(data: String): String {
            // expensiveService 在对象诞生时就已就绪
            return expensiveService.transform(data)
        }
    }
    

5. 场景五:配置类(@Configuration)与Bean定义方法中的依赖

在@Configuration类中,我们经常使用@Bean方法来定义Bean。有时,在@Bean方法内部,我们需要引用其他已经定义好的Bean。如果这个@Configuration类使用了@Autowired lateinit来持有这些依赖,并在@Bean方法中访问它们,就可能遇到顺序问题。

错误示例:

@Configuration
class AppConfig {
    @Autowired
    private lateinit var dataSource: DataSource // 依赖另一个Bean

    @Bean
    fun jdbcTemplate(): JdbcTemplate {
        // 这里访问lateinit的dataSource,风险很高!
        // 因为dataSource这个Bean的创建和注入顺序,可能晚于jdbcTemplate这个Bean的创建。
        return JdbcTemplate(dataSource)
    }

    @Bean
    fun transactionManager(): DataSourceTransactionManager {
        // 同样的问题
        return DataSourceTransactionManager(dataSource)
    }
}

Spring容器在初始化时,会处理@Configuration类,调用其中的@Bean方法来创建Bean。这些方法的调用顺序是不确定的(除非使用@DependsOn)。如果dataSource Bean还没有被创建和注入到AppConfig实例中,那么调用jdbcTemplate()方法就会触发lateinit异常。

解决方案:将依赖作为@Bean方法的参数

Spring容器在调用@Bean方法时,具备方法参数自动注入的能力。这是解决此类问题最优雅、最安全的方式。

正确姿势:

@Configuration
class AppConfig {
    // 不再需要lateinit属性

    @Bean
    fun jdbcTemplate(dataSource: DataSource): JdbcTemplate { // 参数注入
        // dataSource由Spring在调用此方法时自动传入
        return JdbcTemplate(dataSource)
    }

    @Bean
    fun transactionManager(dataSource: DataSource): DataSourceTransactionManager {
        return DataSourceTransactionManager(dataSource)
    }
}

这种方式的好处非常明显:

  • 消除了lateinit:无需可变属性,代码更安全。
  • 明确了依赖关系:从方法签名就能一眼看出创建这个Bean需要什么。
  • 由Spring保证顺序:Spring会先解析出DataSource Bean,然后在调用jdbcTemplate(dataSource)时将其作为参数传入。如果DataSource Bean本身创建失败,jdbcTemplate根本不会被创建,错误更清晰。
  • 便于测试:你可以很容易地传入一个Mock的DataSource来测试jdbcTemplate()方法。

注意:这种方法要求DataSource Bean必须在同一个应用上下文(或父上下文)中可用。它完美契合了Spring的依赖注入哲学。

超越lateinit:更优雅的Kotlin依赖管理模式

聊了这么多lateinit的坑,其实核心思想是:在Spring+Kotlin的世界里,我们应该尽可能避免使用lateinit var进行属性注入。Kotlin提供了比Java更强大的表达能力,让我们可以写出更安全、更简洁的依赖管理代码。

  1. 首选构造函数注入:这是黄金法则。使用val声明不可变属性,通过主构造函数接收依赖。代码不可变,线程安全,初始化时机绝对明确。

    @Service
    class UserService(
        private val userRepository: UserRepository,
        private val emailService: EmailService,
        @Value("\${app.feature.enabled}") private val isFeatureEnabled: Boolean
    ) {
        // 业务逻辑
    }
    
  2. 善用by lazy进行惰性注入:如果某个依赖非常昂贵,且不是所有代码路径都需要,可以考虑使用by lazy。注意,by lazy是线程安全的。

    @Service
    class ReportService(
        private val dataSource: DataSource
    ) {
        // 只有需要生成复杂报告时,才初始化这个昂贵的模板
        private val complexReportTemplate by lazy {
            ExpensiveReportTemplate(dataSource)
        }
    
        fun generateSimpleReport() { /* 不使用 template */ }
        fun generateComplexReport() {
            val report = complexReportTemplate.build() // 第一次访问时初始化
            // ...
        }
    }
    

    但请谨慎使用,确保这不是为了掩盖循环依赖或设计问题。

  3. 使用@ConfigurationProperties绑定配置:对于一组配置属性,使用@ConfigurationProperties比一堆@Value更类型安全、更易于管理。

    @ConfigurationProperties(prefix = "app.mail")
    data class MailProperties(
        val host: String,
        val port: Int = 587,
        val username: String,
        val from: String
    )
    
    @Service
    class NotificationService(
        private val mailProperties: MailProperties // 注入配置类
    ) {
        fun sendAlert() {
            println("Connecting to ${mailProperties.host}:${mailProperties.port}")
        }
    }
    
  4. 考虑使用Kotlin特有的Spring扩展:Spring官方对Kotlin的支持越来越好。例如,你可以使用spring-boot-starter-webflux配合Kotlin协程,或者使用BeanDefinitionDSL以更Kotlin化的方式声明Bean。虽然这些不一定直接解决lateinit问题,但它们代表了更现代、更地道的集成方式。

回过头看,lateinit的设计初衷是为了解决那些框架强制的、生命周期导致的“不能立即初始化”的问题。在Android开发或某些纯Kotlin框架中,它可能很有用。但在Spring这种拥有成熟、明确的生命周期和依赖注入容器的框架里,我们完全有更优的选择。把lateinit当作最后的手段,而不是首选方案,你的Kotlin+SpringBoot代码会健壮得多。下次当你的手指习惯性地敲出@Autowired private lateinit var时,不妨停一下,问问自己:“真的不能用构造函数参数吗?”

Kotlin + SpringBoot读取配置文件 之前在网上搜了好久,但大多都是别人拷贝的内容,使用后还是“lateinit var XXX is not inited”(好像是这个错误:))。 @Configuration @PropertySource("config/source.properties") open class BeanConfig: Environment{ @Autowired lateinit var env: Environment } 每次启动使用env读取配置文件内容时,就报env未初始化。 最后直 阅读详情

相关推荐

【SpringBoot 3.x 第90节】Spring Boot 与 Kotlin 的天作之合,一文带你搞透!

🏆本文收录于《滚雪球学SpringBoot 3.x》,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。

**My Coding Family** 252

SpringBoot项目迁移到Kotlin必看:@Autowired和@Transactional的正确打开方式

本文针对SpringBoot项目迁移至Kotlin时常见的依赖注入与事务管理问题,详细解析了@Autowired和@Transactional注解的正确使用方式。重点探讨了Kotlin默认final类与Spring代理机制的冲突,并提供了构造函数注入、属性注入及使用all-open插件等多种解决方案,帮助开发者避免lateinit属性未初始化等典型错误,确保事务正常生效。

wine的专栏 99

三相并网逆变器预测电流控制simulink仿真模型

内容概要:本文介绍了一个关于三相并网逆变器预测电流控制的Simulink仿真模型,重点实现了基于预测控制策略的电流环设计,以提升并网电能质量与动态响应性能。该模型涵盖了逆变器主电路、空间矢量脉宽调制(SVPWM)、锁相环(PLL)、坐标变换(如abc/dq0)以及电流预测控制算法等核心模块,能够有效实现对并网电流的精确跟踪与谐波抑制。仿真模型可应用于弱电网条件下的稳定性分析、电流控制策略优化及新型控制算法验证,部分资源附带参考文献与设计文档,便于科研复现与深入研究。; 适合人群:电力电子、电气工程及其自动化等相关专业的高校研究生、科研人员及从事新能源并网技术开发的工程师。; 使用场景及目标:①用于研究三相并网逆变器在不同电网条件下的电流控制性能;②验证模型预测控制(MPC)在电流环中的应用效果;③支撑科研论文的仿真实验部分,辅助完成控制算法的设计与对比分析; 阅读建议:建议结合相关电力电子与电机控制理论基础,按照资源目录顺序逐步学习,重点关注控制算法与仿真模型的对应关系,并利用提供的参考文献深化理解,提升科研效率与创新能力。

Kotlin应用——使用kt进行web开发 & 使用h2database进行初始化数据库 & mybatis-plus使用

1.使用kt进行web开发; 2.使用h2database进行初始化数据库; 3.使用 lateinit 关键字,变量在定义时不需要初始化;

Arya的博客,专注后端领域 1598

三相Z源逆变器四种升压控制、三次谐波最大升压控制LCL滤波【附参考资料】

内容概要:本文系统研究了三相Z源逆变器的升压控制技术,重点分析并实现了四种常规升压控制方法与基于三次谐波注入的最大升压控制策略,结合LCL滤波器进行系统优化设计,有效提升了电压增益与输出波形质量。通过Simulink搭建完整的仿真模型,对不同控制策略在动态响应、稳态精度及系统稳定性方面的性能进行了对比验证,提供了详实的参考资料以支持理论推导与仿真复现。研究成果对于提升可再生能源系统中逆变器的电压转换能力与运行可靠性具有重要应用价值。; 适合人群:电力电子、电气工程及其自动化等相关专业的研究生、科研人员及从事新能源发电系统开发的工程技术人员。; 使用场景及目标:①深入理解Z源逆变器的独特升压机制及其与传统逆变器的区别;②掌握多种升压控制策略的设计原理与仿真实现方法;③应用于光伏、风电等需要高升压比和高质量并网性能的电力变换系统中,优化系统整体效率与稳定性。; 阅读建议:建议结合文中的仿真模型与参考文献,动手实践Simulink建模与参数调试,重点关注控制策略对升压效果、谐波抑制及滤波器动态特性的影响,从而深化对Z源逆变器控制机理的理解,并推动其在实际工程中的应用转化。

高校技术转移办公室人员在推进成果对接时,如何提升匹配效率?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

Java 代码转换成 Kotlin代码的一些总结

Java的Public类转换成Kotlin代码后,类前要加open Java类中的静态方法,转换成Kotlin代码后,静态方法放在Kotlin类中的 companion object 代码块中 lombok插件不能在kotlin代码中使用 Slf4j日志库不能在Kotlin中使用,但是可以使用 mu.KotlinLogging @Autowried注解的使用 java中使用 @Autowired private SysLoginService loginService; kotlin中.

weixin_32196893的博客 1549

产业园区运营负责人如何高效对接企业技术需求与科研资源?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

Kotlin实战之获取本地配置文件、远程Apollo配置失败问题排查

Kotlin作为一门JVM脚本语言,收到很多Java开发者的青睐。项目采用Java+Kotlin混合编程。Spring Boot应用开发,不会发生变动的配置放在本地配置文件,可能会变化的配置放在远程Apollo Server。

lonelymanontheway的博客 1182

基于下垂控制的构网型逆变器功率环并网控制Matlab仿真【附参考文献】

内容概要:本文系统阐述了基于下垂控制的构网型逆变器功率环并网控制策略的Matlab/Simulink仿真研究,深入剖析了构网型逆变器在微电网中的核心作用,重点在于通过下垂控制实现对有功功率/频率、无功功率/电压的自主调节,确保系统在并网与孤岛模式间的无缝切换与稳定运行。文章详细介绍了下垂控制的理论基础、控制环路设计、关键参数整定方法,并构建了完整的多场景仿真模型,涵盖了负载突变、多机并联、功率动态响应等工况,全面验证了控制策略的有效性、鲁棒性及良好的功率均分能力,同时附有详尽的参考文献以供理论溯源与深入探究。; 适合人群:电力电子、电气工程、自动化等相关专业的高校研究生、科研人员及从事新能源并网、微电网控制系统开发的工程技术人员。; 使用场景及目标:①用于教学与科研中深入理解构网型逆变器的自主运行机理与下垂控制的实现细节;②为微电网工程中逆变器并网控制系统的方案设计与参数优化提供直接的技术参考;③支撑新能源发电系统(如光伏、风电)配套储能的频率支撑与电压调节功能开发;④作为学术研究的基础,用于复现经典控制算法或在此之上进行自适应下垂、虚拟阻抗等先进控制策略的创新与验证。; 阅读建议:建议读者结合所提供的Matlab仿真模型进行实操,通过修改参数、设置不同扰动工况来观察系统动态响应,从而将理论分析与仿真结果紧密结合,深化理解;同时应仔细研读所附参考文献,以拓宽对下垂控制在复杂微网环境(如弱电网、非线性负载)下应用挑战与前沿解决方案的认知,鼓励在此仿真平台上进行二次开发,探索如基于人工智能的参数自整定等优化路径以进一步提升系统性能。

STM32CUBEMX 使用教程5 — DMA配置 & 串口结合DMA实现数据搬运

DMA(Direct Memory Access),直接存储器访问(或数据搬运),是STM32中很实用的一个外设。DMA 传输数据方式不需要 CPU 参与控制传输,也不用中断处理的方式进行数据搬运处理,纯粹通过硬件为 RAM 与 I/O 设备之间开辟一条直接传送数据的通路,使 CPU 的效率大大提高。

weixin_43866583的博客 1497

Kotlin优于Java的好处:后端和Android

开始之前 (Before We Begin) I have been using Kotlin for a while now and I believe it has the right set of features for developers. I will be sharing the features that worked really well for both Spring B...

weixin_26739079的博客 607

基于MediaPipe的智能俯卧撑计数与姿势矫正系统

在现代健身和体能训练中,俯卧撑是最基础也是最有效的自重训练动作之一。然而,许多人在进行俯卧撑训练时常常存在姿势不正确、计数不准确等问题。本文将介绍如何利用计算机视觉和姿态估计技术,开发一个智能的俯卧撑计数与姿势矫正系统。

qq_36224726的博客 911

Kotlin 与 Spring boot整合,@Value的问题

Kotlin 与 Spring boot整合,@Value的问题 Java与Spring boot集成@Value的用法 ... @Value("${url}") private String url; ... Kotlin中无法这么使用,因为"${xxx}"在kotlin里面会被编译器解析 我们来看下kotlin的语法 val s = ...

weixin_33750452的博客 461

Tiny windows 11 安装与部署

需要注意,安装完后键入 windows 键,输入 update 先更新系统。点击开始,等待完成即可,用户可自定义。

m0_73351035的博客 9025

浏览器缓存导致代码不更新?一招解决部署后用户仍看到旧页面的问题

方案效果适用场景文件名哈希永久解决前端工程化项目HTTP缓存头控制精准控制需服务器权限Meta标签部分生效临时补救方案最佳实践:入口HTML禁用缓存,静态资源使用哈希文件名 + 长期缓存,达到「秒级更新」+「高性能」的平衡!

江拥羡橙的博客 806

Kotlin属性揭秘与延迟初始化特性

在上一次https://www.cnblogs.com/webor2006/p/11210181.html学习了Kotlin的伴生对象,这次来学习属性相关的东东。 属性揭秘: 先声明一个属性: 没啥可说的呀,很简单嘛,其实。。有深层次的东东,表现上看貌似是直接调用的age这个属性,其实是调用age的get方法,下面来看一个完整的例子: 这里就可以给这个属性定义一个get方法来将该属性进行初始...

webor2006的博客 151

译码器Multisim电路仿真汇总——硬件工程师笔记

本文详细介绍了74LS139D和74LS138D两种TTL译码器的功能特点及应用。74LS139D是2线-4线译码器,具有低电平有效输出和使能控制功能,常用于地址译码和数据分配。74LS138D是3线-8线译码器,通过3位输入实现8路输出控制,广泛应用于微处理器系统。文中提供了两种芯片的引脚说明、真值表和典型应用电路,并附有硬件工程师笔试面试相关资源链接。这些基础知识对数字电路设计和硬件工程师面试准备具有重要参考价值。

XU157303764的博客 1252

spring boot + jpa + kotlin入门实例

spring boot +jpa的文章网络上已经有不少,这里主要补充一下用kotlin来做。 kotlin里面的data class来创建entity可以帮助我们减少不少的代码,比如现在这个User的Entity,这是Java版本的: @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AU...

weixin_30474613的博客 174

手把手教你学Simulink--复杂环境适应的四足机器人领域仿真建模实例:构建一个基于Simulink的四足机器人动态平衡保持仿真模型

通过上述步骤,你可以构建一个基本的基于Simulink的四足机器人动态平衡保持仿真模型。此模型不仅有助于理解四足机器人在复杂环境中的动态平衡控制原理,也为进一步优化提供了基础。随着技术的发展,还可以探索更多高级功能,如自适应步态调整、强化学习等,以提高机器人的智能性和适应性。

MHD0815的博客 228

轮式并联五连杆下肢机器人运动控制:从建模到强化学习仿真训练

本文提出了一种基于强化学习的轮式并联五连杆下肢机器人运动控制方法。首先建立了机器人的运动学和动力学模型,推导了正逆解公式,并构建了URDF模型。随后在PyBullet仿真环境中搭建了包含复杂地形的训练场景,设计了基于PPO算法的端到端控制策略,包含状态空间、动作空间和奖励函数的详细设计。通过多组仿真实验验证了该方法在台阶攀爬等任务中的有效性,为轮腿机器人运动控制提供了完整的可复现技术方案。

huanghm88的专栏 342

resttemplate使用_SpringBoot 2.X Kotlin系列之RestTemplate配置及使用

自从RESTFul API兴起后,Spring就给开发者提供了一个访问Rest的客服端,RestTemplate不仅可以很方便的调用http接口,还可以调用同一注册中心下的微服务,同时还有负载均衡和熔断机制。当然我也听说OKhttp,HTTPClient之类的网络框架,但是我们这里重点讲的是RestTemplate(其中一种实现就是基于HTTPClient实现的),因为这是SpringBoot自带...

weixin_36310662的博客 325

骑砍Ⅱ霸主MOD开发(16)-FaceKey&BannerKey

摘要:本文介绍了FaceKey人物模型和BannerKey旗帜模型的生成流程。FaceKey通过序列化参数生成人物模型,包括头部(脸部、嘴、睫毛、眼睛)、眉毛、头发、身体和躯干等组件,每个部分通过特定标签和骨骼绑定实现。BannerKey则通过解析参数生成旗帜模型,包括模型、贴图和颜色等元素。两种模型都采用参数化设计和XML配置文件实现模型生成与定制。

骑砍战团&霸主MOD开发 2280

SpringBoot+Android+小程序全栈固定资产管理系统开发实践

固定资产管理系统是企业IT基础设施的重要组成部分,通过数字化手段解决传统Excel管理的痛点。系统采用状态模式实现资产全生命周期管理,结合SpringBoot后端提供RESTful API,Android原生应用实现高性能移动端操作,微信小程序提供轻量级访问入口。技术实现上涉及JPA持久层、Spring Security认证、Retrofit网络通信等企业级开发技术,采用乐观锁解决并发冲突,通过分层缓存策略优化移动端数据同步。典型应用场景包括教育机构设备管理、企业IT资产管理等,能有效提升资产利用率30%以

weixin_30482181的博客 508

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Spring Boot 应用的测试

Spring Boot 应用的测试 《Spring Boot 实战开发》(陈光剑) —— 基于 Gradle + Kotlin的企业级应用开发最佳实践 本书写到这里,Spring Boot 2.0.0.RC1版本已经于2018.1.31 发布。这是本书最后一章,本章介绍 Spring Boot 应用的测试(质量保障)相关的内容。我们在项目开发中使用分层...

AI天才研究院 8977

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

上一篇: 用STM32H743ZG自制高速U盘:FATFS+USB HS全流程解析(附移植修改点)
下一篇: ALSA驱动开发避坑指南:从Machine/Platform/Codec三模块解析声卡适配难点
orange
博客等级 码龄27年 73粉丝 1073原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值