在此次SpringBoot
升級後,以前的系統內使用實體傳輸受到了限制,若是使用SpringBoot
默認的序列化方式不會出現信任package
的問題,之因此出現這個問題是由於項目使用fastjson
方式進行類的序列化
已經反序列化
,在以前SpringBoot 1.5.10
版本的時候 RabbitMQ
依賴內的DefaultClassMapper
類在構造函數內配置*
,表示信任項目內的全部package
,在SpringBoot 2.0.0
版本時,DefaultClassMapper
類源碼構造函數進行了修改,再也不信任所有package
而是僅僅信任java.util
、java.lang
。java
基於SpringBoot2.0
使用RabbitMQ
自定義MessageConverter
配置信任指定package
或者所有package
。git
專題 | 專題名稱 | 專題描述 |
---|---|---|
001 | Spring Boot 核心技術 | 講解SpringBoot一些企業級層面的核心組件 |
002 | Spring Boot 核心技術章節源碼 | Spring Boot 核心技術簡書每一篇文章碼雲對應源碼 |
003 | Spring Cloud 核心技術 | 對Spring Cloud核心技術全面講解 |
004 | Spring Cloud 核心技術章節源碼 | Spring Cloud 核心技術簡書每一篇文章對應源碼 |
005 | QueryDSL 核心技術 | 全面講解QueryDSL核心技術以及基於SpringBoot整合SpringDataJPA |
006 | SpringDataJPA 核心技術 | 全面講解SpringDataJPA核心技術 |
007 | SpringBoot核心技術學習目錄 | SpringBoot系統的學習目錄,敬請關注點贊!!! |
建立項目添加對應依賴,pom.xml
配置文件以下所示:web
<dependencies>
<!--消息隊列依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--web相關依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--fastjson依賴-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.44</version>
</dependency>
<!--lombok依賴-->
<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
相應的配置信息,以下所示:spring
spring.rabbitmq.host=localhost
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtual-host=/hengyu
複製代碼
具體消息隊列的鏈接配置信息須要根據實際狀況填寫。json
咱們以前的文章都是採用的Enum
方式來配置隊列相關的Exchange
、Name
、 RouteKey
等相關的信息,使用枚舉有個弊端,沒法在註解內做爲屬性的值使用,因此咱們以前的Consumer
類配置監聽的隊列時都是字符串的形式,這樣後期修改時還要修改多個地方(固然隊列信息不多變更),咱們本章使用Constants
常量的形式進行配置,以下所示:數組
/**
* 隊列常量配置
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/7
* Time:下午10:10
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
public interface QueueConstants {
/**
* 消息交換
*/
String MESSAGE_EXCHANGE = "message.direct.exchange";
/**
* 消息隊列名稱
*/
String MESSAGE_QUEUE_NAME = "message.queue";
/**
* 消息路由鍵
*/
String MESSAGE_ROUTE_KEY = "message.send";
}
複製代碼
本章是爲了設置信任package
,因此這裏使用消息中心隊列來模擬,配置代碼以下所示:bash
/**
* 消息隊列配置類
*
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/7
* Time:下午10:07
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
@Configuration
public class MessageRabbitMqConfiguration {
/**
* 交換配置
*
* @return
*/
@Bean
public DirectExchange messageDirectExchange() {
return (DirectExchange) ExchangeBuilder.directExchange(QueueConstants.MESSAGE_EXCHANGE)
.durable(true)
.build();
}
/**
* 消息隊列聲明
*
* @return
*/
@Bean
public Queue messageQueue() {
return QueueBuilder.durable(QueueConstants.MESSAGE_QUEUE_NAME)
.build();
}
/**
* 消息綁定
*
* @return
*/
@Bean
public Binding messageBinding() {
return BindingBuilder.bind(messageQueue())
.to(messageDirectExchange())
.with(QueueConstants.MESSAGE_ROUTE_KEY);
}
}
複製代碼
上面配置類內添加Exchange
、Queue
、Binding
等配置,將messageQueue
使用message.send
路由鍵與messageDirectExchange
交換配置進行綁定。微信
咱們在以前說了只有傳遞實體類時纔會出現信任package
問題,下面咱們須要建立一個簡單的消息傳輸實體,以下所示:app
/**
* 消息實體
*
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/11
* Time:下午5:18
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
@Data
public class MessageEntity implements Serializable {
/**
* 消息內容
*/
private String content;
}
複製代碼
該實體類僅添加了一個content
字段,這樣足夠模擬咱們的場景了,到這裏咱們的配置已經處理完,下面就是咱們的隊列的Provider
以及Consumer
的相關實體類編寫。框架
爲隊列message.queue
添加Provider
的代碼實現,以下所示:
/**
* 消息隊列 - 消息提供者
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/11
* Time:下午6:16
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
@Component
public class MessageProvider {
/**
* logger instance
*/
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
,消費者代碼實現以下所示:
/**
* 消息隊列 - 消息消費者
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/11
* Time:下午5:32
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
@Component
@RabbitListener(queues = QueueConstants.MESSAGE_QUEUE_NAME)
public class MessageConsumer {
/**
* logger instance
*/
static Logger logger = LoggerFactory.getLogger(MessageConsumer.class);
@RabbitHandler
public void handler(@Payload MessageEntity messageEntity) {
logger.info("消費內容:{}", JSON.toJSONString(messageEntity));
}
}
複製代碼
咱們採用控制器發送Get
請求的方式進行發送消息,建立名爲TestController
的控制器,並添加測試方法,以下代碼所示:
/**
* 測試控制器
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/11
* Time:下午5:43
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
@RestController
public class TestController {
/**
* 消息隊列 - 消息提供者 注入
*/
@Autowired
private MessageProvider messageProvider;
/**
* 測試發送消息隊列方法
*
* @param messageEntity 發送消息實體內容
* @return
*/
@RequestMapping(value = "/index")
public String index(MessageEntity messageEntity) {
// 將實體實例寫入消息隊列
messageProvider.sendMessage(messageEntity);
return "Success";
}
}
複製代碼
下面咱們啓動項目,首先先來測試
RabbitMQ
默認的實體類方式,固然這種默認的方式不會產生信任package
的狀況。
咱們爲了證明這一點,來訪問(http://localhost:8080/index?content=admin)[http://localhost:8080/index?content=admin],咱們傳遞content
的值爲admin
,訪問效果控制檯輸出內容以下:
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
替代默認轉換方式。
咱們先來建立一個轉換的實現類,只須要繼承抽象類AbstractMessageConverter
並實現內部的createMessage
、fromMessage
兩個方法就能夠完成實體類的序列化
與反序列化
的轉換,代碼以下所示:
/**
* 自定義消息轉換器
* 採用FastJson完成消息轉換
*
* @author:於起宇 <br/>
* ===============================
* Created with Eclipse.
* Date:2017/10/26
* Time:19:28
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
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";
/**
* 建立消息
*
* @param o 消息對象
* @param messageProperties 消息屬性
* @return
*/
@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);
}
/**
* 轉換消息爲對象
*
* @param message 消息對象
* @return
* @throws MessageConversionException
*/
@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;
}
/**
* 將字節數組轉換成實例對象
*
* @param body Message對象主體字節數組
* @param encoding 字符集
* @param clazz 類型
* @return
* @throws UnsupportedEncodingException
*/
private Object convertBytesToObject(byte[] body, String encoding,
Class<?> clazz) throws UnsupportedEncodingException {
String contentAsString = new String(body, encoding);
return JSON.parseObject(contentAsString, clazz);
}
}
複製代碼
在該轉換類內咱們使用了DefaultClassMapper
來做爲類的映射,咱們能夠先來看下該類相關信任package
的源碼,以下所示:
......
public class DefaultClassMapper implements ClassMapper, InitializingBean {
public static final String DEFAULT_CLASSID_FIELD_NAME = "__TypeId__";
private static final String DEFAULT_HASHTABLE_TYPE_ID = "Hashtable";
// 默認信任的package列表
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() {
// 構造函數初始化信任的package爲默認的pakcage列表
// 僅支持java.util、java.lang兩個package
this.trustedPackages = new LinkedHashSet(TRUSTED_PACKAGES);
this.idClassMapping = new HashMap();
this.classIdMapping = new HashMap();
this.defaultMapClass = LinkedHashMap.class;
this.defaultType = LinkedHashMap.class;
}
......
複製代碼
下面咱們須要將該轉換設置到RabbitTemplate
、SimpleRabbitListenerContainerFactory
內,讓RabbitMQ
支持自定義的消息轉換,以下所示:
/**
* rabbitmq 相關配置
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/11
* Time:下午5:42
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
@Configuration
public class RabbitMqConfiguration {
/**
* 配置消息隊列模版
* 而且設置MessageConverter爲自定義FastJson轉換器
* @param connectionFactory
* @return
*/
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(new RabbitMqFastJsonConverter());
return template;
}
/**
* 自定義隊列容器工廠
* 而且設置MessageConverter爲自定義FastJson轉換器
* @param connectionFactory
* @return
*/
@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路徑,看下控制檯輸出日誌內容以下所示:
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
。
建立一個名爲RabbitMqFastJsonClassMapper
的類而且繼承DefaultClassMapper
,以下所示:
/**
* fastjson 轉換映射
*
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/13
* Time:下午10:17
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
public class RabbitMqFastJsonClassMapper extends DefaultClassMapper {
/**
* 構造函數初始化信任全部pakcage
*/
public RabbitMqFastJsonClassMapper() {
super();
setTrustedPackages("*");
}
}
複製代碼
在上面構造函數內咱們設置了信任所有的package
,添加了RabbitMqFastJsonClassMapper
類後,須要讓MessageConverter
使用該類做爲映射,修改RabbitMqFastJsonConverter
部分代碼以下所示:
/**
* 消息類型映射對象
*/
private static ClassMapper classMapper = new DefaultClassMapper();
>>> 修改成 >>>
/**
* 消息類型映射對象
*/
private static ClassMapper classMapper = new RabbitMqFastJsonClassMapper();
複製代碼
咱們再次重啓項目後,仍然訪問http://localhost:8080/index?content=admin路徑,查看控制檯日誌以下所示:
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
。
本章源碼已經上傳到碼雲: SpringBoot配套源碼地址:gitee.com/hengboy/spr… SpringCloud配套源碼地址:gitee.com/hengboy/spr… SpringBoot相關係列文章請訪問:目錄:SpringBoot學習目錄 QueryDSL相關係列文章請訪問:QueryDSL通用查詢框架學習目錄 SpringDataJPA相關係列文章請訪問:目錄:SpringDataJPA學習目錄,感謝閱讀! 歡迎加入QQ技術交流羣,共同進步。