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

如何为@JmsListener注解方法实现消息前置校验与通用处理?

Add Pre-Processing for All @JmsListener (Logging + JSON Schema Validation)

Great question! To implement universal pre-checks (logging raw messages with headers and JSON Schema validation) before Spring converts JMS messages to your target domain objects (like Order), the most maintainable approach is to extend Spring's MappingJackson2MessageConverter—this lets you inject custom logic into the message conversion pipeline without modifying your existing listener methods.


Step 1: Create a Custom Message Converter

We'll override the fromMessage method to run our pre-processing steps before delegating to the parent class's object conversion logic. This ensures our checks happen before Spring parses the JSON into an Order instance.

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.stereotype.Component;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.TextMessage;
import java.io.IOException;
import java.util.Enumeration;

@Component
public class CustomJmsMessageConverter extends MappingJackson2MessageConverter {

    private final JsonSchemaValidator schemaValidator;
    private final ObjectMapper objectMapper;

    // Inject your schema validator and Jackson ObjectMapper via constructor
    public CustomJmsMessageConverter(JsonSchemaValidator schemaValidator, ObjectMapper objectMapper) {
        this.schemaValidator = schemaValidator;
        this.objectMapper = objectMapper;
        // Optional: Set a message property to specify target object type (if needed)
        this.setTargetTypeMessageProperty("messageType");
    }

    @Override
    public Object fromMessage(Message message) throws JMSException, MessageConversionException {
        // 1. Log raw message with all headers
        logRawMessageWithHeaders(message);
        
        // 2. Validate JSON against your schema
        validateMessageSchema(message);
        
        // Let Spring handle the conversion to Order (or other target type)
        return super.fromMessage(message);
    }

    private void logRawMessageWithHeaders(Message message) throws JMSException {
        // Build a string of all JMS headers
        StringBuilder headers = new StringBuilder("JMS Headers: ");
        Enumeration<String> headerNames = message.getPropertyNames();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            headers.append(key).append("=").append(message.getObjectProperty(key)).append(", ");
        }

        // Log raw message content (only for TextMessages, which we expect for JSON)
        if (message instanceof TextMessage textMessage) {
            String rawJson = textMessage.getText();
            // Replace System.out with your logging framework (SLF4J/Logback, etc.)
            System.out.printf("Received JMS Message: %nRaw JSON: %s%n%s%n", rawJson, headers);
        }
    }

    private void validateMessageSchema(Message message) throws JMSException, MessageConversionException {
        if (!(message instanceof TextMessage textMessage)) {
            throw new MessageConversionException("Only JSON TextMessages are supported");
        }

        try {
            String rawJson = textMessage.getText();
            JsonNode jsonNode = objectMapper.readTree(rawJson);
            // Trigger schema validation (throws exception if invalid)
            schemaValidator.validate(jsonNode);
        } catch (IOException e) {
            throw new MessageConversionException("Failed to parse JSON message", e);
        } catch (SchemaValidationException e) {
            throw new MessageConversionException("JSON message violates schema rules", e);
        }
    }
}

Implement the JSON Schema Validator

You'll need a validator class to handle schema checks. Here's an example using the popular everit-json-schema library (add it to your pom.xml/gradle build first):

import com.fasterxml.jackson.databind.JsonNode;
import org.everit.json.schema.Schema;
import org.everit.json.schema.loader.SchemaLoader;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.springframework.stereotype.Component;
import java.io.InputStream;

@Component
public class JsonSchemaValidator {

    private final Schema orderSchema;

    public JsonSchemaValidator() {
        // Load your JSON Schema file from the classpath (e.g., src/main/resources/order-schema.json)
        try (InputStream schemaStream = getClass().getResourceAsStream("/order-schema.json")) {
            JSONObject rawSchema = new JSONObject(new JSONTokener(schemaStream));
            orderSchema = SchemaLoader.load(rawSchema);
        } catch (Exception e) {
            throw new RuntimeException("Failed to load JSON Schema file", e);
        }
    }

    public void validate(JsonNode jsonNode) {
        // Convert Jackson JsonNode to org.json.JSONObject for the validator
        JSONObject jsonObject = new JSONObject(jsonNode.toString());
        orderSchema.validate(jsonObject); // Throws ValidationException if invalid
    }
}

Step 2: Configure the Converter for Your Listeners

You can apply this converter globally (to all @JmsListener methods) or locally (to specific listeners):

Option 1: Global Configuration (All Listeners)

Update your JMS configuration bean to use the custom converter for all listener containers:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.connection.ConnectionFactory;

@Configuration
public class JmsConfig {

    @Bean
    public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(
            ConnectionFactory connectionFactory,
            CustomJmsMessageConverter customConverter) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        // Attach our custom converter
        factory.setMessageConverter(customConverter);
        return factory;
    }
}

Option 2: Local Configuration (Specific Listener)

If you only want certain listeners to use the converter, specify it directly in the @JmsListener annotation:

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

@Component
public class MyService {

    // Reference the custom converter by bean name
    @JmsListener(destination = "myDestination", messageConverter = "customJmsMessageConverter")
    public void processOrder(Order order) {
        // Your existing business logic here
    }
}

Step 3: Handle Validation Failures (Optional)

When schema validation fails, the converter throws a MessageConversionException. You can add an error handler to catch these exceptions (and others) and handle them gracefully (e.g., log the failure, send to a dead-letter queue):

import org.springframework.jms.listener.adapter.ListenerExecutionFailedException;
import org.springframework.stereotype.Component;
import org.springframework.util.ErrorHandler;
import javax.jms.Message;

@Component("jmsErrorHandler")
public class CustomJmsErrorHandler implements ErrorHandler {

    @Override
    public void handleError(Throwable t) {
        if (t instanceof ListenerExecutionFailedException lefe) {
            Message failedMessage = lefe.getFailedMessage();
            // Log the error and/or route the message to a dead-letter queue
            System.err.printf("Failed to process JMS message %s: %s%n", failedMessage, t.getMessage());
        }
    }
}

Then attach it to your listener (either globally in the container factory or locally in the @JmsListener):

@JmsListener(destination = "myDestination", errorHandler = "jmsErrorHandler")
public void processOrder(Order order) {
    // Business logic
}

Why This Approach Works

  • Non-intrusive: You don't have to modify any existing listener method code—all pre-processing is encapsulated in the converter.
  • Reusable: One configuration applies to all listeners (or a subset) without code duplication.
  • Follows Spring JMS Best Practices: Message conversion is a standard part of Spring's JMS pipeline, making this the most natural place to add pre-checks.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:21:34