如何为@JmsListener注解方法实现消息前置校验与通用处理?
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

