Spring RestTemplate消费HAL API时自动填充关联对象及缓存诉求
Awesome question—you’re absolutely right to look for existing solutions instead of building this from scratch! The Spring ecosystem has dedicated tools to handle HAL hypermedia interactions, automatic link following, and caching of repeated resource requests. Here’s how to implement both requirements:
Spring HATEOAS is built explicitly for working with hypermedia APIs like HAL. It eliminates manual link parsing and exchange() calls by providing utilities to discover and follow links automatically.
Step 1: Add Spring HATEOAS Dependency
First, include the Spring HATEOAS starter in your project (Maven example):
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-hateoas</artifactId> </dependency>
Step 2: Use Traverson for Link Traversal
The Traverson class simplifies navigating hypermedia links. It handles link discovery and resource fetching out of the box. Here’s a refactored version of your method:
import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.Resource; import org.springframework.hateoas.Traverson; import org.springframework.http.ParameterizedTypeReference; import java.net.URI; public Item getItemById(Integer itemId) { // Initialize Traverson with your API base URI and HAL media type Traverson traverson = new Traverson(URI.create("http://my.rest.service"), MediaTypes.HAL_JSON); // Follow the path to the target item and fetch its resource Traverson.TraversalBuilder itemTraversal = traverson.follow("items", String.valueOf(itemId)); Resource<Item> itemResource = itemTraversal.toObject(new ParameterizedTypeReference<Resource<Item>>() {}); Item item = itemResource.getContent(); // Automatically follow the "category" link to fetch the associated resource Resource<Category> categoryResource = itemTraversal.follow("category") .toObject(new ParameterizedTypeReference<Resource<Category>>() {}); item.setCategory(categoryResource.getContent()); return item; }
Bonus: Full Automation with Custom Assemblers
For even less manual work, you can use Spring HATEOAS’s ResourceAssembler or HypermediaBeanDeserializer to map HAL links directly to your entity fields. Annotate your entity classes (e.g., @Relation(collectionRelation = "categories") on Category) to link fields to HAL relation names, and the framework will handle population automatically.
To avoid re-fetching the same resource (like Category 8 for 30 items), use Spring Cache. It integrates seamlessly with Spring HATEOAS and lets you cache responses using the resource URL as a unique key.
Step 1: Enable Caching
Add @EnableCaching to your Spring configuration class to activate caching support:
import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Configuration; @Configuration @EnableCaching public class CacheConfig {}
Step 2: Create a Cached Resource Fetcher
Wrap your resource-fetching logic in a component with @Cacheable to cache responses by their full URL:
import org.springframework.cache.annotation.Cacheable; import org.springframework.hateoas.Resource; import org.springframework.hateoas.Traverson; import org.springframework.http.ParameterizedTypeReference; import org.springframework.stereotype.Component; import java.net.URI; @Component public class CachedHalResourceFetcher { private final Traverson traverson; public CachedHalResourceFetcher() { this.traverson = new Traverson(URI.create("http://my.rest.service"), MediaTypes.HAL_JSON); } // Cache resources using their full URL as the unique key @Cacheable(value = "halResources", key = "#resourceUrl") public <T> Resource<T> fetchResource(String resourceUrl, ParameterizedTypeReference<Resource<T>> typeRef) { // Extract the path segment from the URL to use with Traverson String path = URI.create(resourceUrl).getPath().substring(1); // Remove leading slash return traverson.follow(path.split("/")).toObject(typeRef); } }
Step 3: Use the Cached Fetcher in Your Service
Inject the cached fetcher into your service—duplicate requests for the same category URL will hit the cache instead of making a new API call:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Resource; import org.springframework.http.ParameterizedTypeReference; public class ItemService { @Autowired private CachedHalResourceFetcher resourceFetcher; public Item getItemById(Integer itemId) { // Fetch the item (cached if requested again) String itemUrl = "http://my.rest.service/items/" + itemId; Resource<Item> itemResource = resourceFetcher.fetchResource( itemUrl, new ParameterizedTypeReference<Resource<Item>>() {} ); Item item = itemResource.getContent(); // Fetch the category—cache reuses existing data if already fetched String categoryUrl = itemResource.getLink("category").getHref(); Resource<Category> categoryResource = resourceFetcher.fetchResource( categoryUrl, new ParameterizedTypeReference<Resource<Category>>() {} ); item.setCategory(categoryResource.getContent()); return item; } }
Caching Notes
- Spring Boot auto-configures Caffeine as the default cache provider (you can switch to Ehcache, Redis, etc., based on your needs).
- Adjust cache expiration using
@Cacheableattributes likeexpireAfterWriteto balance freshness and performance.
内容的提问来源于stack exchange,提问作者ahuemmer

