专栏首页mall学习教程花了3天总结的RabbitMQ实用技巧,有点东西!

花了3天总结的RabbitMQ实用技巧,有点东西!

以前看过的关于RabbitMQ核心消息模式的文章都是基于Java API的,最近看了下官方文档,发现这些核心消息模式都可以通过Spring AMQP来实现。于是总结了下RabbitMQ的实用技巧,包括RabbitMQ在Windows和Linux下的安装、5种核心消息模式的Spring AMQP实现,相信对于想要学习和回顾RabbitMQ的朋友都会有所帮助。

简介

RabbitMQ是最受欢迎的开源消息中间件之一,在全球范围内被广泛应用。RabbitMQ是轻量级且易于部署的,能支持多种消息协议。RabbitMQ可以部署在分布式系统中,以满足大规模、高可用的要求。

相关概念

我们先来了解下RabbitMQ中的相关概念,这里以5种消息模式中的路由模式为例。

安装及配置

接下来我们介绍下RabbitMQ的安装和配置,提供Windows和Linux两种安装方式。

Windows下的安装

  • 安装Erlang,下载地址:http://erlang.org/download/otp_win64_21.3.exe
  • 安装RabbitMQ,下载地址:https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe
  • 安装完成后,进入RabbitMQ安装目录下的sbin目录;
  • 在地址栏输入cmd并回车启动命令行,然后输入以下命令启动管理功能。
rabbitmq-plugins enable rabbitmq_management

Linux下的安装

  • 下载rabbitmq 3.7.15的Docker镜像;
docker pull rabbitmq:3.7.15
  • 使用Docker命令启动服务;
docker run -p 5672:5672 -p 15672:15672 --name rabbitmq \
-d rabbitmq:3.7.15
  • 进入容器并开启管理功能;
docker exec -it rabbitmq /bin/bash
rabbitmq-plugins enable rabbitmq_management
  • 开启防火墙便于外网访问。
firewall-cmd --zone=public --add-port=15672/tcp --permanent
firewall-cmd --zone=public --add-port=5672/tcp --permanent
firewall-cmd --reload

访问及配置

  • 访问RabbitMQ管理页面地址,查看是否安装成功(Linux下使用服务器IP访问即可):http://localhost:15672/
  • 输入账号密码并登录,这里使用默认账号密码登录:guest guest
  • 创建帐号并设置其角色为管理员:mall mall
  • 创建一个新的虚拟host为:/mall
  • 点击mall用户进入用户配置页面;
  • 给mall用户配置该虚拟host的权限;
  • 至此,RabbitMQ的配置完成。

5种消息模式

这5种消息模式是构建基于RabbitMQ的消息应用的基础,一定要牢牢掌握它们。学过RabbitMQ的朋友应该了解过这些消息模式的Java实现,这里我们使用Spring AMQP的形式来实现它们。

简单模式

简单模式是最简单的消息模式,它包含一个生产者、一个消费者和一个队列。生产者向队列里发送消息,消费者从队列中获取消息并消费。

模式示意图

Spring AMQP实现

  • 首先需要在pom.xml中添加Spring AMQP的相关依赖;
<!--Spring AMQP依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 然后修改application.yml,添加RabbitMQ的相关配置;
spring:
  rabbitmq:
    host: localhost
    port: 5672
    virtual-host: /mall
    username: mall
    password: mall
    publisher-confirms: true #消息发送到交换器确认
    publisher-returns: true #消息发送到队列确认
  • 添加简单模式相关Java配置,创建一个名为simple.hello的队列、一个生产者和一个消费者;
/**
 * Created by macro on 2020/5/19.
 */
@Configuration
public class SimpleRabbitConfig {

 @Bean
 public Queue hello() {
  return new Queue("simple.hello");
 }

 @Bean
 public SimpleSender simpleSender(){
  return new SimpleSender();
 }

 @Bean
 public SimpleReceiver simpleReceiver(){
  return new SimpleReceiver();
 }

}
  • 生产者通过send方法向队列simple.hello中发送消息;
/**
 * Created by macro on 2020/5/19.
 */
public class SimpleSender {

 private static final Logger LOGGER = LoggerFactory.getLogger(SimpleSender.class);

 @Autowired
 private RabbitTemplate template;

 private static final String queueName="simple.hello";

