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

SpringBoot如何实现动态对象蛇形命名转驼峰并容错入DLQ?

Solution for Recursive Snake-Case to Camel-Case Conversion + DLQ Handling in Spring Cloud Stream

Hi there! Since you're new to Spring, I'll walk you through a straightforward, annotation-driven approach to solve both your recursive naming conversion and dead-letter queue (DLQ) needs.


1. Fix Recursive Snake-Case to Camel-Case Conversion for details

The @JsonNaming annotation only applies to the immediate class fields, not nested objects or dynamic structures like your details field. Here's how to make it work recursively:

Step 1: Adjust EventContent for Better Dynamic Handling

First, change the details field type from Object to Map<String, Object>—this makes it easier to process nested key-value pairs:

@ToString
@AllArgsConstructor
@Getter
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) // Handles top-level fields
public class EventContent {
    private final String dataString1;
    private final String dataString2;
    // ... other fields
    @JsonDeserialize(using = RecursiveSnakeToCamelDeserializer.class)
    private final Map<String, Object> details;
}

Step 2: Create a Recursive Deserializer

Write a custom Jackson deserializer that converts snake-case keys to camel-case recursively (including nested maps and lists):

public class RecursiveSnakeToCamelDeserializer extends StdDeserializer<Map<String, Object>> {

    private static final Pattern SNAKE_CASE_PATTERN = Pattern.compile("_(.)");

    protected RecursiveSnakeToCamelDeserializer() {
        super(Map.class);
    }

    @Override
    public Map<String, Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        JsonNode node = p.getCodec().readTree(p);
        return convertNodeToCamelCaseMap(node);
    }

    private Map<String, Object> convertNodeToCamelCaseMap(JsonNode node) {
        Map<String, Object> result = new HashMap<>();
        node.fields().forEachRemaining(entry -> {
            String camelCaseKey = convertSnakeToCamel(entry.getKey());
            JsonNode valueNode = entry.getValue();
            result.put(camelCaseKey, convertValueToCamelCase(valueNode));
        });
        return result;
    }

    private Object convertValueToCamelCase(JsonNode valueNode) {
        if (valueNode.isObject()) {
            return convertNodeToCamelCaseMap(valueNode);
        } else if (valueNode.isArray()) {
            List<Object> list = new ArrayList<>();
            for (JsonNode element : valueNode) {
                list.add(convertValueToCamelCase(element));
            }
            return list;
        } else {
            // Return primitive values as-is
            if (valueNode.isTextual()) return valueNode.asText();
            if (valueNode.isBoolean()) return valueNode.asBoolean();
            if (valueNode.isNumber()) return valueNode.numberValue();
            return valueNode.asText();
        }
    }

    private String convertSnakeToCamel(String snakeCase) {
        return SNAKE_CASE_PATTERN.matcher(snakeCase).replaceAll(match -> match.group(1).toUpperCase());
    }
}

Step 3: Register the Deserializer (Optional)

If you want to use this deserializer globally for all Map fields, register it with Spring's ObjectMapper:

@Configuration
public class JacksonConfig {
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> builder.deserializerByType(Map.class, new RecursiveSnakeToCamelDeserializer());
    }
}

This way you won't need the @JsonDeserialize annotation on the details field anymore.


2. Enable Fault Tolerance & DLQ for Failed Messages

Spring Cloud Stream has built-in support for Kafka DLQs—no manual message routing needed! Add these configurations to your application.properties (or application.yml):

Properties Configuration

# Enable DLQ for your input channel
spring.cloud.stream.kafka.bindings.blaChannel.consumer.enable-dlq=true

# Set DLQ topic name (default is <input-topic>.dlq)
spring.cloud.stream.kafka.bindings.blaChannel.consumer.dlq-name=your-input-topic.dlq

# Number of retries before sending to DLQ (adjust as needed)
spring.cloud.stream.kafka.bindings.blaChannel.consumer.max-attempts=3

# Optional: Configure retry backoff
spring.cloud.stream.kafka.bindings.blaChannel.consumer.retry.backoff.max-interval=5000
spring.cloud.stream.kafka.bindings.blaChannel.consumer.retry.backoff.initial-interval=1000

Handle Exceptions in Your Listener

To catch conversion errors or business logic failures explicitly and ensure they're routed to DLQ, wrap your doStuffWith logic in a try-catch block:

@StreamListener(MessagingChannels.BLA_CHANNEL)
public void onIncomingEvent(EventContent eventContent, @Header(name = "uuid") UUID messageId ) {
    try {
        doStuffWith(eventContent);
    } catch (Exception e) {
        // Log the error with message ID for debugging
        log.error("Failed to process event with ID: {}", messageId, e);
        // Re-throw the exception so Spring Cloud Stream routes it to DLQ
        throw new RuntimeException(e);
    }
}

Any uncaught exception will trigger the retry mechanism, and after max-attempts failures, the message is sent to your configured DLQ.


3. Simplified Annotation Alternative (No Custom Deserializer)

If you don't want to write a custom deserializer, use Jackson's ObjectMapper directly in your listener to convert the raw JSON payload:

@Autowired
private ObjectMapper objectMapper;

@StreamListener(MessagingChannels.BLA_CHANNEL)
public void onIncomingEvent(@Payload String rawPayload, @Header(name = "uuid") UUID messageId ) {
    try {
        // Configure ObjectMapper to convert snake-case to camel-case recursively
        ObjectMapper camelCaseMapper = objectMapper.copy()
                .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        EventContent eventContent = camelCaseMapper.readValue(rawPayload, EventContent.class);
        
        // Now eventContent.getDetails() has camel-case keys
        doStuffWith(eventContent);
    } catch (JsonProcessingException e) {
        log.error("Failed to deserialize event with ID: {}", messageId, e);
        throw new RuntimeException(e);
    }
}

This approach accepts the raw JSON payload as a String instead of directly deserializing to EventContent, but avoids writing a custom deserializer.


Final Notes

  • Ensure your Kafka broker allows auto-creation of the DLQ topic (or create it manually beforehand).
  • For Java 8, use Spring Boot 2.x (Spring Boot 3+ requires Java 17+).
  • Test with different snake-case nested structures to verify recursive conversion works as expected.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:05:52