第四十八章:SpringBoot2.0新特性 - RabbitMQ信任package设置

在此次SpringBoot升级后,以前的系统内使用实体传输受到了限制,若是使用SpringBoot默认的序列化方式不会出现信任package的问题,之因此出现这个问题是由于项目使用fastjson方式进行类的序列化已经反序列化,在以前SpringBoot 1.5.10版本的时候 RabbitMQ依赖内的DefaultClassMapper类在构造函数内配置*,表示信任项目内的全部package,在SpringBoot 2.0.0版本时,DefaultClassMapper类源码构造函数进行了修改,再也不信任所有package而是仅仅信任java.utiljava.langjava

本章目标

基于SpringBoot2.0使用RabbitMQ自定义MessageConverter配置信任指定package或者所有packagegit

SpringBoot 企业级核心技术学习专题


专题 专题名称 专题描述
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方式来配置队列相关的ExchangeNameRouteKey等相关的信息,使用枚举有个弊端,没法在注解内做为属性的值使用,因此咱们以前的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";
}
复制代码

示例消息队列JavaConfig配置

本章是为了设置信任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);
    }
}
复制代码

上面配置类内添加ExchangeQueueBinding等配置,将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默认实体传输

下面咱们启动项目,首先先来测试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替代默认转换方式。

MessageConverter

咱们先来建立一个转换的实现类,只须要继承抽象类AbstractMessageConverter并实现内部的createMessagefromMessage两个方法就能够完成实体类的序列化反序列化的转换,代码以下所示:

/**
 * 自定义消息转换器
 * 采用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;
    }
......
复制代码

RabbitMqConfiguration

下面咱们须要将该转换设置到RabbitTemplateSimpleRabbitListenerContainerFactory内,让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.utiljava.lang两个package,下面咱们就须要继承DefaultClassMapper来重写构造函数完成信任指定的package

重写DefaultClassMapper构造函数

建立一个名为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技术交流群,共同进步。

QQ技术交流群

微信扫码关注 - 专一分享
相关文章
相关标签/搜索