 public void send() {
  String message = "Hello World!";
  this.template.convertAndSend(queueName, message);
  LOGGER.info(" [x] Sent '{}'", message);
 }

}
  • 消费者从队列simple.hello中获取消息;
/**
 * Created by macro on 2020/5/19.
 */
@RabbitListener(queues = "simple.hello")
public class SimpleReceiver {

    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleReceiver.class);

    @RabbitHandler
    public void receive(String in) {
        LOGGER.info(" [x] Received '{}'", in);
    }

}
  • 在controller中添加测试接口,调用该接口开始发送消息;
/**
 * Created by macro on 2020/5/19.
 */
@Api(tags = "RabbitController", description = "RabbitMQ功能测试")
@Controller
@RequestMapping("/rabbit")
public class RabbitController {

    @Autowired
    private SimpleSender simpleSender;

    @ApiOperation("简单模式")
    @RequestMapping(value = "/simple", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult simpleTest() {
        for(int i=0;i<10;i++){
            simpleSender.send();
            ThreadUtil.sleep(1000);
        }
        return CommonResult.success(null);
    }
}
  • 运行后结果如下,可以发现生产者往队列中发送消息,消费者从队列中获取消息并消费。

工作模式

工作模式是指向多个互相竞争的消费者发送消息的模式,它包含一个生产者、两个消费者和一个队列。两个消费者同时绑定到一个队列上去,当消费者获取消息处理耗时任务时,空闲的消费者从队列中获取并消费消息。

模式示意图

Spring AMQP实现

  • 添加工作模式相关Java配置,创建一个名为work.hello的队列、一个生产者和两个消费者;
/**
 * Created by macro on 2020/5/19.
 */
@Configuration
public class WorkRabbitConfig {

    @Bean
    public Queue workQueue() {
        return new Queue("work.hello");
    }

    @Bean
    public WorkReceiver workReceiver1() {
        return new WorkReceiver(1);
    }

    @Bean
    public WorkReceiver workReceiver2() {
        return new WorkReceiver(2);
    }

    @Bean
    public WorkSender workSender() {
        return new WorkSender();
    }

}
  • 生产者通过send方法向队列work.hello中发送消息,消息中包含一定数量的.号;
/**
 * Created by macro on 2020/5/19.
 */
public class WorkSender {

    private static final Logger LOGGER = LoggerFactory.getLogger(WorkSender.class);

    @Autowired
    private RabbitTemplate template;

    private static final String queueName = "work.hello";

    public void send(int index) {
        StringBuilder builder = new StringBuilder("Hello");
        int limitIndex = index % 3+1;
        for (int i = 0; i < limitIndex; i++) {
            builder.append('.');
        }
        builder.append(index+1);
        String message = builder.toString();
        template.convertAndSend(queueName, message);
        LOGGER.info(" [x] Sent '{}'", message);
    }

}
  • 两个消费者从队列work.hello中获取消息,名称分别为instance 1instance 2,消息中包含.号越多,耗时越长;
/**
 * Created by macro on 2020/5/19.
 */
@RabbitListener(queues = "work.hello")
public class WorkReceiver {

    private static final Logger LOGGER = LoggerFactory.getLogger(WorkReceiver.class);

    private final int instance;

    public WorkReceiver(int i) {
        this.instance = i;
    }

    @RabbitHandler
    public void receive(String in) {
        StopWatch watch = new StopWatch();
        watch.start();
        LOGGER.info("instance {} [x] Received '{}'", this.instance, in);
        doWork(in);
        watch.stop();
        LOGGER.info("instance {} [x] Done in {}s", this.instance, watch.getTotalTimeSeconds());
    }

    private void doWork(String in) {
        for (char ch : in.toCharArray()) {
            if (ch == '.') {
                ThreadUtil.sleep(1000);
            }
        }
    }

}
  • 在controller中添加测试接口,调用该接口开始发送消息;
/**
 * Created by macro on 2020/5/19.
 */
@Api(tags = "RabbitController", description = "RabbitMQ功能测试")
@Controller
@RequestMapping("/rabbit")
public class RabbitController {
    
    @Autowired
    private WorkSender workSender;

