Spring Boot动态对象建模:RESTful API通知接口设计问题
Hey there! Let’s work through this dynamic notification modeling problem for your Spring Boot REST API—this is a common scenario when dealing with multiple resource types, so I’ve got some practical solutions to share.
1. Model Your Notifications with Inheritance & Polymorphism
First, we’ll create an abstract base class for all notifications to hold common properties, then extend it for each specific type (text, link, map, file). This lets us treat all notifications uniformly while preserving their unique attributes.
// Abstract base class public abstract class Notification { private String id; private long createdAt; // Abstract method for dynamic title generation (per your question) public abstract String getTitle(); // Getters and setters for common fields public String getId() { return id; } public void setId(String id) { this.id = id; } public long getCreatedAt() { return createdAt; } public void setCreatedAt(long createdAt) { this.createdAt = createdAt; } }
Now create subclasses for each notification type, each adding their own unique fields and implementing the getTitle() method for dynamic title logic:
// Text notification subclass public class TextNotification extends Notification { private String content; @Override public String getTitle() { // Dynamic title example: truncate content for preview return "New Text Alert: " + content.substring(0, Math.min(content.length(), 20)) + "..."; } // Getter and setter for content public String getContent() { return content; } public void setContent(String content) { this.content = content; } } // Link notification subclass public class LinkNotification extends Notification { private String url; private String linkLabel; @Override public String getTitle() { return "New Link: " + linkLabel; } // Getters and setters for url/linkLabel public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getLinkLabel() { return linkLabel; } public void setLinkLabel(String linkLabel) { this.linkLabel = linkLabel; } } // Repeat similar for MapNotification and FileNotification // Example MapNotification might include latitude/longitude; FileNotification could have fileUrl/fileName
2. Configure Jackson for Polymorphic Parsing
Spring Boot uses Jackson under the hood to serialize/deserialize JSON. We need to tell Jackson how to distinguish between different notification types in the request body. Add these annotations to the abstract Notification class:
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type" // This field in the JSON will tell Jackson which subclass to use ) @JsonSubTypes({ @JsonSubTypes.Type(value = TextNotification.class, name = "TEXT"), @JsonSubTypes.Type(value = LinkNotification.class, name = "LINK"), @JsonSubTypes.Type(value = MapNotification.class, name = "MAP"), @JsonSubTypes.Type(value = FileNotification.class, name = "FILE") }) public abstract class Notification { // ... existing code ... }
Now your NotificationRequest class can wrap the username and the polymorphic notification object:
public class NotificationRequest { private String username; private Notification notification; // Getters and setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Notification getNotification() { return notification; } public void setNotification(Notification notification) { this.notification = notification; } }
3. Build Your REST Endpoint
Your controller can now accept the NotificationRequest and handle each type appropriately. For cleaner code, avoid messy instanceof checks by using the Strategy Pattern next.
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user/notification") public class NotificationController { private final NotificationService notificationService; public NotificationController(NotificationService notificationService) { this.notificationService = notificationService; } @PostMapping public void sendNotification(@RequestBody NotificationRequest request) { notificationService.send(request.getUsername(), request.getNotification()); } }
4. Decouple Sending Logic with Strategy Pattern
To avoid hardcoding type checks, create a strategy interface for sending notifications, then implement it for each type. Spring will automatically pick up these beans.
// Strategy interface public interface NotificationSender { void send(String username, Notification notification); String getNotificationType(); // Matches the "type" value from JSON } // Text notification sender implementation import org.springframework.stereotype.Component; @Component public class TextNotificationSender implements NotificationSender { @Override public void send(String username, Notification notification) { TextNotification textNotification = (TextNotification) notification; // Implement your text notification sending logic here (e.g., push, email) System.out.println("Sending text to " + username + ": " + textNotification.getContent()); } @Override public String getNotificationType() { return "TEXT"; } } // Repeat for LinkNotificationSender, MapNotificationSender, FileNotificationSender
Then create a service to orchestrate the strategy lookup:
import org.springframework.stereotype.Service; import java.util.Map; @Service public class NotificationService { private final Map<String, NotificationSender> senderMap; // Spring injects all NotificationSender beans into this map public NotificationService(Map<String, NotificationSender> senderMap) { this.senderMap = senderMap; } public void send(String username, Notification notification) { NotificationSender sender = senderMap.values().stream() .filter(s -> s.getNotificationType().equals(getNotificationTypeFromClass(notification))) .findFirst() .orElseThrow(() -> new IllegalArgumentException("Unsupported notification type")); sender.send(username, notification); } // Helper method to get the type string from the notification instance private String getNotificationTypeFromClass(Notification notification) { if (notification instanceof TextNotification) return "TEXT"; if (notification instanceof LinkNotification) return "LINK"; if (notification instanceof MapNotification) return "MAP"; if (notification instanceof FileNotification) return "FILE"; throw new IllegalArgumentException("Unknown notification type"); } }
Example Request Body
Here’s what a JSON request for a text notification would look like:
{ "username": "vk321", "notification": { "type": "TEXT", "id": "notif-123", "createdAt": 1699999999000, "content": "Hey, your order has been shipped!" } }
This setup solves your dynamic modeling issues:
- Different notification types have their own unique fields
- Dynamic titles are handled per-type via the
getTitle()method - Spring correctly parses the JSON request into the right subclass
- Sending logic is decoupled and easy to extend for new notification types
内容的提问来源于stack exchange,提问作者VK321

