
1. 背景与问题描述
在现代应用开发中,从第三方API获取数据是常见需求。Spring框架的RestTemplate是一个强大的HTTP客户端,用于同步地进行RESTful服务调用。然而,获取到的数据往往需要进一步处理才能满足业务需求,例如过滤特定条件的数据、去除重复项,或者将原始数据结构转换为更符合内部API或前端展示的格式。
假设我们通过RestTemplate获取了一个包含省份信息的JSON数据,并将其映射到Provinces和ProvincesData等Java对象。原始数据可能包含重复的省份代码(CODPROV)或自治社区代码(CODAUTON),并且我们可能希望只保留不重复的自治社区信息,甚至将这些信息以自定义的字段名呈现。直接操作原始的List
2. 初始数据模型与RestTemplate配置
首先,我们定义了用于映射API响应的Java类:Provinces作为顶层容器,ProvincesData包含具体的省份信息。
// Provinces.java
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Provinces {
@JsonProperty("provincial")
private List provinces;
public Provinces() {}
public Provinces(List provinces) {
this.provinces = provinces;
}
@JsonProperty("provincial")
public List getProvinces() { // 注意:方法名已修正为标准Java Bean规范
return provinces;
}
// 注意:原始问题中setter带有 @JsonProperty("Test"),这可能导致非预期行为。
// 在实际开发中,如果JSON字段名为"provincial",则setter不应有不同的@JsonProperty。
// 此处为演示目的,假设setter对应的是"provincial"或无特定注解。
public void setProvinces(List provinces) { // 方法名已修正
this.provinces = provinces;
}
} // ProvincesData.java
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ProvincesData {
@JsonProperty("CODPROV")
private String codProv;
@JsonProperty("NOMBRE_PROVINCIA")
private String nomeProvincia;
@JsonProperty("CODAUTON")
private String codAuton;
@JsonProperty("COMUNIDAD_CIUDAD_AUTONOMA")
private String comunidadeCidadeAutonoma;
public ProvincesData() {}
public ProvincesData(String codProv, String nomeProvincia, String codAuton, String comunidadeCidadeAutonoma) {
this.codProv = codProv;
this.nomeProvincia = nomeProvincia;
this.codAuton = codAuton;
this.comunidadeCidadeAutonoma = comunidadeCidadeAutonoma;
}
@JsonProperty("CODPROV")
public String getCodProv() {
return codProv;
}
// 注意:原始问题中setter带有 @JsonProperty("Test"),此处修正为标准setter。
public void setCodProv(String codProv) {
this.codProv = codProv;
}
@JsonProperty("NOMBRE_PROVINCIA")
public String getNomeProvincia() {
return nomeProvincia;
}
public void setNomeProvincia(String nomeProvincia) {
this.nomeProvincia = nomeProvincia;
}
@JsonProperty("CODAUTON")
public String getCodAuton() {
return codAuton;
}
public void setCodAuton(String codAuton) {
this.codAuton = codAuton;
}
@JsonProperty("COMUNIDAD_CIUDAD_AUTONOMA")
public String getComunidadeCidadeAutonoma() {
return comunidadeCidadeAutonoma;
}
public void setComunidadeCidadeAutonoma(String comunidadeCidadeAutonoma) {
this.comunidadeCidadeAutonoma = comunidadeCidadeAutonoma;
}
}接下来是RestTemplate的调用以及服务层和控制器层的基本结构:
// Templates.java (或一个工具类)
import org.springframework.web.client.RestTemplate;
public class Templates {
public static Provinces restTemplateProvince(RestTemplate restTemplate) {
String ProvinceCommunityURL = "https://www.el-tiempo.net/api/json/v2/provincias";
Provinces province = restTemplate.getForObject(ProvinceCommunityURL, Provinces.class);
return province;
}
}
// ProvinceService.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class ProvinceService {
@Autowired
private RestTemplate restTemplate; // 确保 RestTemplate 已在配置中定义为 Bean
public Provinces getAllProvinces() {
Provinces listOfProvinces = Templates.restTemplateProvince(restTemplate);
return listOfProvinces;
}
}
// ShowcaseController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ShowcaseController {
@Autowired
private ProvinceService provinceService;
@GetMapping("/provinces")
public Provinces getAllProvinces() {
return provinceService.getAllProvinces();
}
}3. 数据过滤与去重
我们的目标是过滤掉ProvincesData列表中codAuton重复的项,只保留每个codAuton的第一个出现实例。这可以通过Java Stream API结合一个辅助集合来实现。
修改Templates.restTemplateProvince方法,在获取到原始数据后立即进行过滤:
// Templates.java (更新后的版本)
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Templates {
public static Provinces restTemplateProvince(RestTemplate restTemplate) {
String ProvinceCommunityURL = "https://www.el-tiempo.net/api/json/v2/provincias";
Provinces province = restTemplate.getForObject(ProvinceCommunityURL, Provinces.class);
// 如果获取到的数据或其内部列表为空,则直接返回
if (province == null || province.getProvinces() == null) {
return new Provinces(new ArrayList<>()); // 返回一个空的Provinces对象
}
List includedCodAutons = new ArrayList<>(); // 用于跟踪已包含的codAuton
List filteredProvinces = province.getProvinces()
.stream()
.filter(p -> {
// 如果 includedCodAutons 中已包含当前 codAuton,则过滤掉
if (includedCodAutons.contains(p.getCodAuton())) {
return false;
} else {
// 否则,将其添加到 includedCodAutons 并保留该元素
includedCodAutons.add(p.getCodAuton());
return true;
}
})
.collect(Collectors.toList());
// 将过滤后的列表设置回 Provinces 对象
province.setProvinces(filteredProvinces);
return province;
}
} 代码解析:
- includedCodAutons:一个ArrayList,用于存储已经处理过的codAuton值。
- province.getProvinces().stream():将ProvincesData列表转换为流。
- .filter(p -> { ... }):对流中的每个元素应用过滤逻辑。
- 在filter的lambda表达式中,我们检查当前ProvincesData对象的codAuton是否已存在于includedCodAutons中。
- 如果存在,则返回false,表示该元素将被过滤掉。
- 如果不存在,则将其codAuton添加到includedCodAutons中,并返回true,表示保留该元素。
- .collect(Collectors.toList()):将过滤后的流重新收集为一个新的List
。 - province.setProvinces(filteredProvinces):用过滤后的列表更新Provinces对象。
注意事项:
-
效率考虑: 使用ArrayList.contains()进行重复检查的时间复杂度为O(n)。如果数据量非常大,使用HashSet
(new HashSet()) 来存储includedCodAutons会更高效,因为HashSet.contains()的平均时间复杂度为O(1)。 - 数据完整性: 这种过滤方式会保留每个codAuton第一次出现的ProvincesData对象。如果需要基于其他条件(如最新数据、特定属性值)选择要保留的重复项,则需要更复杂的逻辑。
4. 数据转换与自定义输出格式
仅仅过滤去重可能不足以满足所有需求。有时我们还需要将数据转换为一个全新的结构,例如只包含特定字段、字段名称不同,或者完全映射为List
假设我们希望最终API返回的数据是一个包含自治社区代码和名称的简化列表,并且字段名更具可读性。我们可以定义一个新的DTO(Data Transfer Object)来表示这种简化结构。
// SimplifiedCommunityInfo.java
public class SimplifiedCommunityInfo {
private String communityCode;
private String communityName;
public SimplifiedCommunityInfo(String communityCode, String communityName) {
this.communityCode = communityCode;
this.communityName = communityName;
}
// Getters and Setters
public String getCommunityCode() {
return communityCode;
}
public void setCommunityCode(String communityCode) {
this.communityCode = communityCode;
}
public String getCommunityName() {
return communityName;
}
public void setCommunityName(String communityName) {
this.communityName = communityName;
}
}现在,我们可以在ProvinceService中对从Templates.restTemplateProvince获取到的数据进行进一步的转换:
// ProvinceService.java (更新后的版本,包含转换逻辑)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ProvinceService {
@Autowired
private RestTemplate restTemplate;
// 返回原始Provinces对象(已过滤)
public Provinces getFilteredProvinces() {
return Templates.restTemplateProvince(restTemplate);
}
// 返回自定义DTO列表
public List getSimplifiedCommunityInfo() {
Provinces filteredProvinces = Templates.restTemplateProvince(restTemplate);
if (filteredProvinces == null || filteredProvinces.getProvinces() == null) {
return List.of(); // 返回空列表
}
return filteredProvinces.getProvinces()
.stream()
.map(p -> new SimplifiedCommunityInfo(p.getCodAuton(), p.getComunidadeCidadeAutonoma()))
.collect(Collectors.toList());
}
// 如果需要返回 List
public List getCommunityNamesAsStringList() {
Provinces filteredProvinces = Templates.restTemplateProvince(restTemplate);
if (filteredProvinces == null || filteredProvinces.getProvinces() == null) {
return List.of(); // 返回空列表
}
return filteredProvinces.getProvinces()
.stream()
.map(p -> p.getComunidadeCidadeAutonoma() + " (" + p.getCodAuton() + ")")
.collect(Collectors.toList());
}
} 相应的,ShowcaseController也需要更新以调用新的服务方法并返回期望的类型:
// ShowcaseController.java (更新后的版本)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class ShowcaseController {
@Autowired
private ProvinceService provinceService;
@GetMapping("/provinces/filtered")
public Provinces getFilteredProvinces() {
return provinceService.getFilteredProvinces();
}
@GetMapping("/provinces/simplified")
public List getSimplifiedCommunityInfo() {
return provinceService.getSimplifiedCommunityInfo();
}
@GetMapping("/provinces/names")
public List getCommunityNames() {
return provinceService.getCommunityNamesAsStringList();
}
} 代码解析:
- stream().map(...):这是Stream API中用于转换元素的核心操作。它接受一个Function作为参数,将流中的每个元素映射为另一种类型。
- 在getSimplifiedCommunityInfo方法中,我们将每个ProvincesData对象映射为一个SimplifiedCommunityInfo对象,并传入codAuton和comunidadeCidadeAutonoma作为构造参数。
- 在getCommunityNamesAsStringList方法中,我们将每个ProvincesData对象映射为一个格式化的字符串。
5. 总结与最佳实践
通过本文,我们学习了如何利用RestTemplate获取外部API数据,并结合Java Stream API对数据进行高效的过滤、去重和转换。
关键点总结:
- 数据获取: RestTemplate.getForObject()是获取JSON并直接映射到Java对象的便捷方式。
- 数据过滤与去重: 使用stream().filter()结合一个辅助集合(如ArrayList或更高效的HashSet)可以有效地实现基于特定属性的去重。
- 数据转换: stream().map()是实现数据结构转换的关键。您可以将原始对象映射到自定义的DTO、基本类型列表或格式化字符串。
- 职责分离: 将数据获取(Templates类)、业务逻辑(ProvinceService)和API暴露(ShowcaseController)的职责清晰分离,有助于代码的维护性和扩展性。
进一步的考虑:
- 错误处理: 在实际应用中,RestTemplate调用需要包含异常处理机制(如try-catch块),以应对网络问题、API返回错误或数据解析失败的情况。
- 性能优化: 对于大规模数据,除了使用HashSet进行去重外,还可以考虑并行流(parallelStream())来加速处理,但需注意并行流的线程安全问题。
- 缓存机制: 如果API数据不经常变化,可以考虑引入缓存机制(如Spring Cache),避免每次请求都调用外部API,从而提高响应速度并减轻外部API的负载。
- 统一响应格式: 在控制器层,通常会封装响应数据到一个统一的响应对象中,包含状态码、消息和实际数据,以提供更友好的API接口。