    @ApiOperation("工作模式")
    @RequestMapping(value = "/work", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult workTest() {
        for(int i=0;i<10;i++){
            workSender.send(i);
            ThreadUtil.sleep(1000);
        }
        return CommonResult.success(null);
    }
}
  • 运行后结果如下,可以发现生产者往队列中发送包含不同数量.号的消息,instance 1instance 2消费者互相竞争,分别消费了一部分消息。

发布/订阅模式

发布/订阅模式是指同时向多个消费者发送消息的模式(类似广播的形式),它包含一个生产者、两个消费者、两个队列和一个交换机。两个消费者同时绑定到不同的队列上去,两个队列绑定到交换机上去,生产者通过发送消息到交换机,所有消费者接收并消费消息。

模式示意图

Spring AMQP实现

  • 添加发布/订阅模式相关Java配置,创建一个名为exchange.fanout的交换机、一个生产者、两个消费者和两个匿名队列,将两个匿名队列都绑定到交换机;
/**
 * Created by macro on 2020/5/19.
 */
@Configuration
public class FanoutRabbitConfig {

    @Bean
    public FanoutExchange fanout() {
        return new FanoutExchange("exchange.fanout");
    }

    @Bean
    public Queue fanoutQueue1() {
        return new AnonymousQueue();
    }

    @Bean
    public Queue fanoutQueue2() {
        return new AnonymousQueue();
    }

    @Bean
    public Binding fanoutBinding1(FanoutExchange fanout, Queue fanoutQueue1) {
        return BindingBuilder.bind(fanoutQueue1).to(fanout);
    }

    @Bean
    public Binding fanoutBinding2(FanoutExchange fanout, Queue fanoutQueue2) {
        return BindingBuilder.bind(fanoutQueue2).to(fanout);
    }

    @Bean
    public FanoutReceiver fanoutReceiver() {
        return new FanoutReceiver();
    }

    @Bean
    public FanoutSender fanoutSender() {
        return new FanoutSender();
    }

}
  • 生产者通过send方法向交换机exchange.fanout中发送消息,消息中包含一定数量的.号;
/**
 * Created by macro on 2020/5/19.
 */
public class FanoutSender {
    private static final Logger LOGGER = LoggerFactory.getLogger(FanoutSender.class);
    @Autowired
    private RabbitTemplate template;

    private static final String exchangeName = "exchange.fanout";

    public void send(int index) {
        StringBuilder builder = new StringBuilder("Hello");
        int limitIndex = index % 3 + 1;
        for (int i = 0; i < limitIndex; i++) {
            builder.append('.');
        }
        builder.append(index + 1);
        String message = builder.toString();
        template.convertAndSend(exchangeName, "", message);
        LOGGER.info(" [x] Sent '{}'", message);
    }

}
  • 消费者从绑定的匿名队列中获取消息,消息中包含.号越多,耗时越长,由于该消费者可以从两个队列中获取并消费消息,可以看做两个消费者,名称分别为instance 1instance 2
/**
 * Created by macro on 2020/5/19.
 */
public class FanoutReceiver {

    private static final Logger LOGGER = LoggerFactory.getLogger(FanoutReceiver.class);

    @RabbitListener(queues = "#{fanoutQueue1.name}")
    public void receive1(String in) {
        receive(in, 1);
    }

    @RabbitListener(queues = "#{fanoutQueue2.name}")
    public void receive2(String in) {
        receive(in, 2);
    }

    private void receive(String in, int receiver) {
        StopWatch watch = new StopWatch();
        watch.start();
        LOGGER.info("instance {} [x] Received '{}'", receiver, in);
        doWork(in);
        watch.stop();
        LOGGER.info("instance {} [x] Done in {}s", receiver, watch.getTotalTimeSeconds());
    }

    private void doWork(String in) {
        for (char ch : in.toCharArray()) {
            if (ch == '.') {
                ThreadUtil.sleep(1000);
            }
        }
    }

}
  • 在controller中添加测试接口,调用该接口开始发送消息;
/**
 * Created by macro on 2020/5/19.
 */
@Api(tags = "RabbitController", description = "RabbitMQ功能测试")
@Controller
@RequestMapping("/rabbit")
public class RabbitController {
    
    @Autowired
    private FanoutSender fanoutSender;

