Kotlin 是一个基于 JVM 的编程语言, 它的简洁、便利早已不言而喻;
Kotlin 能够胜任 Java 做的所有事;
SpringBoot3于2022年11月24号第一版正式发布后,
带来了许多令人兴奋的新特性和改进。
环境变化
- JDK
Spring Boot 3.0以上 需要Java 17,并且兼容 Java 20(包括 Java 20)。还需要Spring Framework 6.0.9或更高版本。
- GraalVM支持
Spring Native 也是升级的一个重大特性,支持使用 GraalVM 将 Spring 的应用程序编译成本地可执行的镜像文件,可以显著提升启动速度、峰值性能以及减少内存使用。
…
踩坑记录
1、创建项目,修改 POM 文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.2.RELEASE</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> </dependencies>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.35</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.14</version> </dependency>
/添加Kotlin 依赖 <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jdk8</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-reflect</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> </dependency>
|
在 Kotlin 中,data class 默认没有无参构造方法,并且 data class 默认为 final 类型,不可以被继承。如果我们使用 SpringBoot + Kotlin 的模式,那么使用 @autowired 就可能遇到这个问题。因此,我们可以添加 NoArg插件(kotlin-noarg) 为标注的类生成无参构造方法。使用 AllOpen插件(kotlin-allopen) 为被标注的类去掉 final,才允许被继承。
2、yml/properties配置文件
1 2 3 4 5 6 7 8 9
| 在kotlin和java混合开发时发现注解@ConfigurationProperties无效读取不到对应的属性, 需要使用注解@Value才有效。
两者区别: 两者都不能直接给static变量赋值。 @ConfigurationProperties是可以放在默认的类注解上,@Value可以读取单个配置项, 加到get/set方法或者属性名上。
注意: @Value注解中指定的系统属性名,必须存在,且必须跟配置文件中的相同。
|
3、redis/json配置
StringRedisTemplate 和 RedisTemplate 是有区别的,不过 StringRedisTemplate 实际是 RedisTemplate<String,String>; StringRedisTemplate 是经过字符串序列化可直接查看内容,而 RedisTemplate 是经过默认JDK序列化的无法直接查看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| val mapper = ObjectMapper()
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false)
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
mapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false) mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) mapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), false) mapper.serializerProvider.setNullValueSerializer(NullStringJsonSerializer()) mapper.registerModule(Java8TimeModule())
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
mapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.EVERYTHING, JsonTypeInfo.As.PROPERTY )
|
3、参数校验
在 Java 中各种注解的参数校验均有效,但是放在 Kotlin 中有些却无效。由于使用 Kotlin开发后台的资料比较少,同时经过我几天研究发现在参数校验的注解前面field:
才有效果。
如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13
| data class MailDTO( @field:Valid val from: MailSender? = null, @field:NotEmpty val sendTo: List<String> = arrayListOf(), @field:NotBlank val subject: String = "", val text: String = "", )
|
4、优化相关
knife4j vs swagger,推荐knife4j,功能强大,页面友好。
减少扫描路径,手动注入重量级Bean或者使用 starter 机制。
SpringBoot 的 starter 机制,优化下缓存组件的实现,可以做到自动注入、开箱即用。
只要改造下缓存组件的代码,在 resources 文件中添加一个 META-INF/spring.factotries 文件,
在下面配置一个 EnableAutoConfiguration 即可,这样项目在启动时也会扫描到这个 jar 中的 spring.factotries 文件,将 XxxAdCacheConfiguration 配置类自动引入,而不需要扫描"com.xxx.demo"整个路径了:
1 2 3
| org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.xxx.demo.XxxAdCacheConfiguration
|
5、反射相关
JDK17限制反射,对于包扫描和反射的权限控制更加的严格。
解决方案:
一个粗暴的解决办法是将没开放的module强制对外开放,即保持和Java9之前的版本一致。 –add-exports导出包,意味着其中的所有公共类型和成员都可以在编译和运行时访问。 –add-opens打开包,意味着其中的所有类型和成员(不仅是公共类型)都可以在运行时访问。 主要区别在于–add-opens允许深度反射,即非公共成员的访问,才可以调用setAccessible(true)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| //SGM需要加入 --add-opens java.management/java.lang.management=ALL-UNNAMED --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED --add-opens java.management/sun.management=ALL-UNNAMED
//R2M需要加入 --add-opens java.base/java.time=ALL-UNNAMED
//Ducc需要加入 --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED
//AKS需要加入 --add-exports java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED
异常:Causedby: java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException 原因:Java11 删除了 Java EE modules,其中就包括 java.xml.bind (JAXB)。
<!-- API, java.xml.bind module --> <dependency> <groupId>jakarta.xml.bind</groupId> <artifactId>jakarta.xml.bind-api</artifactId> <version>2.3.2</version> </dependency> <!-- Runtime, com.sun.xml.bind module --> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.3.2</version> </dependency>
|
但是,这种解决方案可能存在潜在的安全风险,因为它打开了一个本来是受保护的模块,允许其他模块进行未经授权的访问。因此,建议在必要时使用此选项,同时确保您的应用程序没有潜在的漏洞和安全问题
…
总结建议
SpringBoot 整合 Kotlin 非常容易,并简化 SpringBoot 应用的初始搭建以及开发过程。JDK17性能也更强,最大的亮点是可以使用亚毫秒级停顿的GC性能(至少百倍的GC性能提升),所以强烈建议升级到JDK17。