You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Java的RESTful汽车服务实现Conditional-GET功能问询

Implementing Conditional-GET (304 Not Modified) in Your Java RESTful Service

Hey Tommy, great question! Adding Conditional-GET support is a fantastic way to optimize your API—cutting down on unnecessary data transfer and lightening server load. Let’s walk through exactly how to implement this using the Last-Modified/If-Modified-Since headers you mentioned, plus a bonus tip on ETag for even better precision.

Core Concept Recap

Conditional-GET works like this:

  1. When a client first requests a car resource, your server returns the data and a Last-Modified header containing the timestamp of when the resource was last updated.
  2. On subsequent requests, the client sends an If-Modified-Since header with that same timestamp.
  3. Your server compares the resource’s current last-modified time to the value in If-Modified-Since. If the resource hasn’t changed, return 304 Not Modified (no body needed); if it has, return 200 OK with the updated data and fresh Last-Modified header.

Step-by-Step Implementation (Spring Boot Example)

Assuming you’re using Spring Boot (the most common framework for Java REST services), here’s how to modify your existing GET endpoint:

1. Update Your Entity to Track Last-Modified Time

First, make sure your Car entity has a field to track when it was last updated (most databases support auto-updating this on save):

@Entity
public class Car {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String make;
    private String model;
    // ... other fields
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    // Auto-update timestamp on save/update
    @PreUpdate
    @PrePersist
    public void updateTimestamp() {
        this.updatedAt = LocalDateTime.now();
    }

    // Getters and setters
}

2. Modify the GET Endpoint to Handle Conditional Logic

Update your controller method to check the If-Modified-Since header and return the appropriate status:

@GetMapping("/cars/{id}")
public ResponseEntity<Car> getCar(
        @PathVariable Long id,
        @RequestHeader(value = "If-Modified-Since", required = false) String ifModifiedSince
) {
    // Fetch the car from your database
    Car car = carRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Car not found with ID: " + id));

    // Convert entity's updated time to epoch milliseconds
    ZonedDateTime lastModifiedZoned = car.getUpdatedAt().atZone(ZoneId.systemDefault());
    long lastModifiedMillis = lastModifiedZoned.toInstant().toEpochMilli();

    // Check if client sent an If-Modified-Since header
    if (ifModifiedSince != null) {
        try {
            // Parse the HTTP-standard RFC 1123 date format
            SimpleDateFormat httpDateFormat = new SimpleDateFormat(
                    "EEE, dd MMM yyyy HH:mm:ss zzz",
                    Locale.US
            );
            long clientTimestamp = httpDateFormat.parse(ifModifiedSince).getTime();

            // If resource hasn't changed, return 304
            if (lastModifiedMillis <= clientTimestamp) {
                return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
            }
        } catch (ParseException e) {
            // Malformed header? Ignore it and return 200 as usual
            e.printStackTrace();
        }
    }

    // Return 200 OK with data and Last-Modified header
    return ResponseEntity.ok()
            .lastModified(lastModifiedMillis)
            .body(car);
}

3. Bonus: Add ETag for Higher Precision

Last-Modified only tracks time to the second, which might not be enough if your resource can be updated multiple times per second. For better accuracy, add an ETag (a unique hash of the resource content) alongside Last-Modified:

@GetMapping("/cars/{id}")
public ResponseEntity<Car> getCar(
        @PathVariable Long id,
        @RequestHeader(value = "If-Modified-Since", required = false) String ifModifiedSince,
        @RequestHeader(value = "If-None-Match", required = false) String ifNoneMatch
) {
    Car car = carRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Car not found with ID: " + id));

    ZonedDateTime lastModifiedZoned = car.getUpdatedAt().atZone(ZoneId.systemDefault());
    long lastModifiedMillis = lastModifiedZoned.toInstant().toEpochMilli();

    // Generate ETag using car ID + updated timestamp (could also hash the full entity)
    String eTag = "\"" + car.getId() + "-" + lastModifiedMillis + "\"";

    // Check ETag first (takes priority over Last-Modified per HTTP specs)
    if (ifNoneMatch != null && ifNoneMatch.equals(eTag)) {
        return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
    }

    // Then check If-Modified-Since as before
    if (ifModifiedSince != null) {
        try {
            SimpleDateFormat httpDateFormat = new SimpleDateFormat(
                    "EEE, dd MMM yyyy HH:mm:ss zzz",
                    Locale.US
            );
            long clientTimestamp = httpDateFormat.parse(ifModifiedSince).getTime();

            if (lastModifiedMillis <= clientTimestamp) {
                return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

    // Return 200 OK with both headers
    return ResponseEntity.ok()
            .lastModified(lastModifiedMillis)
            .eTag(eTag)
            .body(car);
}

Testing with Postman

  1. First Request: Send a GET to /cars/{id}. Check the response headers—you’ll see Last-Modified (and ETag if you added it).
  2. Subsequent Request: Add the If-Modified-Since header (using the value from the first request’s Last-Modified) or If-None-Match (using the ETag value) to your request. If the car data hasn’t changed, you’ll get a 304 Not Modified response with no body.

Key Notes

  • Always update the updatedAt field whenever you perform a PUT/PATCH on a car (the @PreUpdate callback handles this automatically).
  • The HTTP date format must be RFC 1123 (e.g., Wed, 21 Oct 2015 07:28:00 GMT)—Spring’s lastModified() method converts milliseconds to this format for you.
  • A 304 response should never include a body—use ResponseEntity.status(HttpStatus.NOT_MODIFIED).build() to ensure this.

内容的提问来源于stack exchange,提问作者Tommy Hamm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:41:08