    @ApiOperation("发布/订阅模式")
    @RequestMapping(value = "/fanout", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult fanoutTest() {
        for(int i=0;i<10;i++){
            fanoutSender.send(i);
            ThreadUtil.sleep(1000);
        }
        return CommonResult.success(null);
    }
}
  • 运行后结果如下,可以发现生产者往队列中发送包含不同数量.号的消息,instance 1instance 2同时获取并消费了消息。

路由模式

路由模式是可以根据路由键选择性给多个消费者发送消息的模式,它包含一个生产者、两个消费者、两个队列和一个交换机。两个消费者同时绑定到不同的队列上去,两个队列通过路由键绑定到交换机上去,生产者发送消息到交换机,交换机通过路由键转发到不同队列,队列绑定的消费者接收并消费消息。

模式示意图

Spring AMQP实现

  • 添加路由模式相关Java配置,创建一个名为exchange.direct的交换机、一个生产者、两个消费者和两个匿名队列,队列通过路由键都绑定到交换机,队列1的路由键为orangeblack队列2的路由键为greenblack
/**
 * Created by macro on 2020/5/19.
 */
@Configuration
public class DirectRabbitConfig {

    @Bean
    public DirectExchange direct() {
        return new DirectExchange("exchange.direct");
    }

    @Bean
    public Queue directQueue1() {
        return new AnonymousQueue();
    }

    @Bean
    public Queue directQueue2() {
        return new AnonymousQueue();
    }

    @Bean
    public Binding directBinding1a(DirectExchange direct, Queue directQueue1) {
        return BindingBuilder.bind(directQueue1).to(direct).with("orange");
    }

    @Bean
    public Binding directBinding1b(DirectExchange direct, Queue directQueue1) {
        return BindingBuilder.bind(directQueue1).to(direct).with("black");
    }

    @Bean
    public Binding directBinding2a(DirectExchange direct, Queue directQueue2) {
        return BindingBuilder.bind(directQueue2).to(direct).with("green");
    }

    @Bean
    public Binding directBinding2b(DirectExchange direct, Queue directQueue2) {
        return BindingBuilder.bind(directQueue2).to(direct).with("black");
    }

    @Bean
    public DirectReceiver receiver() {
        return new DirectReceiver();
    }


    @Bean
    public DirectSender directSender() {
        return new DirectSender();
    }

}
  • 生产者通过send方法向交换机exchange.direct中发送消息,发送时使用不同的路由键,根据路由键会被转发到不同的队列;
/**
 * Created by macro on 2020/5/19.
 */
public class DirectSender {

 @Autowired
 private RabbitTemplate template;

 private static final String exchangeName = "exchange.direct";

 private final String[] keys = {"orange", "black", "green"};

 private static final Logger LOGGER = LoggerFactory.getLogger(DirectSender.class);

 public void send(int index) {
  StringBuilder builder = new StringBuilder("Hello to ");
  int limitIndex = index % 3;
  String key = keys[limitIndex];
  builder.append(key).append(' ');
  builder.append(index+1);
  String message = builder.toString();
  template.convertAndSend(exchangeName, key, message);
  LOGGER.info(" [x] Sent '{}'", message);
 }

}
  • 消费者从自己绑定的匿名队列中获取消息,由于该消费者可以从两个队列中获取并消费消息,可以看做两个消费者,名称分别为instance 1instance 2
/**
 * Created by macro on 2020/5/19.
 */
public class DirectReceiver {
    private static final Logger LOGGER = LoggerFactory.getLogger(DirectReceiver.class);

    @RabbitListener(queues = "#{directQueue1.name}")
    public void receive1(String in){
        receive(in, 1);
    }

    @RabbitListener(queues = "#{directQueue2.name}")
    public void receive2(String in){
        receive(in, 2);
    }

    private void receive(String in, int receiver){
        StopWatch watch = new StopWatch();
        watch.start();
        LOGGER.info("instance {} [x] Received '{}'", receiver, in);
        doWork(in);
        watch.stop();
        LOGGER.info("instance {} [x] Done in {}s", receiver, watch.getTotalTimeSeconds());
    }

    private void doWork(String in){
        for (char ch : in.toCharArray()) {
            if (ch == '.') {
                ThreadUtil.sleep(1000);
            }
        }
    }

}
  • 在controller中添加测试接口,调用该接口开始发送消息;
/**
 * Created by macro on 2020/5/19.
 */
@Api(tags = "RabbitController", description = "RabbitMQ功能测试")
@Controller
@RequestMapping("/rabbit")
public class RabbitController {

