
本教程旨在解决spring boot应用中周期性读取并持久化外部json文件数据的挑战。我们将深入探讨为何`getresourceasstream`不适用于动态更新的文件,并提供一种最佳实践方案,包括将json文件放置于外部可配置路径、利用java nio进行文件读取、以及采用构造器注入等spring boot推荐模式,确保数据能够实时更新至数据库。
在Spring Boot应用程序中,当我们需要周期性地读取一个不断更新的JSON文件,并将其数据持久化到数据库时,常常会遇到一些挑战。一个常见的误区是尝试将这些动态文件放置在src/main/resources目录下,并通过Class.getResourceAsStream()方法进行读取。
src/main/resources目录主要用于存放应用程序的静态资源,如配置文件、模板文件或静态网页内容。在应用程序打包(例如,生成JAR或WAR文件)时,这些资源会被嵌入到最终的可执行文件中,成为classpath的一部分。这意味着,一旦应用程序被打包并启动,Class.getResourceAsStream()方法将从这个静态的classpath中读取文件内容。即使原始文件系统中的src/main/resources/json/file.json被更新,应用程序也无法感知到这些变化,因为它读取的是打包时嵌入的旧版本文件。
虽然Spring的@Scheduled注解能够实现周期性任务的执行,但如果文件读取源本身是静态的,那么无论任务执行多少次,都只会获取到相同的数据,从而无法满足“读取不断更新的文件”的需求。因此,对于需要运行时动态更新的文件,必须采用不同的策略。
要解决上述问题,核心思想是将动态更新的JSON文件放置在应用程序外部的可访问路径,并使用Java标准库提供的I/O功能来读取。
建议将动态更新的JSON文件放置在应用程序外部的独立目录中,例如:
这样做的好处是,文件的更新不会影响应用程序的打包和部署,并且可以独立于应用程序进行管理。
为了使文件路径具有灵活性和可配置性,我们应该将其定义在application.properties或application.yml文件中,并通过Spring的@Value注解注入到代码中。
src/main/resources/application.properties
app.data.json-file-path=/path/to/your/external/file.json # 例如,在Linux上可能是 /opt/myapp/data/file.json # 在Windows上可能是 C:/myapp/data/file.json
注意: 请将/path/to/your/external/file.json替换为实际的外部文件路径。
我们将使用Java NIO(New I/O)来读取外部文件,因为它提供了更现代、更高效的文件操作API。
Easily find JSON paths within JSON objects using our intuitive Json Path Finder
30
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.example.demo.model.Master;
import com.example.demo.Services.MasterService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Component // 确保这个类被Spring扫描并管理
public class JsonFileProcessor {
private final MasterService masterService;
private final String jsonFilePath; // 外部JSON文件的路径
// 推荐使用构造器注入,而不是字段注入
public JsonFileProcessor(MasterService masterService,
@Value("${app.data.json-file-path}") String jsonFilePath) {
this.masterService = masterService;
this.jsonFilePath = jsonFilePath;
}
@Scheduled(fixedRate = 90000) // 每90秒执行一次
public void readAndUpdateDatabase() {
ObjectMapper mapper = new ObjectMapper();
TypeReference<List<Master>> typeReference = new TypeReference<List<Master>>(){};
try {
// 使用Java NIO读取外部文件
Path path = Paths.get(jsonFilePath);
// 检查文件是否存在且可读
if (!Files.exists(path) || !Files.isReadable(path)) {
System.err.println("Error: JSON file not found or not readable at " + jsonFilePath);
return;
}
// 读取文件所有字节并反序列化
List<Master> masters = mapper.readValue(Files.readAllBytes(path), typeReference);
System.out.println("Read " + masters.size() + " records from " + jsonFilePath);
// 将读取到的数据保存到数据库
masterService.saveAll(masters); // 假设MasterService有一个saveAll方法
System.out.println("Saved " + masters.size() + " records to database.");
} catch (IOException e) {
System.err.println("Unable to read or save masters from " + jsonFilePath + ": " + e.getMessage());
e.printStackTrace();
}
}
}为了使上述解决方案与Spring Boot应用程序无缝集成,并遵循最佳实践,我们需要对现有代码进行一些调整。
Spring官方推荐使用构造器注入(Constructor Injection)而非字段注入(Field Injection)。构造器注入使得依赖关系更加明确,方便测试,并有助于避免循环依赖问题。
com.example.demo.Services.MasterService
package com.example.demo.Services;
import com.example.demo.Repository.MasterRepository;
import com.example.demo.model.Master;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MasterService {
private final MasterRepository masterRepository; // 使用final关键字
// 构造器注入
public MasterService(MasterRepository masterRepository) {
this.masterRepository = masterRepository;
}
public Iterable<Master> list() {
return masterRepository.findAll();
}
public Master save(Master master){
return masterRepository.save(master);
}
// 推荐添加一个saveAll方法来批量保存
public Iterable<Master> saveAll(List<Master> masters) {
return masterRepository.saveAll(masters);
}
}确保Spring Boot主应用程序类启用了定时任务调度和事务管理。
com.example.demo.ReadAndWriteJsonApplication
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableScheduling // 启用定时任务调度
@EnableTransactionManagement // 启用事务管理
public class ReadAndWriteJsonApplication {
public static void main(String[] args) {
SpringApplication.run(ReadAndWriteJsonApplication.class, args);
}
// 原有的readFile方法可以移除,由JsonFileProcessor类处理
// 如果有其他初始化逻辑,可以考虑使用@PostConstruct
}注意: 原代码中main方法里调用的TimerTaskUtil如果不是Spring管理的Bean,通常不建议在Spring Boot应用中这样启动独立线程,因为它可能脱离Spring的生命周期管理。如果需要定时任务,@Scheduled是首选。
MasterRepository保持不变,它继承了Spring Data JPA的CrudRepository,提供了基本的CRUD操作。
com.example.demo.Repository.MasterRepository
package com.example.demo.Repository;
import com.example.demo.model.Master;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MasterRepository extends CrudRepository<Master, Long> {
// 可以根据需要添加自定义查询方法
}通过将动态更新的JSON文件外部化,并结合Spring Boot的@Value注解进行路径配置、Java NIO进行文件读取、以及@Scheduled注解进行周期性任务调度,我们可以构建一个健壮且可维护的应用程序,实现对外部动态JSON文件的实时数据同步。同时,遵循Spring推荐的构造器注入等最佳实践,将有助于提升代码质量和可测试性。
以上就是Spring Boot中动态读取并持久化外部JSON文件数据教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号