Spring框架高级特性深度解析
立即解锁
发布时间: 2025-08-19 02:30:24 阅读量: 2 订阅数: 17 


Spring 6与Kotlin:深入指南
### Spring框架高级特性深度解析
#### 1. Spring事件机制
在Spring应用中,事件机制是一种强大的工具,用于组件间的解耦通信。以下是一个简单的事件发布示例代码:
```kotlin
fun publish(message: String?) {
ctx!!.publishEvent(MessageEvent(this, message!!))
}
companion object {
@JvmStatic
fun main(args: Array<String>) {
val ctx = AnnotationConfigApplicationContext(EventsConfig::class.java)
val pub = ctx.getBean("publisher") as Publisher
pub.publish("I send an SOS to the world... ")
pub.publish("... I hope that someone gets my...")
pub.publish("... Message in a bottle")
}
}
```
在这个示例中,`Publisher` 类通过 `publish` 方法发布 `MessageEvent` 事件。`EventsConfig` 类为空,主要用于启用组件扫描,以便让Spring容器识别 `Publisher` 和 `MessageEventListener` 。运行该示例会得到如下输出:
```
INFO : MessageEventListener - Received: I send an SOS to the world...
INFO : MessageEventListener - Received: ... I hope that someone gets my...
INFO : MessageEventListener - Received: ... Message in a bottle
```
这表明 `MessageEventListener` 成功响应了 `Publisher` 发布的事件。
##### 事件使用的考虑因素
在应用中,某些组件需要在特定事件发生时得到通知。常见的做法有两种:一是编写代码显式通知每个组件;二是使用消息技术,如JMS。但显式通知会导致组件与发布者耦合,很多情况下这种耦合是不必要的。
例如,应用中缓存了产品详情以减少数据库访问,另一个组件负责修改并持久化产品详情。为避免缓存失效,更新组件需要通知缓存组件。更好的解决方案是,更新组件在每次修改产品详情时发布一个事件,感兴趣的组件(如缓存组件)监听该事件。这样可以保持组件间的解耦,便于后续移除缓存或添加新的监听器。
通常,事件适用于快速执行且不属于主应用逻辑的反应式逻辑。对于长时间运行且属于主要业务逻辑的流程,建议使用JMS或类似的消息系统,如RabbitMQ。
#### 2. 资源访问
应用常常需要以不同形式访问各种资源,如文件系统中的配置文件、类路径下的JAR文件中的图像数据,或远程服务器上的数据。Spring提供了一种与协议无关的统一资源访问机制,使得应用可以以相同的方式访问资源,无论资源存储在何处。
Spring资源支持的核心是 `org.springframework.core.io.Resource` 接口,该接口定义了十个自解释的方法:
- `contentLength()`
- `exists()`
- `getDescription()`
- `getFile()`
- `getFileName()`
- `getURI()`
- `getURL()`
- `isOpen()`
- `isReadable()`
- `lastModified()`
此外,还有一个不太直观的方法 `createRelative()` ,用于创建相对于当前实例路径的新 `Resource` 实例。
以下是一个使用 `ApplicationContext` 访问三种不同资源的示例代码:
```kotlin
package com.apress.prospring6.four
import org.springframework.core.io.Resource
// other import statements omitted
object ResourceDemo {
private val LOGGER = LoggerFactory.getLogger(ResourceDemo::class.java)
@Throws(Exception::class)
@JvmStatic
fun main(args: Array<String>) {
val ctx = AnnotationConfigApplicationContext()
val baseDir = File(System.getProperty("java.io.tmpdir"))
val filePath = Files.createFile(Path.of(baseDir.absolutePath, "test.txt"))
Files.writeString(filePath, "Hello World!")
filePath.toFile().deleteOnExit()
val res1 = ctx.getResource("file://$filePath")
displayInfo(res1)
val res2 = ctx.getResource("classpath:test.txt")
displayInfo(res2)
val res3 = ctx.getResource("http://iuliana-cosmina.com")
displayInfo(res3)
}
@Throws(Exception::class)
private fun displayInfo(res: Resource) {
LOGGER.info("Resource class: {}", res.javaClass)
LOGGER.info("Resource URL content: {}",
BufferedReader(InputStreamReader(res.url.content as InputStream)).lines().
parallel()
.collect(Collectors.joining("\n"))
)
LOGGER.info(" -------------")
}
}
```
在调用 `getResource()` 方法时,需要传入资源的URI。对于 `res1` 和 `res3` ,使用了常见的 `file:` 和 `http:` 协议;对于 `res2` ,使用了Spring特定的 `classpath:` 协议,表示在类路径中查找资源。
运行该示例会得到如下输出:
```
INFO : ResourceDemo - Resource class: class org.springframework.core.io.FileUrlResource
INFO : ResourceDemo - Resource URL content: Hello World!
INFO : ResourceDemo - -------------
INFO : ResourceDemo - Resource class: class org.springframework.core.io.ClassPathResource
INFO : ResourceDemo - Resource URL content: Hello World from the classpath!
INFO : ResourceDemo - -------------
INFO : ResourceDemo - Resource class: class org.springframework.core.io.UrlResource
INFO : ResourceDemo - Resource URL content: <html>
<center><h1>301 Moved Permanently</h1></center></body>
</html>
INFO : ResourceDemo - -------------
```
需要注意的是,访问 `http://iuliana-cosmina.com` 时返回 `301 Moved Permanently` ,因为该网站实际使用的是安全HTTP,应使用 `ctx.getResource("https://iuliana-cosmina.com")` 来提取主页的实际内容。
对于 `file:` 和 `http:` 协议,Spring默认返回 `UrlResource` 实例。如果需要 `FileSystemResource` 实例,可以使用 `FileSystemResourceLoader` 。获取 `Resource` 实例后,可以使用 `getFile()`、`getInputStream()` 或 `getURL()` 方法访问资源内容。由于 `getFile()` 在某些情况下(如使用 `http:` 协议)会抛出 `FileNotFoundException` ,建议使用 `getInputStream()` 方法。
#### 3. 高级Java/Kotlin配置类
之前介绍的Java/Kotlin配置类比较基础,下面将介绍更多的配置选项。以 `MessageRender` 和 `ConfigurableMessageProvider` 为例,假设要将消息外部化到名为 `message.properties` 的属性文件中,通过构造函数注入从该文件读取的值。`message.properties` 的内容如下:
```
message=Only hope can keep me together
```
以下是使用 `@PropertySource` 注解加载属性文件并注入消息提供者的示例代码:
```kotlin
package com.apress.prospring6.four
import com.apress.prospring6.two.decoupled.MessageProvider
import com.apress.prospring6.two.decoupled.MessageRenderer
import org.springframework.core.env.Environment
// other import statements omitted
object PropertySourcesDemo {
@JvmStatic
fun main(args: Array<String>) {
val ctx: ApplicationContext =
AnnotationConfigApplicationContext(PropertySourcesCfg::class.java)
val mr = ctx.getBean(
"messageRenderer",
MessageRenderer::class.java
)
mr.render()
}
}
@Configuration
@PropertySource(value = ["classpath:message.properties"])
internal open class PropertySourcesCfg {
@Autowired
var env: Environment? = null
@Bean
@Lazy
open fun messageProvider(): MessageProvider =
ConfigurableMessageProvider(env!!.getProperty("message"))
@Bean(name = ["messageRenderer"])
@Scope(value = "prototype")
@DependsOn(value = ["messageProvider"])
open fun messageRenderer(): MessageRenderer =
StandardOutMessageRenderer().apply {
messageProvider = messageProvider()
}
}
internal class ConfigurableMessageProvider(
@param:Value("Configurable me
```
0
0
复制全文
相关推荐