    @Autowired
    private DirectSender directSender;

    @ApiOperation("路由模式")
    @RequestMapping(value = "/direct", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult directTest() {
        for(int i=0;i<10;i++){
            directSender.send(i);
            ThreadUtil.sleep(1000);
        }
        return CommonResult.success(null);
    }
}
  • 运行后结果如下,可以发现生产者往队列中发送包含不同路由键的消息,instance 1获取到了orangeblack消息,instance 2获取到了greenblack消息。

通配符模式

通配符模式是可以根据路由键匹配规则选择性给多个消费者发送消息的模式,它包含一个生产者、两个消费者、两个队列和一个交换机。两个消费者同时绑定到不同的队列上去,两个队列通过路由键匹配规则绑定到交换机上去,生产者发送消息到交换机,交换机通过路由键匹配规则转发到不同队列,队列绑定的消费者接收并消费消息。

特殊匹配符号

  • *:只能匹配一个单词;
  • #:可以匹配零个或多个单词。

模式示意图

Spring AMQP实现

  • 添加通配符模式相关Java配置,创建一个名为exchange.topic的交换机、一个生产者、两个消费者和两个匿名队列,匹配*.orange.**.*.rabbit发送到队列1,匹配lazy.#发送到队列2
/**
 * Created by macro on 2020/5/19.
 */
@Configuration
public class TopicRabbitConfig {

    @Bean
    public TopicExchange topic() {
        return new TopicExchange("exchange.topic");
    }

    @Bean
    public Queue topicQueue1() {
        return new AnonymousQueue();
    }

    @Bean
    public Queue topicQueue2() {
        return new AnonymousQueue();
    }

    @Bean
    public Binding topicBinding1a(TopicExchange topic, Queue topicQueue1) {
        return BindingBuilder.bind(topicQueue1).to(topic).with("*.orange.*");
    }

    @Bean
    public Binding topicBinding1b(TopicExchange topic, Queue topicQueue1) {
        return BindingBuilder.bind(topicQueue1).to(topic).with("*.*.rabbit");
    }

    @Bean
    public Binding topicBinding2a(TopicExchange topic, Queue topicQueue2) {
        return BindingBuilder.bind(topicQueue2).to(topic).with("lazy.#");
    }

    @Bean
    public TopicReceiver topicReceiver() {
        return new TopicReceiver();
    }

    @Bean
    public TopicSender topicSender() {
        return new TopicSender();
    }

}
  • 生产者通过send方法向交换机exchange.topic中发送消息,消息中包含不同的路由键
/**
 * Created by macro on 2020/5/19.
 */
public class TopicSender {

 @Autowired
 private RabbitTemplate template;

 private static final String exchangeName = "exchange.topic";

 private static final Logger LOGGER = LoggerFactory.getLogger(TopicSender.class);


 private final String[] keys = {"quick.orange.rabbit", "lazy.orange.elephant", "quick.orange.fox",
   "lazy.brown.fox", "lazy.pink.rabbit", "quick.brown.fox"};

 public void send(int index) {
  StringBuilder builder = new StringBuilder("Hello to ");
  int limitIndex = index%keys.length;
  String key = keys[limitIndex];
  builder.append(key).append(' ');
  builder.append(index+1);
  String message = builder.toString();
  template.convertAndSend(exchangeName, key, message);
  LOGGER.info(" [x] Sent '{}'",message);
  System.out.println(" [x] Sent '" + message + "'");
 }

}
  • 消费者从自己绑定的匿名队列中获取消息,由于该消费者可以从两个队列中获取并消费消息,可以看做两个消费者,名称分别为instance 1instance 2
/**
 * Created by macro on 2020/5/19.
 */
public class TopicReceiver {

 private static final Logger LOGGER = LoggerFactory.getLogger(TopicReceiver.class);

 @RabbitListener(queues = "#{topicQueue1.name}")
 public void receive1(String in){
  receive(in, 1);
 }

 @RabbitListener(queues = "#{topicQueue2.name}")
 public void receive2(String in){
  receive(in, 2);
 }

