在这次SpringBoot
升级后,之前的系统内使用实体传输受到了限制,如果使用SpringBoot
默认的序列化方式不会出现信任package
的问题,之所以出现这个问题是因为项目使用fastjson
方式进行类的序列化
已经反序列化
,在之前SpringBoot 1.5.10
版本的时候 RabbitMQ
依赖内的DefaultClassMapper
类在构造函数内配置*
,表示信任项目内的所有package
,在SpringBoot 2.0.0
版本时,DefaultClassMapper
类源码构造函数进行了修改,不再信任全部package
而是仅仅信任java.util
、java.lang
。
本章目标 基于SpringBoot2.0
使用RabbitMQ
自定义MessageConverter
配置信任指定package
或者全部package
。
构建项目 创建项目添加对应依赖,pom.xml
配置文件如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-amqp</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.44</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies >
消息队列配置文件 我们需要在application.properties
配置文件内添加RabbitMQ
相应的配置信息,如下所示:
1 2 3 4 spring.rabbitmq.host =localhost spring.rabbitmq.username =admin spring.rabbitmq.password =admin spring.rabbitmq.virtual-host =/hengyu
具体消息队列的连接配置信息需要根据实际情况填写。
队列常量配置 我们之前的文章都是采用的Enum
方式来配置队列相关的Exchange
、Name
、 RouteKey
等相关的信息,使用枚举有个弊端,无法在注解内作为属性的值使用,所以我们之前的Consumer
类配置监听的队列时都是字符串的形式,这样后期修改时还要修改多个地方(当然队列信息很少变动),我们本章使用Constants
常量的形式进行配置,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public interface QueueConstants { String MESSAGE_EXCHANGE = "message.direct.exchange" ; String MESSAGE_QUEUE_NAME = "message.queue" ; String MESSAGE_ROUTE_KEY = "message.send" ; }
示例消息队列JavaConfig配置 本章是为了设置信任package
,所以这里使用消息中心队列来模拟,配置代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @Configuration public class MessageRabbitMqConfiguration { @Bean public DirectExchange messageDirectExchange () { return (DirectExchange) ExchangeBuilder.directExchange(QueueConstants.MESSAGE_EXCHANGE) .durable(true ) .build(); } @Bean public Queue messageQueue () { return QueueBuilder.durable(QueueConstants.MESSAGE_QUEUE_NAME) .build(); } @Bean public Binding messageBinding () { return BindingBuilder.bind(messageQueue()) .to(messageDirectExchange()) .with(QueueConstants.MESSAGE_ROUTE_KEY); } }
上面配置类内添加Exchange
、Queue
、Binding
等配置,将messageQueue
使用message.send
路由键与messageDirectExchange
交换配置进行绑定。
我们在之前说了只有传递实体类时才会出现信任package
问题,下面我们需要创建一个简单的消息传输实体,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Data public class MessageEntity implements Serializable { private String content; }
该实体类仅添加了一个content
字段,这样足够模拟我们的场景了,到这里我们的配置已经处理完,下面就是我们的队列的Provider
以及Consumer
的相关实体类编写。
消息提供者 为队列message.queue
添加Provider
的代码实现,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Component public class MessageProvider { static Logger logger = LoggerFactory.getLogger(MessageProvider.class); @Autowired private AmqpTemplate amqpTemplate; public void sendMessage (Object object) { logger.info("写入消息队列内容:{}" , JSON.toJSONString(object)); amqpTemplate.convertAndSend(QueueConstants.MESSAGE_EXCHANGE, QueueConstants.MESSAGE_ROUTE_KEY, object); } }
消息消费者 当然我们有了Provider
必然要有对应的Consumer
,消费者代码实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Component @RabbitListener(queues = QueueConstants.MESSAGE_QUEUE_NAME) public class MessageConsumer { static Logger logger = LoggerFactory.getLogger(MessageConsumer.class); @RabbitHandler public void handler (@Payload MessageEntity messageEntity) { logger.info("消费内容:{}" , JSON.toJSONString(messageEntity)); } }
创建测试控制器 我们采用控制器发送Get
请求的方式进行发送消息,创建名为TestController
的控制器,并添加测试方法,如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @RestController public class TestController { @Autowired private MessageProvider messageProvider; @RequestMapping(value = "/index") public String index (MessageEntity messageEntity) { messageProvider.sendMessage(messageEntity); return "Success" ; } }
测试RabbitMQ默认实体传输
下面我们启动项目,首先先来测试RabbitMQ
默认的实体类方式,当然这种默认的方式不会产生信任package
的情况。
我们为了证实这一点,来访问(http://localhost:8080/index?content=admin)[http://localhost:8080/index?content=admin],我们传递`content`的值为`admin`,访问效果控制台输出内容如下:
1 2 2018-03-13 21:59:08.844 INFO 16047 --- [nio-8080-exec-1] c.h.chapter48.provider.MessageProvider : 写入消息队列内容:{"content" :"admin" } 2018-03-13 21:59:08.898 INFO 16047 --- [cTaskExecutor-1] c.h.chapter48.consumer.MessageConsumer : 消费内容:{"content" :"admin" }
可以看到控制台的输出内容,直接完成了消息的消费,是没有任何问题的,下面我们对RabbitMQ
添加自定义MessageConverter
的配置,使用fastjson
替代默认转换方式。
MessageConverter 我们先来创建一个转换的实现类,只需要继承抽象类AbstractMessageConverter
并实现内部的createMessage
、fromMessage
两个方法就可以完成实体类的序列化
与反序列化
的转换,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 public class RabbitMqFastJsonConverter extends AbstractMessageConverter { private Logger logger = LoggerFactory.getLogger(RabbitMqFastJsonConverter.class); private static ClassMapper classMapper = new DefaultClassMapper (); private static String DEFAULT_CHART_SET = "UTF-8" ; @Override protected Message createMessage (Object o, MessageProperties messageProperties) { byte [] bytes = null ; try { String jsonString = JSON.toJSONString(o); bytes = jsonString.getBytes(DEFAULT_CHART_SET); } catch (IOException e) { throw new MessageConversionException ( "Failed to convert Message content" , e); } messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); messageProperties.setContentEncoding(DEFAULT_CHART_SET); if (bytes != null ) { messageProperties.setContentLength(bytes.length); } classMapper.fromClass(o.getClass(), messageProperties); return new Message (bytes, messageProperties); } @Override public Object fromMessage (Message message) throws MessageConversionException { Object content = null ; MessageProperties properties = message.getMessageProperties(); if (properties != null ) { String contentType = properties.getContentType(); if (contentType != null && contentType.contains("json" )) { String encoding = properties.getContentEncoding(); if (encoding == null ) { encoding = DEFAULT_CHART_SET; } try { Class<?> targetClass = classMapper.toClass( message.getMessageProperties()); content = convertBytesToObject(message.getBody(), encoding, targetClass); } catch (IOException e) { throw new MessageConversionException ( "Failed to convert Message content" , e); } } else { logger.warn("Could not convert incoming message with content-type [" + contentType + "]" ); } } if (content == null ) { content = message.getBody(); } return content; } private Object convertBytesToObject (byte [] body, String encoding, Class<?> clazz) throws UnsupportedEncodingException { String contentAsString = new String (body, encoding); return JSON.parseObject(contentAsString, clazz); } }
在该转换类内我们使用了DefaultClassMapper
来作为类的映射,我们可以先来看下该类相关信任package
的源码,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ...... public class DefaultClassMapper implements ClassMapper , InitializingBean { public static final String DEFAULT_CLASSID_FIELD_NAME = "__TypeId__" ; private static final String DEFAULT_HASHTABLE_TYPE_ID = "Hashtable" ; private static final List<String> TRUSTED_PACKAGES = Arrays.asList("java.util" , "java.lang" ); private final Set<String> trustedPackages; private volatile Map<String, Class<?>> idClassMapping; private volatile Map<Class<?>, String> classIdMapping; private volatile Class<?> defaultMapClass; private volatile Class<?> defaultType; public DefaultClassMapper () { this .trustedPackages = new LinkedHashSet (TRUSTED_PACKAGES); this .idClassMapping = new HashMap (); this .classIdMapping = new HashMap (); this .defaultMapClass = LinkedHashMap.class; this .defaultType = LinkedHashMap.class; } ......
RabbitMqConfiguration 下面我们需要将该转换设置到RabbitTemplate
、SimpleRabbitListenerContainerFactory
内,让RabbitMQ
支持自定义的消息转换,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Configuration public class RabbitMqConfiguration { @Bean public RabbitTemplate rabbitTemplate (ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate (connectionFactory); template.setMessageConverter(new RabbitMqFastJsonConverter ()); return template; } @Bean public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory (ConnectionFactory connectionFactory) { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory (); factory.setConnectionFactory(connectionFactory); factory.setMessageConverter(new RabbitMqFastJsonConverter ()); factory.setDefaultRequeueRejected(false ); return factory; } }
重启测试 上面的代码配置我们已经把MessageConverter
改成了fastjson
,重启项目,再次访问http://localhost:8080/index?content=admin 路径,看下控制台输出日志内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 Caused by: java.lang.IllegalArgumentException: The class 'com.hengyu.chapter48.entity.MessageEntity' is not in the trusted packages: [java.util, java.lang]. If you believe this class is safe to deserialize, please provide its name. If the serialization is only done by a trusted source , you can also enable trust all (*). at org.springframework.amqp.support.converter.DefaultClassMapper.toClass(DefaultClassMapper.java:211) ~[spring-amqp-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.amqp.support.converter.DefaultClassMapper.toClass(DefaultClassMapper.java:199) ~[spring-amqp-2.0.2.RELEASE.jar:2.0.2.RELEASE] at com.hengyu.chapter48.RabbitMqFastJsonConverter.fromMessage(RabbitMqFastJsonConverter.java:88) ~[classes/:na] at org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener.extractMessage(AbstractAdaptableMessageListener.java:246) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter$MessagingMessageConverterAdapter .extractPayload(MessagingMessageListenerAdapter.java:266) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.amqp.support.converter.MessagingMessageConverter.fromMessage(MessagingMessageConverter.java:118) ~[spring-amqp-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.toMessagingMessage(MessagingMessageListenerAdapter.java:168) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:115) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1414) ~[spring-rabbit-2.0.2.RELEASE.jar:2.0.2.RELEASE] ... 8 common frames omitted
可以看到控制台已经输出了不信任com.hengyu.chapter48.entity.MessageEntity
实体的错误信息,也表明了仅信任java.util
、java.lang
两个package
,下面我们就需要继承DefaultClassMapper
来重写构造函数完成信任指定的package
。
重写DefaultClassMapper构造函数 创建一个名为RabbitMqFastJsonClassMapper
的类并且继承DefaultClassMapper
,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class RabbitMqFastJsonClassMapper extends DefaultClassMapper { public RabbitMqFastJsonClassMapper () { super (); setTrustedPackages("*" ); } }
在上面构造函数内我们设置了信任全部的package
,添加了RabbitMqFastJsonClassMapper
类后,需要让MessageConverter
使用该类作为映射,修改RabbitMqFastJsonConverter
部分代码如下所示:
1 2 3 4 5 6 7 8 9 private static ClassMapper classMapper = new DefaultClassMapper ();>>> 修改为 >>> private static ClassMapper classMapper = new RabbitMqFastJsonClassMapper ();
再次重启测试 我们再次重启项目后,仍然访问http://localhost:8080/index?content=admin 路径,查看控制台日志如下所示:
1 2 2018-03-13 22:23:35.414 INFO 16121 --- [nio-8080-exec-1] c.h.chapter48.provider.MessageProvider : 写入消息队列内容:{"content" :"admin" } 2018-03-13 22:23:35.493 INFO 16121 --- [cTaskExecutor-1] c.h.chapter48.consumer.MessageConsumer : 消费内容:{"content" :"admin" }
根据日志输出已经证明可以正常的完成消息的消费。
总结 如果使用RabbitMQ
默认的转换方式,并不会涉及到本章遇到的信任package
问题,如果想自定义消息转换并且使用DefaultClassMapper
作为映射,肯定会出现信任package
的问题,所以如果需要自定义转换的小伙伴,记住要设置trustedPackages
。