 public void receive(String in, int receiver){
  StopWatch watch = new StopWatch();
  watch.start();
  LOGGER.info("instance {} [x] Received '{}'", receiver, in);
  doWork(in);
  watch.stop();
  LOGGER.info("instance {} [x] Done in {}s", receiver, watch.getTotalTimeSeconds());
 }

 private void doWork(String in){
  for (char ch : in.toCharArray()) {
   if (ch == '.') {
    ThreadUtil.sleep(1000);
   }
  }
 }

}
  • 在controller中添加测试接口,调用该接口开始发送消息;
/**
 * Created by macro on 2020/5/19.
 */
@Api(tags = "RabbitController", description = "RabbitMQ功能测试")
@Controller
@RequestMapping("/rabbit")
public class RabbitController {

    @Autowired
    private TopicSender topicSender;

    @ApiOperation("通配符模式")
    @RequestMapping(value = "/topic", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult topicTest() {
        for(int i=0;i<10;i++){
            topicSender.send(i);
            ThreadUtil.sleep(1000);
        }
        return CommonResult.success(null);
    }
}
  • 运行后结果如下,可以发现生产者往队列中发送包含不同路由键的消息,instance 1instance 2分别获取到了匹配的消息。

参考资料

RabbitMQ Tutorials:https://www.rabbitmq.com/getstarted.html

项目源码地址

https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-rabbit

本文分享自微信公众号 - macrozheng(gh_433697c99b25),作者:梦想de星空

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 手把手教你搞定权限管理,结合Spring Security实现接口的动态权限控制!

    首先我们需要创建一个过滤器,用于实现动态权限控制,这里需要注意的是doFilter方法,对于OPTIONS请求直接放行,否则前端调用会出现跨域问题。对于配置在I...

    macrozheng
  • SpringBoot中处理校验逻辑的两种方式,真的很机智!

    这种方式的优点是可以使用注解来实现参数校验,不需要一些重复的校验逻辑,但是也有一些缺点,比如需要在Controller的方法中额外注入一个BindingResu...

    macrozheng
  • 写代码有这些想法,同事才不会认为你是复制粘贴程序员!

    最近做完需求,有一些思考不够深入的代码,写一下总结,希望大家日常写代码多点思考,多点总结,加油!同时哪里有不对的,也望指出。

    macrozheng
  • 【学习】网站数据分析:网站分析的基本度量

    ? 我们在使用各种网站分析工具的时候,会看到很多不同的度量指标,可能不同的工具会有不同的命名和定义,这里列举一些常见的度量,简单说明一下它们是如何计算得到的。...

    小莹莹
  • 二十五、Hystrix累计统计流、分发流、最大并发流、配置流、功能流(附代码示例)

    上篇文章 介绍了Hystrix的“主流”:在滑动窗口内统计流、健康流。既然Hystrix的指标数据收集是基于事件驱动,那么自然可以多一些监听流,那么本文将做个收...

    YourBatman
  • CAS 5.3.1系列之使用cas overlay搭建服务端(一)

    CAS默认是要https的链接才能登录的,不过学习的话是可以先去掉https限制,本博客介绍的是基于Cas5.3.1的,之前改过4.0,4.2.7的,详情见ht...

    用户1208223
  • SpringBoot 系列教程自动配置选择生效

    写了这么久的 Spring 系列博文,发现了一个问题,之前所有的文章都是围绕的让一个东西生效;那么有没有反其道而行之的呢?

    一灰灰blog
  • [WCF安全系列]绑定、安全模式与客户端凭证类型:NetNamedPipeBinding、NetTcpBinding与NetMsmqBinding

    在前面两篇(《绑定、安全模式与客户端凭证类型:BasicHttpBinding》和《绑定、安全模式与客户端凭证类型:WSHttpBinding与WSDualHt...

    蒋金楠
  • bootstrap treeview根据后端数据id,级联勾选下级

    服务端传到前段一个树状目录某一个目录id,如何显示这个目录勾选,顺带这个目录的所有下级也勾选呢。

    hotqin888
  • 如何获取hibernate代理类代理的实际对象实例?

    在hibernate中,通过sql语句查询带clob字段的记录,查出来的结果集是List<HashMap<String,Object>>类型,在调用jackso...

    程序员一一涤生

扫码关注云+社区

领取腾讯云代金券