• [技术干货] Springboot 整合RabbitMq
    该篇文章内容较多,包括有rabbitMq相关的一些简单理论介绍,provider消息推送实例,consumer消息消费实例,Direct、Topic、Fanout的使用,消息回调、手动确认等。 (但是关于rabbitMq的安装,就不介绍了) 在安装完rabbitMq后,输入http://ip:15672/ ,是可以看到一个简单后台管理界面的。  在这个界面里面我们可以做些什么? 可以手动创建虚拟host,创建用户,分配权限,创建交换机,创建队列等等,还有查看队列消息,消费效率,推送效率等等。 以上这些管理界面的操作在这篇暂时不做扩展描述,我想着重介绍后面实例里会使用到的。 首先先介绍一个简单的一个消息推送到接收的流程,提供一个简单的图: RabbitMq -JCccc 黄色的圈圈就是我们的消息推送服务,将消息推送到 中间方框里面也就是 rabbitMq的服务器,然后经过服务器里面的交换机、队列等各种关系(后面会详细讲)将数据处理入列后,最终右边的蓝色圈圈消费者获取对应监听的消息。 常用的交换机有以下三种,因为消费者是从队列获取信息的,队列是绑定交换机的(一般),所以对应的消息推送/接收模式也会有以下几种:  Direct Exchange  直连型交换机,根据消息携带的路由键将消息投递给对应队列。 大致流程,有一个队列绑定到一个直连交换机上,同时赋予一个路由键 routing key 。 然后当一个消息携带着路由值为X,这个消息通过生产者发送给交换机时,交换机就会根据这个路由值X去寻找绑定值也是X的队列。 Fanout Exchange 扇型交换机,这个交换机没有路由键概念,就算你绑了路由键也是无视的。 这个交换机在接收到消息后,会直接转发到绑定到它上面的所有队列。 Topic Exchange 主题交换机,这个交换机其实跟直连交换机流程差不多,但是它的特点就是在它的路由键和绑定键之间是有规则的。 简单地介绍下规则:  *  (星号) 用来表示一个单词 (必须出现的) #  (井号) 用来表示任意数量(零个或多个)单词 通配的绑定键是跟队列进行绑定的,举个小例子 队列Q1 绑定键为 *.TT.*          队列Q2绑定键为  TT.# 如果一条消息携带的路由键为 A.TT.B,那么队列Q1将会收到; 如果一条消息携带的路由键为TT.AA.BB,那么队列Q2将会收到;  主题交换机是非常强大的,为啥这么膨胀? 当一个队列的绑定键为 "#"(井号) 的时候,这个队列将会无视消息的路由键,接收所有的消息。 当 * (星号) 和 # (井号) 这两个特殊字符都未在绑定键中出现的时候,此时主题交换机就拥有的直连交换机的行为。 所以主题交换机也就实现了扇形交换机的功能,和直连交换机的功能。  另外还有 Header Exchange 头交换机 ,Default Exchange 默认交换机,Dead Letter Exchange 死信交换机,这几个该篇暂不做讲述。 好了,一些简单的介绍到这里为止,  接下来我们来一起编码。 本次实例教程需要创建2个springboot项目,一个 rabbitmq-provider (生产者),一个rabbitmq-consumer(消费者)。 首先创建 rabbitmq-provider, pom.xml里用到的jar依赖:          <!--rabbitmq-->         <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> 然后application.yml:  server:   port: 8021 spring:   #给项目来个名字   application:     name: rabbitmq-provider   #配置rabbitMq 服务器   rabbitmq:     host: 127.0.0.1     port: 5672     username: root     password: root     #虚拟host 可以不设置,使用server默认host     virtual-host: JCcccHost ps:里面的虚拟host配置项不是必须的,我自己在rabbitmq服务上创建了自己的虚拟host,所以我配置了;你们不创建,就不用加这个配置项(默认写 :virtual-host: /)。 一定要注意 要注意 要注意!!!!! 那么怎么建一个单独的host呢? 假如我就是想给某个项目接入,使用一个单独host,顺便使用一个单独的账号,就好像我文中配置的 root 这样。 其实也很简便: virtual-host的创建:  账号user的创建:  然后记得给账号分配权限,指定使用某个virtual host: 其实还可以特定指定交换机使用权等等:  回归正题,继续继续。 接着我们先使用下direct exchange(直连型交换机),创建DirectRabbitConfig.java(对于队列和交换机持久化以及连接使用设置,在注释里有说明,后面的不同交换机的配置就不做同样说明了):  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Configuration public class DirectRabbitConfig {       //队列 起名:TestDirectQueue     @Bean     public Queue TestDirectQueue() {         // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效         // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable         // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。         //   return new Queue("TestDirectQueue",true,true,false);           //一般设置一下队列的持久化就好,其余两个就是默认false         return new Queue("TestDirectQueue",true);     }       //Direct交换机 起名:TestDirectExchange     @Bean     DirectExchange TestDirectExchange() {       //  return new DirectExchange("TestDirectExchange",true,true);         return new DirectExchange("TestDirectExchange",true,false);     }       //绑定  将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting     @Bean     Binding bindingDirect() {         return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");     }     @Bean     DirectExchange lonelyDirectExchange() {         return new DirectExchange("lonelyDirectExchange");     } } 然后写个简单的接口进行消息推送(根据需求也可以改为定时任务等等,具体看需求),SendMessageController.java:  import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import java.util.UUID;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @RestController public class SendMessageController {       @Autowired     RabbitTemplate rabbitTemplate;  //使用RabbitTemplate,这提供了接收/发送等等方法       @GetMapping("/sendDirectMessage")     public String sendDirectMessage() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "test message, hello!";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String,Object> map=new HashMap<>();         map.put("messageId",messageId);         map.put("messageData",messageData);         map.put("createTime",createTime);         //将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange         rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map);         return "ok";     }     } 把rabbitmq-provider项目运行,调用下接口: 因为我们目前还没弄消费者 rabbitmq-consumer,消息没有被消费的,我们去rabbitMq管理页面看看,是否推送成功: 再看看队列(界面上的各个英文项代表什么意思,可以自己查查哈,对理解还是有帮助的): 很好,消息已经推送到rabbitMq服务器上面了。 接下来,创建rabbitmq-consumer项目: pom.xml里的jar依赖:          <!--rabbitmq-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-amqp</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter</artifactId>         </dependency> 然后是 application.yml:    server:   port: 8022 spring:   #给项目来个名字   application:     name: rabbitmq-consumer   #配置rabbitMq 服务器   rabbitmq:     host: 127.0.0.1     port: 5672     username: root     password: root     #虚拟host 可以不设置,使用server默认host     virtual-host: JCcccHost 然后一样,创建DirectRabbitConfig.java(消费者单纯的使用,其实可以不用添加这个配置,直接建后面的监听就好,使用注解来让监听器监听对应的队列即可。配置上了的话,其实消费者也是生成者的身份,也能推送该消息。):  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Configuration public class DirectRabbitConfig {       //队列 起名:TestDirectQueue     @Bean     public Queue TestDirectQueue() {         return new Queue("TestDirectQueue",true);     }       //Direct交换机 起名:TestDirectExchange     @Bean     DirectExchange TestDirectExchange() {         return new DirectExchange("TestDirectExchange");     }       //绑定  将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting     @Bean     Binding bindingDirect() {         return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");     } } 然后是创建消息接收监听类,DirectReceiver.java:  @Component @RabbitListener(queues = "TestDirectQueue")//监听的队列名称 TestDirectQueue public class DirectReceiver {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("DirectReceiver消费者收到消息  : " + testMessage.toString());     }   } 然后将rabbitmq-consumer项目运行起来,可以看到把之前推送的那条消息消费下来了: 然后可以再继续调用rabbitmq-provider项目的推送消息接口,可以看到消费者即时消费消息: 那么直连交换机既然是一对一,那如果咱们配置多台监听绑定到同一个直连交互的同一个队列,会怎么样? 可以看到是实现了轮询的方式对消息进行消费,而且不存在重复消费。 接着,我们使用Topic Exchange 主题交换机。 在rabbitmq-provider项目里面创建TopicRabbitConfig.java:  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/   @Configuration public class TopicRabbitConfig {     //绑定键     public final static String man = "topic.man";     public final static String woman = "topic.woman";       @Bean     public Queue firstQueue() {         return new Queue(TopicRabbitConfig.man);     }       @Bean     public Queue secondQueue() {         return new Queue(TopicRabbitConfig.woman);     }       @Bean     TopicExchange exchange() {         return new TopicExchange("topicExchange");     }         //将firstQueue和topicExchange绑定,而且绑定的键值为topic.man     //这样只要是消息携带的路由键是topic.man,才会分发到该队列     @Bean     Binding bindingExchangeMessage() {         return BindingBuilder.bind(firstQueue()).to(exchange()).with(man);     }       //将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#     // 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列     @Bean     Binding bindingExchangeMessage2() {         return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");     }   } 然后添加多2个接口,用于推送消息到主题交换机:      @GetMapping("/sendTopicMessage1")     public String sendTopicMessage1() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "message: M A N ";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> manMap = new HashMap<>();         manMap.put("messageId", messageId);         manMap.put("messageData", messageData);         manMap.put("createTime", createTime);         rabbitTemplate.convertAndSend("topicExchange", "topic.man", manMap);         return "ok";     }       @GetMapping("/sendTopicMessage2")     public String sendTopicMessage2() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "message: woman is all ";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> womanMap = new HashMap<>();         womanMap.put("messageId", messageId);         womanMap.put("messageData", messageData);         womanMap.put("createTime", createTime);         rabbitTemplate.convertAndSend("topicExchange", "topic.woman", womanMap);         return "ok";     } } 生产者这边已经完事,先不急着运行,在rabbitmq-consumer项目上,创建TopicManReceiver.java: import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.util.Map;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Component @RabbitListener(queues = "topic.man") public class TopicManReceiver {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("TopicManReceiver消费者收到消息  : " + testMessage.toString());     } } 再创建一个TopicTotalReceiver.java:  package com.elegant.rabbitmqconsumer.receiver;   import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.util.Map;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/   @Component @RabbitListener(queues = "topic.woman") public class TopicTotalReceiver {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("TopicTotalReceiver消费者收到消息  : " + testMessage.toString());     } } 同样,加主题交换机的相关配置,TopicRabbitConfig.java(消费者一定要加这个配置吗? 不需要的其实,理由在前面已经说过了。):  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/   @Configuration public class TopicRabbitConfig {     //绑定键     public final static String man = "topic.man";     public final static String woman = "topic.woman";       @Bean     public Queue firstQueue() {         return new Queue(TopicRabbitConfig.man);     }       @Bean     public Queue secondQueue() {         return new Queue(TopicRabbitConfig.woman);     }       @Bean     TopicExchange exchange() {         return new TopicExchange("topicExchange");     }     //将firstQueue和topicExchange绑定,而且绑定的键值为topic.man     //这样只要是消息携带的路由键是topic.man,才会分发到该队列     @Bean     Binding bindingExchangeMessage() {         return BindingBuilder.bind(firstQueue()).to(exchange()).with(man);     }       //将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#     // 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列     @Bean     Binding bindingExchangeMessage2() {         return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");     }   }  然后把rabbitmq-provider,rabbitmq-consumer两个项目都跑起来,先调用/sendTopicMessage1  接口: 然后看消费者rabbitmq-consumer的控制台输出情况: TopicManReceiver监听队列1,绑定键为:topic.man TopicTotalReceiver监听队列2,绑定键为:topic.# 而当前推送的消息,携带的路由键为:topic.man   所以可以看到两个监听消费者receiver都成功消费到了消息,因为这两个recevier监听的队列的绑定键都能与这条消息携带的路由键匹配上。 接下来调用接口/sendTopicMessage2: 然后看消费者rabbitmq-consumer的控制台输出情况: TopicManReceiver监听队列1,绑定键为:topic.man TopicTotalReceiver监听队列2,绑定键为:topic.# 而当前推送的消息,携带的路由键为:topic.woman 所以可以看到两个监听消费者只有TopicTotalReceiver成功消费到了消息。 接下来是使用Fanout Exchang 扇型交换机。 同样地,先在rabbitmq-provider项目上创建FanoutRabbitConfig.java: import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/   @Configuration public class FanoutRabbitConfig {       /**      *  创建三个队列 :fanout.A   fanout.B  fanout.C      *  将三个队列都绑定在交换机 fanoutExchange 上      *  因为是扇型交换机, 路由键无需配置,配置也不起作用      */         @Bean     public Queue queueA() {         return new Queue("fanout.A");     }       @Bean     public Queue queueB() {         return new Queue("fanout.B");     }       @Bean     public Queue queueC() {         return new Queue("fanout.C");     }       @Bean     FanoutExchange fanoutExchange() {         return new FanoutExchange("fanoutExchange");     }       @Bean     Binding bindingExchangeA() {         return BindingBuilder.bind(queueA()).to(fanoutExchange());     }       @Bean     Binding bindingExchangeB() {         return BindingBuilder.bind(queueB()).to(fanoutExchange());     }       @Bean     Binding bindingExchangeC() {         return BindingBuilder.bind(queueC()).to(fanoutExchange());     } } 然后是写一个接口用于推送消息,        @GetMapping("/sendFanoutMessage")     public String sendFanoutMessage() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "message: testFanoutMessage ";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> map = new HashMap<>();         map.put("messageId", messageId);         map.put("messageData", messageData);         map.put("createTime", createTime);         rabbitTemplate.convertAndSend("fanoutExchange", null, map);         return "ok";     } 接着在rabbitmq-consumer项目里加上消息消费类,  FanoutReceiverA.java:  import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.util.Map; /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Component @RabbitListener(queues = "fanout.A") public class FanoutReceiverA {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverA消费者收到消息  : " +testMessage.toString());     }   } FanoutReceiverB.java:  import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.util.Map; /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Component @RabbitListener(queues = "fanout.B") public class FanoutReceiverB {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverB消费者收到消息  : " +testMessage.toString());     }   } FanoutReceiverC.java:  import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.util.Map;   /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Component @RabbitListener(queues = "fanout.C") public class FanoutReceiverC {       @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverC消费者收到消息  : " +testMessage.toString());     }   } 然后加上扇型交换机的配置类,FanoutRabbitConfig.java(消费者真的要加这个配置吗? 不需要的其实,理由在前面已经说过了):  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Configuration public class FanoutRabbitConfig {       /**      *  创建三个队列 :fanout.A   fanout.B  fanout.C      *  将三个队列都绑定在交换机 fanoutExchange 上      *  因为是扇型交换机, 路由键无需配置,配置也不起作用      */         @Bean     public Queue queueA() {         return new Queue("fanout.A");     }       @Bean     public Queue queueB() {         return new Queue("fanout.B");     }       @Bean     public Queue queueC() {         return new Queue("fanout.C");     }       @Bean     FanoutExchange fanoutExchange() {         return new FanoutExchange("fanoutExchange");     }       @Bean     Binding bindingExchangeA() {         return BindingBuilder.bind(queueA()).to(fanoutExchange());     }       @Bean     Binding bindingExchangeB() {         return BindingBuilder.bind(queueB()).to(fanoutExchange());     }       @Bean     Binding bindingExchangeC() {         return BindingBuilder.bind(queueC()).to(fanoutExchange());     } } 最后将rabbitmq-provider和rabbitmq-consumer项目都跑起来,调用下接口/sendFanoutMessage : 然后看看rabbitmq-consumer项目的控制台情况: 可以看到只要发送到 fanoutExchange 这个扇型交换机的消息, 三个队列都绑定这个交换机,所以三个消息接收类都监听到了这条消息。 到了这里其实三个常用的交换机的使用我们已经完毕了,那么接下来我们继续讲讲消息的回调,其实就是消息确认(生产者推送消息成功,消费者接收消息成功)。 在rabbitmq-provider项目的application.yml文件上,加上消息确认的配置项后:  ps: 本篇文章使用springboot版本为 2.1.7.RELEASE ;  如果你们在配置确认回调,测试发现无法触发回调函数,那么存在原因也许是因为版本导致的配置项不起效, 可以把publisher-confirms: true 替换为  publisher-confirm-type: correlated  server:   port: 8021 spring:   #给项目来个名字   application:     name: rabbitmq-provider   #配置rabbitMq 服务器   rabbitmq:     host: 127.0.0.1     port: 5672     username: root     password: root     #虚拟host 可以不设置,使用server默认host     virtual-host: JCcccHost       #确认消息已发送到交换机(Exchange)     #publisher-confirms: true     publisher-confirm-type: correlated       #确认消息已发送到队列(Queue)     publisher-returns: true 然后是配置相关的消息确认回调函数,RabbitConfig.java:  import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;     /**  * @Author : JCccc  * @CreateTime : 2019/9/3  * @Description :  **/ @Configuration public class RabbitConfig {       @Bean     public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){         RabbitTemplate rabbitTemplate = new RabbitTemplate();         rabbitTemplate.setConnectionFactory(connectionFactory);         //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数         rabbitTemplate.setMandatory(true);           rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {             @Override             public void confirm(CorrelationData correlationData, boolean ack, String cause) {                 System.out.println("ConfirmCallback:     "+"相关数据:"+correlationData);                 System.out.println("ConfirmCallback:     "+"确认情况:"+ack);                 System.out.println("ConfirmCallback:     "+"原因:"+cause);             }         });           rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {             @Override             public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {                 System.out.println("ReturnCallback:     "+"消息:"+message);                 System.out.println("ReturnCallback:     "+"回应码:"+replyCode);                 System.out.println("ReturnCallback:     "+"回应信息:"+replyText);                 System.out.println("ReturnCallback:     "+"交换机:"+exchange);                 System.out.println("ReturnCallback:     "+"路由键:"+routingKey);             }         });           return rabbitTemplate;     }   } 到这里,生产者推送消息的消息确认调用回调函数已经完毕。 可以看到上面写了两个回调函数,一个叫 ConfirmCallback ,一个叫 RetrunCallback; 那么以上这两种回调函数都是在什么情况会触发呢?  先从总体的情况分析,推送消息存在四种情况:  ①消息推送到server,但是在server里找不到交换机 ②消息推送到server,找到交换机了,但是没找到队列 ③消息推送到sever,交换机和队列啥都没找到 ④消息推送成功  那么我先写几个接口来分别测试和认证下以上4种情况,消息确认触发回调函数的情况:  ①消息推送到server,但是在server里找不到交换机 写个测试接口,把消息推送到名为‘non-existent-exchange’的交换机上(这个交换机是没有创建没有配置的):      @GetMapping("/TestMessageAck")     public String TestMessageAck() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "message: non-existent-exchange test message ";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> map = new HashMap<>();         map.put("messageId", messageId);         map.put("messageData", messageData);         map.put("createTime", createTime);         rabbitTemplate.convertAndSend("non-existent-exchange", "TestDirectRouting", map);         return "ok";     } 调用接口,查看rabbitmq-provuder项目的控制台输出情况(原因里面有说,没有找到交换机'non-existent-exchange'):  2019-09-04 09:37:45.197 ERROR 8172 --- [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'non-existent-exchange' in vhost 'JCcccHost', class-id=60, method-id=40) ConfirmCallback:     相关数据:null ConfirmCallback:     确认情况:false ConfirmCallback:     原因:channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'non-existent-exchange' in vhost 'JCcccHost', class-id=60, method-id=40)     结论: ①这种情况触发的是 ConfirmCallback 回调函数。   ②消息推送到server,找到交换机了,但是没找到队列   这种情况就是需要新增一个交换机,但是不给这个交换机绑定队列,我来简单地在DirectRabitConfig里面新增一个直连交换机,名叫‘lonelyDirectExchange’,但没给它做任何绑定配置操作:      @Bean     DirectExchange lonelyDirectExchange() {         return new DirectExchange("lonelyDirectExchange");     } 然后写个测试接口,把消息推送到名为‘lonelyDirectExchange’的交换机上(这个交换机是没有任何队列配置的):      @GetMapping("/TestMessageAck2")     public String TestMessageAck2() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "message: lonelyDirectExchange test message ";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> map = new HashMap<>();         map.put("messageId", messageId);         map.put("messageData", messageData);         map.put("createTime", createTime);         rabbitTemplate.convertAndSend("lonelyDirectExchange", "TestDirectRouting", map);         return "ok";     } 调用接口,查看rabbitmq-provuder项目的控制台输出情况:  ReturnCallback:     消息:(Body:'{createTime=2019-09-04 09:48:01, messageId=563077d9-0a77-4c27-8794-ecfb183eac80, messageData=message: lonelyDirectExchange test message }' MessageProperties [headers={}, contentType=application/x-java-serialized-object, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0]) ReturnCallback:     回应码:312 ReturnCallback:     回应信息:NO_ROUTE ReturnCallback:     交换机:lonelyDirectExchange ReturnCallback:     路由键:TestDirectRouting ConfirmCallback:     相关数据:null ConfirmCallback:     确认情况:true ConfirmCallback:     原因:null 可以看到这种情况,两个函数都被调用了; 这种情况下,消息是推送成功到服务器了的,所以ConfirmCallback对消息确认情况是true; 而在RetrunCallback回调函数的打印参数里面可以看到,消息是推送到了交换机成功了,但是在路由分发给队列的时候,找不到队列,所以报了错误 NO_ROUTE 。   结论:②这种情况触发的是 ConfirmCallback和RetrunCallback两个回调函数。  ③消息推送到sever,交换机和队列啥都没找到  这种情况其实一看就觉得跟①很像,没错 ,③和①情况回调是一致的,所以不做结果说明了。   结论: ③这种情况触发的是 ConfirmCallback 回调函数。   ④消息推送成功 那么测试下,按照正常调用之前消息推送的接口就行,就调用下 /sendFanoutMessage接口,可以看到控制台输出:  ConfirmCallback:     相关数据:null ConfirmCallback:     确认情况:true ConfirmCallback:     原因:null 结论: ④这种情况触发的是 ConfirmCallback 回调函数。 以上是生产者推送消息的消息确认 回调函数的使用介绍(可以在回调函数根据需求做对应的扩展或者业务数据处理)。  接下来我们继续, 消费者接收到消息的消息确认机制。  和生产者的消息确认机制不同,因为消息接收本来就是在监听消息,符合条件的消息就会消费下来。 所以,消息接收的确认机制主要存在三种模式:  ①自动确认, 这也是默认的消息确认情况。  AcknowledgeMode.NONE RabbitMQ成功将消息发出(即将消息成功写入TCP Socket)中立即认为本次投递已经被正确处理,不管消费者端是否成功处理本次投递。 所以这种情况如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。 一般这种情况我们都是使用try catch捕捉异常后,打印日志用于追踪数据,这样找出对应数据再做后续处理。  ② 根据情况确认, 这个不做介绍 ③ 手动确认 , 这个比较关键,也是我们配置接收消息确认机制时,多数选择的模式。 消费者收到消息后,手动调用basic.ack/basic.nack/basic.reject后,RabbitMQ收到这些消息后,才认为本次投递成功。 basic.ack用于肯定确认  basic.nack用于否定确认(注意:这是AMQP 0-9-1的RabbitMQ扩展)  basic.reject用于否定确认,但与basic.nack相比有一个限制:一次只能拒绝单条消息   消费者端以上的3个方法都表示消息已经被正确投递,但是basic.ack表示消息已经被正确处理。 而basic.nack,basic.reject表示没有被正确处理: 着重讲下reject,因为有时候一些场景是需要重新入列的。 channel.basicReject(deliveryTag, true);  拒绝消费当前消息,如果第二参数传入true,就是将数据重新丢回队列里,那么下次还会消费这消息。设置false,就是告诉服务器,我已经知道这条消息数据了,因为一些原因拒绝它,而且服务器也把这个消息丢掉就行。 下次不想再消费这条消息了。 使用拒绝后重新入列这个确认模式要谨慎,因为一般都是出现异常的时候,catch异常再拒绝入列,选择是否重入列。 但是如果使用不当会导致一些每次都被你重入列的消息一直消费-入列-消费-入列这样循环,会导致消息积压。 顺便也简单讲讲 nack,这个也是相当于设置不消费某条消息。 channel.basicNack(deliveryTag, false, true); 第一个参数依然是当前消息到的数据的唯一id; 第二个参数是指是否针对多条消息;如果是true,也就是说一次性针对当前通道的消息的tagID小于当前这条消息的,都拒绝确认。 第三个参数是指是否重新入列,也就是指不确认的消息是否重新丢回到队列里面去。 同样使用不确认后重新入列这个确认模式要谨慎,因为这里也可能因为考虑不周出现消息一直被重新丢回去的情况,导致积压。 看了上面这么多介绍,接下来我们一起配置下,看看一般的消息接收 手动确认是怎么样的。 ​​​在消费者项目里, 新建MessageListenerConfig.java上添加代码相关的配置代码: import com.elegant.rabbitmqconsumer.receiver.MyAckReceiver; import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   /**  * @Author : JCccc  * @CreateTime : 2019/9/4  * @Description :  **/ @Configuration public class MessageListenerConfig {       @Autowired     private CachingConnectionFactory connectionFactory;     @Autowired     private MyAckReceiver myAckReceiver;//消息接收处理类       @Bean     public SimpleMessageListenerContainer simpleMessageListenerContainer() {         SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);         container.setConcurrentConsumers(1);         container.setMaxConcurrentConsumers(1);         container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认消息         //设置一个队列         container.setQueueNames("TestDirectQueue");         //如果同时设置多个如下: 前提是队列都是必须已经创建存在的         //  container.setQueueNames("TestDirectQueue","TestDirectQueue2","TestDirectQueue3");         //另一种设置队列的方法,如果使用这种情况,那么要设置多个,就使用addQueues         //container.setQueues(new Queue("TestDirectQueue",true));         //container.addQueues(new Queue("TestDirectQueue2",true));         //container.addQueues(new Queue("TestDirectQueue3",true));         container.setMessageListener(myAckReceiver);           return container;     } } 对应的手动确认消息监听类,MyAckReceiver.java(手动确认模式需要实现 ChannelAwareMessageListener): //之前的相关监听器可以先注释掉,以免造成多个同类型监听器都监听同一个队列。  import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; import org.springframework.stereotype.Component;   import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import java.util.Map;   @Component   public class MyAckReceiver implements ChannelAwareMessageListener {       @Override     public void onMessage(Message message, Channel channel) throws Exception {         long deliveryTag = message.getMessageProperties().getDeliveryTag();         try {             byte[] body = message.getBody();             ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(body));             Map<String,String> msgMap = (Map<String,String>) ois.readObject();             String messageId = msgMap.get("messageId");             String messageData = msgMap.get("messageData");             String createTime = msgMap.get("createTime");             ois.close();             System.out.println("  MyAckReceiver  messageId:"+messageId+"  messageData:"+messageData+"  createTime:"+createTime);             System.out.println("消费的主题消息来自:"+message.getMessageProperties().getConsumerQueue());             channel.basicAck(deliveryTag, true); //第二个参数,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息 //            channel.basicReject(deliveryTag, true);//第二个参数,true会重新放回队列,所以需要自己根据业务逻辑判断什么时候使用拒绝         } catch (Exception e) {             channel.basicReject(deliveryTag, false);             e.printStackTrace();         }     }      } 这时,先调用接口/sendDirectMessage, 给直连交换机TestDirectExchange 的队列TestDirectQueue 推送一条消息,可以看到监听器正常消费了下来: 到这里,我们其实已经掌握了怎么去使用消息消费的手动确认了。 但是这个场景往往不够! 因为很多伙伴之前给我评论反应,他们需要这个消费者项目里面,监听的好几个队列都想变成手动确认模式,而且处理的消息业务逻辑不一样。 没有问题,接下来看代码 场景: 除了直连交换机的队列TestDirectQueue需要变成手动确认以外,我们还需要将一个其他的队列 或者多个队列也变成手动确认,而且不同队列实现不同的业务处理。 那么我们需要做的第一步,往SimpleMessageListenerContainer里添加多个队列: 然后我们的手动确认消息监听类,MyAckReceiver.java 就可以同时将上面设置到的队列的消息都消费下来。 但是我们需要做不用的业务逻辑处理,那么只需要  根据消息来自的队列名进行区分处理即可,如:  import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import java.util.Map;     @Component public class MyAckReceiver implements ChannelAwareMessageListener {       @Override     public void onMessage(Message message, Channel channel) throws Exception {         long deliveryTag = message.getMessageProperties().getDeliveryTag();         try {             byte[] body = message.getBody();             ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(body));             Map<String,String> msgMap = (Map<String,String>) ois.readObject();             String messageId = msgMap.get("messageId");             String messageData = msgMap.get("messageData");             String createTime = msgMap.get("createTime");             ois.close();               if ("TestDirectQueue".equals(message.getMessageProperties().getConsumerQueue())){                 System.out.println("消费的消息来自的队列名为:"+message.getMessageProperties().getConsumerQueue());                 System.out.println("消息成功消费到  messageId:"+messageId+"  messageData:"+messageData+"  createTime:"+createTime);                 System.out.println("执行TestDirectQueue中的消息的业务处理流程......");                              }               if ("fanout.A".equals(message.getMessageProperties().getConsumerQueue())){                 System.out.println("消费的消息来自的队列名为:"+message.getMessageProperties().getConsumerQueue());                 System.out.println("消息成功消费到  messageId:"+messageId+"  messageData:"+messageData+"  createTime:"+createTime);                 System.out.println("执行fanout.A中的消息的业务处理流程......");               }                          channel.basicAck(deliveryTag, true); //            channel.basicReject(deliveryTag, true);//为true会重新放回队列         } catch (Exception e) {             channel.basicReject(deliveryTag, false);             e.printStackTrace();         }     } } ok,这时候我们来分别往不同队列推送消息,看看效果: 调用接口/sendDirectMessage  和 /sendFanoutMessage , 如果你还想新增其他的监听队列,也就是按照这种方式新增配置即可(或者完全可以分开多个消费者项目去监听处理)。  好,这篇Springboot整合rabbitMq教程就暂且到此。 ————————————————                       原文链接:https://blog.csdn.net/qq_35387940/article/details/100514134 
  • [技术干货] Spring boot之@EnableAutoConfiguration
    一、@EnableAutoConfiguration的作用 简单点说就是Spring Boot根据依赖中的jar包,自动选择实例化某些配置,配置类必须有@Configuration注解。 说白了,还是实例化对象,只是实例化的是依赖包中的类。 另外,我们也可以按照自动装配的规范自己定义装配的类。 二、@EnableAutoConfiguration和 @Configuration 的区别 @Configuration:表示作用的类是个配置类。我们平时也会写配置类,比如我们系统中的DataSourceConfig类,但是由于这个类是在Starter对应的子目录下,会自动加载,所以@EnableAutoConfiguration就作用不到了。 @EnableAutoConfiguration:是一个加载Starter目录包之外的需要Spring自动生成bean对象(是否需要的依据是"META-INF/spring.factories"中org.springframework.boot.autoconfigure.EnableAutoConfiguration后面是有能找到那个bean)的带有@Configuration注解的类。一般就是对各种引入的spring-boot-starter依赖包指定的(spring.factories)类进行实例化。 整体流程: 1、我们启动类中会加上@SpringBootApplication 2、@SpringBootApplication包含了@EnableAutoConfiguration,告知应用启动的时候需要扫描依赖包中需要实例化的类 3、Springboot启动的时候会去扫描META-INF/spring.factories,查看具体是哪些类需要实例化 4、扫描哪些需要实例化的类,看下是否有@Configuration注解,如果有,则实例化 5、实例化的时候可能需要一些配置属性,一般这些类都会加上@EnableConfigurationProperties(RocketMQProperties.class) 6、RocketMQProperties会将属性映射为bean类 三、@EnableAutoConfiguration案例 1、Kafka自动装配 我们会发现,当我们在application.properties中配置spring.kafka.xxx,就可以在程序中直接使用了,说明Kafka已经被实例化了,问题来了,是什么时候实例化的?其实就是@EnableAutoConfiguration注解自动实例化的。实现类:KafkaAutoConfiguration 2、JMX自动装配 当我们引入Spring Boot Actuator相关包之后就能自动采集到Spring boot应用的相关运行指标了,怎么做到的? 其实这些指标都是通过JMX标准采集的,而Spring boot的自动装配也包含了JMX,即SpringApplicationAdminJmxAutoConfiguration。 四、spring-boot-starter与@EnableAutoConfiguration的关系 spring-boot-starter:自动导入组件对应的maven包,目的主要是简单开发人员手动去配置maven各种依赖包,不仅麻烦,碰到版本匹配问题还很容易出错。 @EnableAutoConfiguration:需要依赖已经加载好的包进行bean实例化。但不一定需要依赖spring-boot-starter,自动手动把包加载也可以 五、关于实例化 在Jfinal中我们需要通过Plugin的规范去生成对象到上下文中。 而在Spring boot中,我们可以通过@Configuration以及@Service,@Component等注解生成对象到IOC容器中。  未必所有实例化都是通过@EnableAutoConfiguration,@Configuration以及@Service,@Component等注解,也可以通过其他方式(说白了怎么加载类的问题,因为实例化最终就是找到类,然后通过反射生成对象而已)。  其他实例化方式其实可以从META-INF/spring.factories中看出来,里面不仅有org.springframework.boot.autoconfigure.EnableAutoConfiguration,还有org.springframework.context.ApplicationContextInitializer(初始化的时候实例化),org.springframework.context.ApplicationListener(初始化监听的时候实例化),org.springframework.boot.diagnostics.FailureAnalyzer(应用启动失败分析类)等等。  但是对于这些实例化方式有个共同点,都需要在META-INF/spring.factories中配置。  六、默认自动装配的类 # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\ org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\ org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\ org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\ org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\ org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\ org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\ org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\ org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\ org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\ org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration ———————————————— 原文链接:https://blog.csdn.net/w2009211777/article/details/122609547 
  • [技术干货] SpringBoot集成RabbitMQ
    spring boot版本  <parent>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-parent</artifactId>     <version>2.2.2.RELEASE</version>     <relativePath/> <!-- lookup parent from repository --> </parent> 首先创建一个生产者rabbitmq-provider,yml配置如下:  server:   port: 8021   servlet:     application-display-name: rabbitmq-provider     context-path: /rabbitmq-provider     session:       timeout: 30m spring:   application:     name: ${server.servlet.application-display-name}   rabbitmq:     sys-name: ${server.servlet.application-display-name}     host: 127.0.0.1     port: 5672     username: guest     password: guest     virtual-host: /     publisher-confirms: true #支持发布确认     publisher-returns: true  #支持发布返回     listener:       simple:         acknowledge-mode: manual #采用手动应答         retry:           enabled: true #是否支持重试           initial-interval: 1000 # 第一次和第二次 尝试发布或交付(此处是消费者交付,template.retry中是消费者发布)的 间隔时间           max-interval: 10000   # 两次重试的最大间隔           max-attempts: 1  # 尝试发布或交付的 最大次数(重试次数)           multiplier: 1.0  # 每次重试都比上一次重试间隔时长大x倍           stateless: true  # 重试是无状态还是有状态的 pom.xml:  <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> java版本:  ```java <properties>     <java.version>1.8</java.version> </properties> 创建一个消费者 rabbitmq-consumer: yml文件  server:   port: 8022   servlet:     application-display-name: rabbitmq-consumer     context-path: /rabbitmq-consumer     session:       timeout: 30m spring:   application:     name: ${server.servlet.application-display-name}   rabbitmq:     sys-name: ${server.servlet.application-display-name}     host: 127.0.0.1     port: 5672     username: guest     password: guest     virtual-host: /     publisher-confirms: true #支持发布确认     publisher-returns: true  #支持发布返回     listener:       simple:         acknowledge-mode: manual #采用手动应答         retry:           enabled: true #是否支持重试           initial-interval: 1000 # 第一次和第二次 尝试发布或交付(此处是消费者交付,template.retry中是消费者发布)的 间隔时间           max-interval: 10000   # 两次重试的最大间隔           max-attempts: 1  # 尝试发布或交付的 最大次数(重试次数)           multiplier: 1.0  # 每次重试都比上一次重试间隔时长大x倍           stateless: true  # 重试是无状态还是有状态的 pom文件与jdk与生产者相同。  Direct Exchange例子: 创建配置文件:  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  /**  * 方法名:          DirectRabbitConfig  * 方法功能描述:  *  * @param:  * @return:  * @Author:   * @Create Date:   2019/12/19  */ @Configuration public class DirectRabbitConfig {     //队列 起名:TestDirectQueue     @Bean     public Queue TestDirectQueue() {         return new Queue("TestDirectQueue",true);  //true 是否持久     }      //Direct交换机 起名:TestDirectExchange     @Bean     DirectExchange TestDirectExchange() {         return new DirectExchange("TestDirectExchange");     }      //绑定  将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting     @Bean     Binding bindingDirect() {         return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");     }  } 创建发送消息的Rest  import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;  import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import java.util.UUID;  /**  * 方法名:          SendMessageController  * 方法功能描述:  *  * @param:  * @return:  * @Author: 陈超  * @Create Date:   2019/12/19  */ @RestController public class SendMessageController {      @Autowired     RabbitTemplate rabbitTemplate;  //使用RabbitTemplate,这提供了接收/发送等等方法      @GetMapping("/sendDirectMessage")     public String sendDirectMessage() {         String messageId = String.valueOf(UUID.randomUUID());         String messageData = "test message, hello!";         String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));         Map<String, Object> map = new HashMap<>();         map.put("messageId", messageId);         map.put("messageData", messageData);         map.put("createTime", createTime);         //将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange         rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map);         return "ok";     } } 启动项目运行,返回OK说明发送消息成功 打开http://localhost:15672/#/queues 队列TestDirectQueue已经加入到消息队列中。 现在使用消费者来消费消息,同样的创建DirectRabbitConfig类,与生产者的相同。 创建接收消息的类:  import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component;  import java.util.Map;  /**  * 方法名:          DirectReceiver  * 方法功能描述:  *  * @param:  * @return:  * @Author: cc  * @Create Date:   2019/12/20  */  @Component @RabbitListener(queues = "TestDirectQueue")//监听的队列名称 TestDirectQueue public class DirectReceiver {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("DirectReceiver消费者收到消息  : " + testMessage.toString());     }  }  现在已经将生产者与消费者都准备好,启动消费者rabbitmq-consumer,RabbitListener监听器会监听名称为“TestDirectQueue”的队列,将消息接收打印。 同时,生产者发送消息,消费者也会显示发送的消息。  Topic Exchange举例: 首先回顾,topic是按照通配符的方式来进行匹配的。 在生产者中加入配置文件TopicRabbitConfig:  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  /**  * 方法名:          TopicRabbitConfig  * 方法功能描述:  *  * @param:  * @return:  * @Author: 陈超  * @Create Date:   2019/12/20  */ @Configuration public class TopicRabbitConfig {      //绑定键     public static final String red = "topic.red";     public static final String green = "topic.green";      @Bean     public Queue firstQueue() {         return new Queue(TopicRabbitConfig.red);     }      @Bean     public Queue secondQueue() {         return new Queue(TopicRabbitConfig.green);     }      @Bean     TopicExchange exchange() {         return new TopicExchange("topicExchange");     }      //将firstQueue和topicExchange绑定,而且绑定的键值为topic.red     //这样只要是消息携带的路由键是topic.red,才会分发到该队列     @Bean     Binding bindingExchangeMessage() {         return BindingBuilder.bind(firstQueue()).to(exchange()).with(red);     }      //将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#     // 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列     @Bean     Binding bindingExchangeMessage2() {         return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");     }  }  也就是secondQueue绑定了"topic.green",而secondQueue绑定了bindingExchangeMessage2。那么green就可以按照topic的规则进行适配。 创建发送机:  @GetMapping("/sendTopicMessageRed") public String sendTopicMessageRed() {     String messageId = String.valueOf(UUID.randomUUID());     String messageData = "红灯消息 RED";     String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));     Map<String, Object> redMap = new HashMap<>();     redMap.put("messageId", messageId);     redMap.put("messageData", messageData);     redMap.put("createTime", createTime);     rabbitTemplate.convertAndSend("topicExchange", "topic.red", redMap);     return "ok"; }  @GetMapping("/sendTopicMessageGreen") public String sendTopicMessageGreen() {     String messageId = String.valueOf(UUID.randomUUID());     String messageData = "绿灯与红灯消息 ";     String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));     Map<String, Object> greenMap = new HashMap<>();     greenMap.put("messageId", messageId);     greenMap.put("messageData", messageData);     greenMap.put("createTime", createTime);     rabbitTemplate.convertAndSend("topicExchange", "topic.green", greenMap);     return "ok"; } 接下来在消费者中创建同样的配置类TopicRabbitConfig。创建接收机 @Component @RabbitListener(queues = "topic.red") public class TopicRedReceiver {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("TopicRedReceiver  : " + testMessage.toString());     } }  @Component @RabbitListener(queues = "topic.green") public class TopicGreenReceiver {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("TopicGreenReceiver  : " + testMessage.toString());     } } 接下来发送消息: 生产者发送红灯消息,调用 消费者接收到 可以看出绿灯的接收者也接受到红灯消息,这是因为我们在TopicRabbitConfig 配置中的 //将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#     // 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列     @Bean     Binding bindingExchangeMessage2() {         return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");     } 发挥了作用。 现在发送绿灯消息  只接收到了绿灯消息,因为红灯的接收机没有进行适配。 Fanout Exchang 适配器 创建配置文件:  import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  /**  * 方法名:          FanoutRabbitConfig  * 方法功能描述:  *  * @param:  * @return:  * @Author:   * @Create Date:   2019/12/20  */  @Configuration public class FanoutRabbitConfig {      /**      *  创建三个队列 :fanout.A   fanout.B  fanout.C      *  将三个队列都绑定在交换机 fanoutExchange 上      *  因为是扇型交换机, 路由键无需配置,配置也不起作用      */     @Bean     public Queue queueA() {         return new Queue("fanout.A");     }      @Bean     public Queue queueB() {         return new Queue("fanout.B");     }      @Bean     public Queue queueC() {         return new Queue("fanout.C");     }      @Bean     FanoutExchange fanoutExchange() {         return new FanoutExchange("fanoutExchange");     }      @Bean     Binding bindingExchangeA() {         return BindingBuilder.bind(queueA()).to(fanoutExchange());     }      @Bean     Binding bindingExchangeB() {         return BindingBuilder.bind(queueB()).to(fanoutExchange());     }      @Bean     Binding bindingExchangeC() {         return BindingBuilder.bind(queueC()).to(fanoutExchange());     } } 在生产者中创建发送方法: @GetMapping("/sendFanoutMessage") public String sendFanoutMessage() {     String messageId = String.valueOf(UUID.randomUUID());     String messageData = "message: testFanoutMessage ";     String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));     Map<String, Object> map = new HashMap<>();     map.put("messageId", messageId);     map.put("messageData", messageData);     map.put("createTime", createTime);     rabbitTemplate.convertAndSend("fanoutExchange", null, map);     return "ok"; } 交换机指向了创建的交换机fanoutExchange。 接下来是消费者 同样创建配置文件FanoutRabbitConfig ,与生产者的相同 创建接受类  @Component @RabbitListener(queues = "fanout.A") public class FanoutReceiverA {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverA消费者收到消息  : " +testMessage.toString());     } } @Component @RabbitListener(queues = "fanout.B") public class FanoutReceiverB {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverB消费者收到消息  : " +testMessage.toString());     }  } @Component @RabbitListener(queues = "fanout.C") public class FanoutReceiverC {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("FanoutReceiverC消费者收到消息  : " +testMessage.toString());     }  } 创建好之后调用生产者中的发送消息的方法sendFanoutMessage。 三个方法都接受到,说明扇形交换器成功 RabbirMQ消息的回调与消息的确认 在application.yml添加配置项  rabbitmq:   sys-name: ${server.servlet.application-display-name}   host: 127.0.0.1   port: 5672   username: guest   password: guest   virtual-host: /   publisher-confirm-type: correlated #确认消息已发送到交换机   publisher-returns: true  #确认消息已发送到队列(Queue) 在生产者中添加RabbitConfig配置  import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  /**  * 方法名:          RabbitConfig  * 方法功能描述:  *  * @param:  * @return:  * @Author:   * @Create Date:   2019/12/23  */  @Configuration public class RabbitConfig {      @Bean     public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) {         RabbitTemplate rabbitTemplate = new RabbitTemplate();         rabbitTemplate.setConnectionFactory(connectionFactory);         //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数         rabbitTemplate.setMandatory(Boolean.TRUE);          rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {             @Override             public void confirm(CorrelationData correlationData, boolean b, String s) {                 System.out.println("ConfirmCallback:     " + "相关数据:" + correlationData);                 System.out.println("ConfirmCallback:     " + "确认情况:" + b);                 System.out.println("ConfirmCallback:     " + "原因:" + s);             }         });          rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {             @Override             public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {                 System.out.println("ReturnCallback:     " + "消息:" + message);                 System.out.println("ReturnCallback:     " + "回应码:" + replyCode);                 System.out.println("ReturnCallback:     " + "回应信息:" + replyText);                 System.out.println("ReturnCallback:     " + "交换机:" + exchange);                 System.out.println("ReturnCallback:     " + "路由键:" + routingKey);             }         });         return rabbitTemplate;     } } 到这里,生产者推送消息的消息确认调用回调函数已经完毕。 可以看到上面写了两个回调函数,一个叫 ConfirmCallback ,一个叫 RetrunCallback; 那么以上这两种回调函数都是在什么情况会触发呢? 先从总体的情况分析,推送消息存在四种情况: ①消息推送到server,但是在server里找不到交换机 ②消息推送到server,找到交换机了,但是没找到队列 ③消息推送到sever,交换机和队列啥都没找到 ④消息推送成功  测试①消息推送到server,但是在server里找不到交换机这种情况: 写个测试接口,把消息推送到名为‘non-existent-exchange’的交换机上(这个交换机是没有创建没有配置的)  @GetMapping("/TestMessageAck") public String TestMessageAck() {     String messageId = String.valueOf(UUID.randomUUID());     String messageData = "message: non-existent-exchange test message ";     String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));     Map<String, Object> map = new HashMap<>();     map.put("messageId", messageId);     map.put("messageData", messageData);     map.put("createTime", createTime);     rabbitTemplate.convertAndSend("non-existent-exchange", "TestDirectRouting", map);     return "ok"; }  调用接口: 返回消息 报错未找到该交换机,ConfirmCallback发挥作用。  测试②消息推送到server,找到交换机了,但是没找到队列 这种情况 这种情况就是需要新增一个交换机,但是不给这个交换机绑定队列,我来简单地在DirectRabitConfig里面新增一个直连交换机,名叫‘lonelyDirectExchange’,但没给它做任何绑定配置操作:  @Bean DirectExchange lonelyDirectExchange() {     return new DirectExchange("lonelyDirectExchange"); } 然后写个测试接口,把消息推送到名为‘lonelyDirectExchange’的交换机上(这个交换机是没有任何队列配置的):  @GetMapping("/TestMessageAckLoneLy") public String TestMessageAckLoneLy() {     String messageId = String.valueOf(UUID.randomUUID());     String messageData = "message: lonelyDirectExchange test message ";     String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));     Map<String, Object> map = new HashMap<>();     map.put("messageId", messageId);     map.put("messageData", messageData);     map.put("createTime", createTime);     rabbitTemplate.convertAndSend("lonelyDirectExchange", "TestDirectRouting", map);     return "ok"; } 调用接口: 发挥了作用。这种情况下,消息是推送成功到服务器了的,所以ConfirmCallback对消息确认情况是true; 而在RetrunCallback回调函数的打印参数里面可以看到,消息是推送到了交换机成功了,但是在路由分发给队列的时候,找不到队列,所以报了错误 NO_ROUTE 。 结论:②这种情况触发的是 ConfirmCallback和RetrunCallback两个回调函数。 测试③消息推送到sever,交换机和队列啥都没找到 这种情况其实一看就觉得跟①很像,没错 ,③和①情况回调是一致的,所以不做结果说明了。 结论: ③这种情况触发的是 ConfirmCallback 回调函数。 测试④消息推送成功 结论: ④这种情况触发的是 ConfirmCallback 回调函数。 以上是生产者推送消息的消息确认 回调函数的使用介绍(可以在回调函数根据需求做对应的扩展或者业务数据处理)。 接下来我们测试消费者接到消息的消息确认机制 和生产者的消息确认机制不同,因为消息接收本来就是在监听消息,符合条件的消息就会消费下来。 所以,消息接收的确认机制主要存在三种模式:  ①自动确认, 这也是默认的消息确认情况。 AcknowledgeMode.NONE RabbitMQ成功将消息发出(即将消息成功写入TCP Socket)中立即认为本次投递已经被正确处理,不管消费者端是否成功处理本次投递。 所以这种情况如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。 一般这种情况我们都是使用try catch捕捉异常后,打印日志用于追踪数据,这样找出对应数据再做后续处理。  ② 不确认, 这个不做介绍 ③ 手动确认 , 这个比较关键,也是我们配置接收消息确认机制时,多数选择的模式。 消费者收到消息后,手动调用basic.ack/basic.nack/basic.reject后,RabbitMQ收到这些消息后,才认为本次投递成功。 basic.ack用于肯定确认 basic.nack用于否定确认(注意:这是AMQP 0-9-1的RabbitMQ扩展) basic.reject用于否定确认,但与basic.nack相比有一个限制:一次只能拒绝单条消息 消费者端以上的3个方法都表示消息已经被正确投递,但是basic.ack表示消息已经被正确处理,但是basic.nack,basic.reject表示没有被正确处理,但是RabbitMQ中仍然需要删除这条消息。  之前介绍用了很多个交换,现在我们就先给直连型交换机添加消息接收确认机制, 新建MessageListenerConfig.java上添加代码相关的配置代码(可以看到注释掉的代码,就是给扇型交换机配置消息确认,只用在这个里面继续添加对应的队列和对应的接收类即可,当然对应的接收类也需要跟后面介绍一样加上对应方法):  import com.cc.mq.client.DirectReceiver; import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.core.MessageListener; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  /**  * 方法名:          MessageListenerConfig  * 方法功能描述:  *  * @param:  * @return:  * @Author:   * @Create Date:   2019/12/23  */ @Configuration public class MessageListenerConfig {      @Autowired     private CachingConnectionFactory connectionFactory;     @Autowired     private DirectReceiver directReceiver;//Direct消息接收处理类     @Autowired     DirectRabbitConfig directRabbitConfig;     @Bean     public SimpleMessageListenerContainer simpleMessageListenerContainer() {         SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);         container.setConcurrentConsumers(1);         container.setMaxConcurrentConsumers(1);         container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认消息         container.setQueues(directRabbitConfig.TestDirectQueue());         container.setMessageListener(directReceiver);         return container;     }  } 这里的DirectReceiver 是我们之前创建的接受类。 然后在直连型交换机的消息接收处理类上需要添加相关的消息手动确认代码DirectReceiver.java: @Component @RabbitListener(queues = "TestDirectQueue")//监听的队列名称 TestDirectQueue public class DirectReceiver implements ChannelAwareMessageListener {      @RabbitHandler     public void process(Map testMessage) {         System.out.println("DirectReceiver消费者收到消息  : " + testMessage.toString());     }      @Override     public void onMessage(Message message, Channel channel) throws Exception {         long deliveryTag = message.getMessageProperties().getDeliveryTag();         try {             //因为传递消息的时候用的map传递,所以将Map从Message内取出需要做些处理             String msg = message.toString();             String[] msgArray = msg.split("'");//可以点进Message里面看源码,单引号直接的数据就是我们的map消息数据             Map<String, String> msgMap = this.mapStringToMap(msgArray[1].trim());             String messageId=msgMap.get("messageId");             String messageData=msgMap.get("messageData");             String createTime=msgMap.get("createTime");             System.out.println("messageId:"+messageId+"  messageData:"+messageData+"  createTime:"+createTime);             channel.basicAck(deliveryTag, true); //       channel.basicReject(deliveryTag, true);//为true会重新放回队列         } catch (Exception e) {             channel.basicReject(deliveryTag, false);             e.printStackTrace();         }      }          //这种解析方式是有问题的,在生产的时候不要这样做     private Map<String, String> mapStringToMap(String str) {         str = str.substring(1, str.length() - 1);         String[] strs = str.split(",");         Map<String, String> map = new HashMap<String, String>();         for (String string : strs) {             String key = string.split("=")[0].trim();             String value = string.split("=")[1];             map.put(key, value);         }         return map;     }  } 然后现在将rabbitmq-provider 、rabbitmq-consumer两个项目跑起来,调用下/sendDirectMessage接口往直连型交换机推送一条消息,看看监听到的消息结果: 手动的确认模式的投递效率略低于自动,但是可以弥补自动确认模式的不足,更加准确地去记录消息消费情况。 那么如果需要有些消息接收类设置自动确认,有些消息接收类设置手动确认的话,那只需要将需要设置手动确认的相关队列加到之前的MessageListenerConfig的SimpleMessageListenerContainer里面就行。 ———————————————— 原文链接:https://blog.csdn.net/qq_24848931/article/details/104468398 
  • [互动交流] CodeArts IDE for java怎么默认maven配置
    请问默认设置maven的路径,现在每次都要去配置,有点麻烦。并且新的版本如何去运行springboot项目
  • [技术干货] 探索Java中最常用的框架:Spring、Spring MVC、Spring Boot、MyBatis和Netty -转载
     Spring MVC框架  Spring MVC是Spring框架的一部分,用于构建Web应用程序。它提供了一种MVC(Model-View-Controller)的架构,使得Web应用的开发更有组织和易于维护。  模型(Model):模型代表应用程序的数据和业务逻辑。  视图(View):视图负责显示模型的数据。  控制器(Controller):控制器接受来自用户的请求,处理它们并选择适当的视图来响应请求。   Spring MVC的优点:  1.松耦合:Spring MVC使用了松耦合的设计,允许将控制器、模型和视图分开开发,从而提高了代码的可维护性。 2.高度可扩展:Spring MVC支持自定义视图解析器、拦截器等,使得定制化开发变得容易。 3.强大的数据绑定:Spring MVC可以将请求参数绑定到控制器方法的参数,大大减少了开发工作。 4.REST支持:Spring MVC支持构建RESTful Web服务,通过注解和配置来定义REST端点。     Spring Boot框架 Spring Boot是Spring的扩展,旨在简化Spring应用程序的创建和开发。它通过提供默认配置、自动配置和快速开发功能,大大减少了开发者的工作量。  Spring Boot的特点:   1.自动配置:Spring Boot根据项目中使用的库和类自动配置应用程序。如果你需要自定义配置,只需覆盖默认配置即可。 2.嵌入式Web服务器:Spring Boot集成了嵌入式的Web服务器,如Tomcat、Jetty等,无需额外配置。 3.生产就绪特性:Spring Boot内置了用于监控、度量、健康检查的功能,便于生产环境的部署。 4.开箱即用:Spring Boot提供了一系列的“Starter”依赖,可以快速构建特定类型的应用程序,如Web应用、数据访问应用、消息队列等。  MyBatis框架  MyBatis是一种优秀的持久层框架,它简化了数据库访问操作。与其他ORM框架不同,MyBatis使用XML或注解配置SQL语句,提供了更灵活的SQL编写方式。  MyBatis的特点:  1.SQL分离:MyBatis将SQL语句与Java代码分离,提供了更好的可读性和维护性。 2.参数映射:MyBatis可以将Java对象和数据库表之间的字段映射自动处理。 3.高性能:MyBatis执行SQL语句的性能很高,支持懒加载、缓存等特性。 4.灵活性:MyBatis支持自定义类型处理器、插件等扩展功能。  Netty框架 Netty是一个基于事件驱动的网络应用程序框架,用于快速开发高性能的网络服务器和客户端。它支持各种传输协议,如TCP、UDP、HTTP等。  Netty的特点:  1.高性能:Netty的事件驱动架构使得它在高负载情况下表现出色。 2.可扩展性:Netty的组件是可扩展的,可以轻松添加自定义的处理器。 3.多协议支持:Netty支持多种协议,使其适用于各种应用,包括Web服务、实时通信等。 4.成熟的生态系统:Netty有一个活跃的社区,提供了丰富的扩展和文档资源。  适用场景  1.使用Spring来构建企业级应用,特别是那些需要控制反转和面向切面编程的应用。 2.开发Web应用程序时,可以使用Spring MVC来处理Web请求。 3.使用Spring Boot来快速创建独立的Spring应用程序,减少配置工作。 4.需要数据库持久化操作时,可以选择MyBatis作为ORM框架。 5.需要构建高性能的网络应用程序时,可以使用Netty。   结语  Spring、Spring MVC、Spring Boot、MyBatis和Netty是Java开发中最常用的框架之一,每个框架都有自己的优点和适用场景。选择合适的框架取决于你的项目需求和技术栈。熟练掌握这些框架将有助于提高你的Java开发技能,加速项目开发,并提高应用程序的性能和质量。希望本文对你更好地理解这些框架提供了帮助。  关于探索Java中最常用的框架:Spring、Spring MVC、Spring Boot、MyBatis和Netty,懒大王就先分享到这里了,如果你认为这篇文章对你有帮助,请给懒大王点个赞点个关注吧,如果发现什么问题,欢迎评论区留言!!💕💕         ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/weixin_58070962/article/details/135497866 
  • [专题汇总] 新一年的汇总来啦,2024年1月技术干货合集
    本次给大家带来的是CodeArts最新的干货合集25篇,涵盖了,python,java,springboot,kafka,vue,pinia等多个干货,快来看吧。 1.idea一直indexing无法操作的问题解决【转】 https://bbs.huaweicloud.com/forum/thread-0294142071416308014-1-1.html  2.VSCode打开Json文件格式化的简单步骤【转】 https://bbs.huaweicloud.com/forum/thread-0243142071705276018-1-1.html  3.PHP8.3更新内容新特性及支持版本探究【转】 https://bbs.huaweicloud.com/forum/thread-0271142072065293019-1-1.html  4.springboot如何配置多kafka【转】 https://bbs.huaweicloud.com/forum/thread-0243142072165273019-1-1.html  5.java中@NotBlank限制属性不能为空【转】 https://bbs.huaweicloud.com/forum/thread-0297142073070924013-1-1.html  6.正则去除中括号(符号)及里面包含的内容(最新推荐)【转】 https://bbs.huaweicloud.com/forum/thread-0243142071315437017-1-1.html  7.python Dtale库交互式数据探索分析和可视化界面【转】 https://bbs.huaweicloud.com/forum/thread-0255142071244033020-1-1.html  8.python dataprep库简化加速数据科学操作【转】 https://bbs.huaweicloud.com/forum/thread-0294142071061284012-1-1.html  9.python kornia计算机视觉库实现图像变化【转】 https://bbs.huaweicloud.com/forum/thread-0255142070801280018-1-1.html  10.基于Python实现n-gram文本生成的示例代码【转】 https://bbs.huaweicloud.com/forum/thread-0271142070636778018-1-1.html  11.python第三方模块xmltodict库优雅处理xml格式为json【转】 https://bbs.huaweicloud.com/forum/thread-0259142068233301011-1-1.html  12.python Stanza处理NLP任务使用详解(多语言处理工具)【转】 https://bbs.huaweicloud.com/forum/thread-0271142068138833017-1-1.html  13.Python Arrow处理时间数据使用详解(标准库之外另一种选择)【转】 https://bbs.huaweicloud.com/forum/thread-0297142067851262012-1-1.html  14.python list 切片倒着取的实现示例【转】 https://bbs.huaweicloud.com/forum/thread-0243142067708249015-1-1.html  15.Python Fire中两种命令行参数灵活设置方式详解【转】 https://bbs.huaweicloud.com/forum/thread-0271142067433292016-1-1.html  16.pinia初学习 https://bbs.huaweicloud.com/forum/thread-0202141875876618017-1-1.html  17.为什么需要使用$nextTick?【转】 https://bbs.huaweicloud.com/forum/thread-0231141875789608020-1-1.html  18.Vue自动生成组件名【转】 https://bbs.huaweicloud.com/forum/thread-0296141875621746009-1-1.html  19.工程化第一步这个package.json要真的搞明白才行【转】 https://bbs.huaweicloud.com/forum/thread-0267141875153948017-1-1.html  20.关于vite的跨域问题【转】 https://bbs.huaweicloud.com/forum/thread-0231141874969502019-1-1.html  21.前端无感知刷新token & 超时自动退出【转】 https://bbs.huaweicloud.com/forum/thread-02118141874791175015-1-1.html  22.按钮防连点终极解决方案 【转】 https://bbs.huaweicloud.com/forum/thread-0224141874429208008-1-1.html  23.5分钟搞定vue3函数式弹窗【转】 https://bbs.huaweicloud.com/forum/thread-0202141874275399016-1-1.html  24.有了Composition API后,有些场景或许你不需要pinia了【转】 https://bbs.huaweicloud.com/forum/thread-02118141874125748014-1-1.html  25.Vue中的$attrs你真的会用吗?【转】 https://bbs.huaweicloud.com/forum/thread-0296141873968992008-1-1.html 
  • [技术干货] java中@NotBlank限制属性不能为空【转】
    在实体类的对应属性上添加 @NotBlank 注解,可以实现对空置的限制。除了 @NotBlank 外,还有 @NotNull 和 @NotEmpty ,它们的区别如下所示: 1.String name = null; @NotNull: false @NotEmpty:false  @NotBlank:false   2.String name = ""; @NotNull:true @NotEmpty: false @NotBlank: false  3.String name = " "; @NotNull: true @NotEmpty: true @NotBlank: false  4.String name = "Great answer!"; @NotNull: true @NotEmpty:true @NotBlank:true 需要注意的是 @NotNull 一般作用在 Integer 类型上,其还可以配合 @size、@Max、@Min 对字段的数值大小进行控制。而 @NotEmpty 表示不能为空,且长度必须大于 0 ,一般用在集合类或数组上。 @NotBlank 只能作用在 String 类型上,并且调用 trim() 后,长度必须大于 0 。同时,使用 @NotBlank 等注解时,一定要和 @valid 一起使用,不然@NotBlank不起作用,如:、    @PostMapping("/save")     @ApiOperation(value = "新增", notes = "传入supplementaryMaterial")     public ActionResult save(@Valid @RequestBody SupplementaryMaterial supplementaryMaterial) {         return new ActionResult().status(supplementaryMaterialService.save(supplementaryMaterial));     } @NotEmpty、@NotBlank、@NotNull三种注解的区别1. @NotEmpty@NotEmpty 注解用于验证一个字符串、集合或数组是否为空。它检查目标对象是否为 null,并且长度是否为零。换句话说,被 @NotEmpty 注解标注的字段必须至少包含一个非空字符、元素或项。 public class User {     @NotEmpty     private String username;      // Getter and Setter } 在上面的例子中,username 字段被标注为 @NotEmpty,这意味着在验证时,username 必须既不为 null,也不为空字符串。2. @NotBlank@NotBlank 注解也用于验证字符串是否为空,但它比 @NotEmpty 更严格。它会去除字符串两端的空格,并验证处理后的字符串是否为空。因此,@NotBlank 只适用于字符串类型的字段。 public class Product {     @NotBlank     private String productName;      // Getter and Setter } 在上述示例中,productName 字段被标注为 @NotBlank,这要求字段既不为 null,也不为空字符串,且去除两端空格后也不能为空。3. @NotNull相对于前两者,@NotNull 注解更加简单,它只检查目标字段是否为 null。这意味着它适用于所有类型的字段,包括字符串、对象、基本数据类型等。 public class Order {     @NotNull     private Long orderId;      // Getter and Setter } 在上述代码中,orderId 字段被标注为 @NotNull,这表示在处理时,orderId 不能为 null。区别总结@NotEmpty 验证字段不为 null,且长度不为零。适用于字符串、集合和数组。@NotBlank 验证字段不为 null,长度不为零,且去除两端空格后也不为空。仅适用于字符串。@NotNull 验证字段不为 null。适用于所有类型的字段。
  • [技术干货] idea一直indexing无法操作的问题解决【转】
    今天主要分享一下在使用idea 2020.3版本开发maven项目的时候,一直出现有效件index, 有时候是scaning indexing, 有时候是update indexing, indexing的时候,idea基本上就没办法操作了,连跳入到类或方法里都跳不了。不厌其烦。于是开始在网上找解决方法: 得到的90%及以上的结果,都是让我点击: File-invalid cache/Restart, ,简直了,看到这我就想骂人,一看就是天下文章一大抄,应该根本就没有验证过,我试了一点也不管用。这样的作者 和文章真的是误导人。于是我就开始自己找方法,在stackoverflow上,有一个解决方案,说是在设置里,把下面红框千面的勾选去掉,试了,也不管用后来仔细观察了一下,每次它indexing都是在index jdk或者是 maven仓库,然后我就抱着试试看的态度,在设置里直接搜索index,果然又发现。把对应的jdk和maven改为不下载,使用本地索引。完美解决。补充: 其实做了如上的设置后,indexing的情况还是时有发生,只不过频率降低了一些,但是其实还是没有从根本上解决问题。后来无奈体验了各个版本的idea,发现在升级到了2021.3.2以后的版本,该问题再也没有出现过。所以大家如果一直被这个问题困扰,建议升级一下。
  • [技术干货] 顶级Javaer必知的常用类库,你都用过哪些【转】
    1.日志库2.JSON解析库3.单元测试库4.通用库5.HTTP 库6.XML 解析库7.Excel 阅读库8.字节码库9.数据库连接池库10.消息库11.PDF 库12.日期和时间库13.集合库14.电子邮件 API15.HTML 解析库16.密码库17.嵌入式 SQL 数据库库18.JDBC 故障排除库19.序列化库20.网络库优秀且经验丰富的 Java 开发人员的特点之一是对 API 的广泛了解,包括 JDK 和第三方库。如何使用现有的 API 进行开发,而不是为常见的东西编写新的代码。是提升开发效率必选之路。一般来说,我会为日常项目提供有用的库,包括 Log4j 等日志库、Jackson 等 JSON 解析库以及 JUnit 和 Mockito 等单元测试 API。如果您需要在项目中使用它们,则可以在项目的类路径中包含这些库的 JAR 以开始使用它们,也可以使用Maven进行依赖管理。对 Java 程序员有用的开源库下面是收集的一些有用的第三方库,Java 开发人员可以在他们的应用程序中使用它们来完成很多有用的任务。为了使用这些库,Java 开发人员应该熟悉这一点,这就是本文的重点。如果您有一个想法,那么您可以研究该库并使用它。1. 日志库日志库非常常见,因为您在每个项目中都需要它们。它们对于服务器端应用程序来说是最重要的,因为日志只放置在您可以看到应用程序正在发生什么的地方。尽管 JDK 附带了自己的日志库,但仍有更好的替代方案可用,例如 Log4j、SLF4j 和 LogBack。Java 开发人员应该熟悉日志库的优缺点,并知道为什么使用 SLF4j 比普通的 Log4j 更好。2. JSON解析库在当今的 Web 服务和物联网世界中,JSON 已成为将信息从客户端传输到服务器的首选协议。它们已取代 XML,成为以独立于平台的方式传输信息的首选方式。不幸的是,JDK 没有JSON 库。但是,有许多优秀的第三方库可以让您解析和创建 JSON 消息,例如 Jackson 和 Gson。Java Web 开发人员应该至少熟悉这些库中的一个。3. 单元测试库单元测试是将普通开发人员与优秀开发人员区分开来的最重要的事情。程序员经常得到不编写单元测试的借口,但避免单元测试的最常见借口是缺乏流行单元测试库的经验和知识,包括 JUnit、Mockito 和 PowerMock。图片4. 通用库Java 开发人员可以使用一些优秀的通用第三方库,例如 Apache Commons 和 Google Guava。我总是在我的项目中包含这些库,因为它们简化了很多任务。重新发明轮子是没有意义的。我们应该更喜欢使用久经考验的库,而不是时不时地编写我们自己的例程。图片Java 开发人员最好熟悉 Google Guava 和 Apache Commons 库。5. HTTP 库我不喜欢 JDK 的一件事是它们缺乏对 HTTP 的支持。虽然您可以使用包中的类建立 HTTP 连接 http://java.net,但使用开源第三方库(如 Apache HttpClient 和 HttpCore)并不容易或无缝。图片尽管 JDK 9 带来了对 HTTP 2.0 的支持以及对 HTTP 的更好支持,但我强烈建议所有 Java 开发人员熟悉流行的 HTTP 客户端库,包括 HttpClient 和 HttpCore。6. XML 解析库有许多 XML 解析库,包括 Xerces、JAXB、JAXP、Dom4j 和 Xstream。Xerces2 是 Apache Xerces 系列中的下一代高性能、完全兼容的 XML 解析器。这个新版本的 Xerces 引入了 Xerces Native Interface (XNI),这是一个用于构建解析器组件和配置的完整框架,它非常模块化且易于编程。图片Apache Xerces2 解析器是 XNI 的参考实现,但其他解析器组件、配置和解析器可以使用 Xerces Native Interface 编写。Dom4j 是另一个用于 Java 应用程序的灵活 XML 框架。7. Excel 阅读库信不信由你——所有现实世界的应用程序都必须以某种形式与 Microsoft Office 交互。许多应用程序需要提供在 Excel 中导出数据的功能,如果您必须从 Java 应用程序中执行相同操作,则需要 Apache POI API。这是一个非常丰富的库,允许您 从 Java 程序读取和写入 XLS 文件。您可以查看该链接以获取在核心 Java 应用程序中读取 Excel 文件的工作示例。8. 字节码库如果您正在编写生成代码或与字节码交互的框架或库,那么您需要一个字节码库。它们允许您读取和修改应用程序生成的字节码。Java 世界中一些流行的字节码库是 javassist 和 Cglib Nodep。图片Javassist(JAVA 编程助手)使 Java 字节码操作变得非常简单。它是一个用于在 Java 中编辑字节码的类库。ASM 是另一个有用的字节码编辑库。9. 数据库连接池库如果您从 Java 应用程序与数据库进行交互,但不使用数据库连接池库,那么,您会丢失一些东西。推荐程序员摸鱼地址:https://www.yoodb.com/slack-off/home.html由于在运行时创建数据库连接需要时间并且使请求处理速度变慢,因此始终建议使用数据库连接库。一些流行的是 Commons Pool 和 DBCP。在 Web 应用程序中,它的 Web 服务器通常提供这些功能,但在核心 Java 应用程序中,您需要将这些连接池库包含到您的类路径中才能使用数据库连接池。10. 消息库与日志记录和数据库连接类似,消息传递也是许多实际 Java 应用程序的共同特征。Java 提供 JMS 或 Java 消息传递服务,它不是 JDK 的一部分。对于此组件,您需要包含一个单独的 jms.jar图片同样,如果您使用第三方消息传递协议,例如 Tibco RV,那么您需要 tibrv.jar 在应用程序类路径中使用第三方 JAR 。11. PDF 库与 Microsoft Excel 类似,PDF 库是另一种普遍存在的格式。如果您需要在应用程序中支持 PDF 功能,例如 在 PDF 文件中导出数据,您可以使用 iText 和 Apache FOP 库。两者都提供有用的 PDF 相关功能,但 iText 更丰富更好。图片12. 日期和时间库在 Java 8 之前,JDK 的数据和时间库有很多缺陷,因为它们不是线程安全的、不可变的和容易出错的。许多 Java 开发人员依靠 JodaTime 来实现他们的日期和时间要求。从 JDK 8 开始,没有理由使用 Joda,因为您可以在 JDK 8 的新日期和时间 API中获得所有这些功能,但是如果您使用的是较旧的 Java 版本,那么 JodaTime 是一个值得学习的库。图片13. 集合库尽管 JDK 拥有丰富的集合库,但也有一些第三方库提供了更多选项,例如 Apache Commons 集合、Goldman Sachs 集合、Google 集合和 Trove。Trove 库特别有用,因为它为 Java 提供了高速的常规和原始集合。图片FastUtil 是另一个类似的 API。它通过提供特定类型的映射、集合、列表和优先级队列来扩展 Java 集合框架,这些映射、集合、列表和优先级队列具有较小的内存占用、快速访问和插入;它还提供大(64 位)数组、集合和列表,以及用于二进制和文本文件的快速、实用的 I/O 类。14. 电子邮件 APIjavax.mail 和 Apache Commons Email 都提供了用于从 Java 发送电子邮件的 API 。它建立在 JavaMail API 之上,旨在简化它。图片15. HTML 解析库与JSON和XML类似,HMTL 是我们许多人必须处理的另一种常见格式。值得庆幸的是,我们有 JSoup,它极大地简化了在 Java 应用程序中使用 HTML。您可以使用JSoup不仅解析 HTML,还可以创建 HTML 文档图片它提供了一个非常方便的 API 用于提取和操作数据,使用最好的DOM、CSS 和类似 jquery 的方法。JSoup 实现了 WHATWG HTML5 规范并将HTML解析为与现代浏览器相同的 DOM。16.密码库Apache Commons Codec 包包含各种格式的简单编码器和解码器,例如Base64和 Hexadecimal。除了这些广泛使用的编码器和解码器之外,编解码器包还维护了一组语音编码实用程序。图片17. 嵌入式 SQL 数据库库我真的很喜欢像 H2 这样的内存数据库,你可以将它嵌入到你的 Java 应用程序中。它们非常适合测试您的 SQL 脚本和运行需要数据库的单元测试。但是,H2 不是唯一的 DB,您还可以选择 Apache Derby 和 HSQL。图片18. JDBC 故障排除库有一些很好的 JDBC 扩展库可以让调试更容易,比如 P6spy。这是一个库,可以无缝拦截和记录数据库数据,而无需更改应用程序的代码。您可以使用它们来记录 SQL 查询及其时间。例如,如果您在代码中使用PreparedStatment和CallableStatement,这些库可以记录带有参数的准确调用以及执行所需的时间。图片19. 序列化库Google 协议缓冲区是一种以高效且可扩展的格式对结构化数据进行编码的方法。它是Java 序列化的更丰富和更好的替代方案。我强烈建议有经验的 Java 开发人员学习 Google Protobuf。图片20. 网络库一些有用的网络库是 Netty 和 Apache MINA。如果您正在编写需要执行低级网络任务的应用程序,请考虑使用这些库。图片以上就是今天小编分享给大家的一些工作中常用的库,了解并熟练的运用他们,不仅可以大大提高你的开发效率,也可以学习优秀代码的设计,提高自己的编码能力。
  • [技术干货] 高并发场景下的 HttpClient 优化方案,QPS大大提升!【转】
    1 背景我们有个业务,会调用其他部门提供的一个基于http的服务,日调用量在千万级别。使用了httpclient来完成业务。之前因为qps上不去,就看了一下业务代码,并做了一些优化,记录在这里。先对比前后:优化之前,平均执行时间是250ms;优化之后,平均执行时间是80ms,降低了三分之二的消耗,容器不再动不动就报警线程耗尽了,清爽~2 分析项目的原实现比较粗略,就是每次请求时初始化一个httpclient,生成一个httpPost对象,执行,然后从返回结果取出entity,保存成一个字符串,最后显式关闭response和client。我们一点点分析和优化:2.1 httpclient反复创建开销httpclient是一个线程安全的类,没有必要由每个线程在每次使用时创建,全局保留一个即可。2.2 反复创建tcp连接的开销tcp的三次握手与四次挥手两大裹脚布过程,对于高频次的请求来说,消耗实在太大。试想如果每次请求我们需要花费5ms用于协商过程,那么对于qps为100的单系统,1秒钟我们就要花500ms用于握手和挥手。又不是高级领导,我们程序员就不要搞这么大做派了,改成keep alive方式以实现连接复用!2.3 重复缓存entity的开销原本的逻辑里,使用了如下代码:HttpEntityentity=httpResponse.getEntity();String response = EntityUtils.toString(entity);这里我们相当于额外复制了一份content到一个字符串里,而原本的httpResponse仍然保留了一份content,需要被consume掉,在高并发且content非常大的情况下,会消耗大量内存。并且,我们需要显式的关闭连接,ugly。3 实现按上面的分析,我们主要要做三件事:一是单例的client,二是缓存的保活连接,三是更好的处理返回结果。一就不说了,来说说二。提到连接缓存,很容易联想到数据库连接池。httpclient4提供了一个PoolingHttpClientConnectionManager 作为连接池。接下来我们通过以下步骤来优化:3.1 定义一个keep alive strategy关于keep-alive,本文不展开说明,只提一点,是否使用keep-alive要根据业务情况来定,它并不是灵丹妙药。还有一点,keep-alive和time_wait/close_wait之间也有不少故事。在本业务场景里,我们相当于有少数固定客户端,长时间极高频次的访问服务器,启用keep-alive非常合适再多提一嘴,http的keep-alive 和tcp的KEEPALIVE不是一个东西。回到正文,定义一个strategy如下:ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() { @Override public long getKeepAliveDuration(HttpResponse response, HttpContext context) { HeaderElementIterator it = new BasicHeaderElementIterator (response.headerIterator(HTTP.CONN_KEEP_ALIVE)); while (it.hasNext()) { HeaderElement he = it.nextElement(); String param = he.getName(); String value = he.getValue(); if (value != null && param.equalsIgnoreCase ("timeout")) { return Long.parseLong(value) * 1000; } } return 60 * 1000;//如果没有约定,则默认定义时长为60s }};3.2 配置一个PoolingHttpClientConnectionManagerPoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();connectionManager.setMaxTotal(500);connectionManager.setDefaultMaxPerRoute(50);//例如默认每路由最高50并发,具体依据业务来定也可以针对每个路由设置并发数。3.3 生成httpclienthttpClient = HttpClients.custom() .setConnectionManager(connectionManager) .setKeepAliveStrategy(kaStrategy) .setDefaultRequestConfig(RequestConfig.custom().setStaleConnectionCheckEnabled(true).build()) .build();注意:使用setStaleConnectionCheckEnabled方法来逐出已被关闭的链接不被推荐。更好的方式是手动启用一个线程,定时运行closeExpiredConnections 和closeIdleConnections方法,如下所示。public static class IdleConnectionMonitorThread extends Thread { private final HttpClientConnectionManager connMgr; private volatile boolean shutdown; public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) { super(); this.connMgr = connMgr; } @Override public void run() { try { while (!shutdown) { synchronized (this) { wait(5000); // Close expired connections connMgr.closeExpiredConnections(); // Optionally, close connections // that have been idle longer than 30 sec connMgr.closeIdleConnections(30, TimeUnit.SECONDS); } } } catch (InterruptedException ex) { // terminate } } public void shutdown() { shutdown = true; synchronized (this) { notifyAll(); } } }3.4 使用httpclient执行method时降低开销这里要注意的是,不要关闭connection。推荐程序员摸鱼地址:https://www.yoodb.com/slack-off/home.html一种可行的获取内容的方式类似于,把entity里的东西复制一份:res = EntityUtils.toString(response.getEntity(),"UTF-8");EntityUtils.consume(response1.getEntity());但是,更推荐的方式是定义一个ResponseHandler,方便你我他,不再自己catch异常和关闭流。在此我们可以看一下相关的源码:public <T> T execute(final HttpHost target, final HttpRequest request, final ResponseHandler<? extends T> responseHandler, final HttpContext context) throws IOException, ClientProtocolException { Args.notNull(responseHandler, "Response handler"); final HttpResponse response = execute(target, request, context); final T result; try { result = responseHandler.handleResponse(response); } catch (final Exception t) { final HttpEntity entity = response.getEntity(); try { EntityUtils.consume(entity); } catch (final Exception t2) { // Log this exception. The original exception is more // important and will be thrown to the caller. this.log.warn("Error consuming content after an exception.", t2); } if (t instanceof RuntimeException) { throw (RuntimeException) t; } if (t instanceof IOException) { throw (IOException) t; } throw new UndeclaredThrowableException(t); } // Handling the response was successful. Ensure that the content has // been fully consumed. final HttpEntity entity = response.getEntity(); EntityUtils.consume(entity);//看这里看这里 return result;}可以看到,如果我们使用resultHandler执行execute方法,会最终自动调用consume方法,而这个consume方法如下所示:public static void consume(final HttpEntity entity) throws IOException { if (entity == null) { return; } if (entity.isStreaming()) { final InputStream instream = entity.getContent(); if (instream != null) { instream.close(); } }}可以看到最终它关闭了输入流。4 其他通过以上步骤,基本就完成了一个支持高并发的httpclient的写法,下面是一些额外的配置和提醒:4.1 httpclient的一些超时配置CONNECTION_TIMEOUT是连接超时时间,SO_TIMEOUT是socket超时时间,这两者是不同的。连接超时时间是发起请求前的等待时间;socket超时时间是等待数据的超时时间。HttpParams params = new BasicHttpParams();//设置连接超时时间Integer CONNECTION_TIMEOUT = 2 * 1000; //设置请求超时2秒钟 根据业务调整Integer SO_TIMEOUT = 2 * 1000; //设置等待数据超时时间2秒钟 根据业务调整 //定义了当从ClientConnectionManager中检索ManagedClientConnection实例时使用的毫秒级的超时时间//这个参数期望得到一个java.lang.Long类型的值。如果这个参数没有被设置,默认等于CONNECTION_TIMEOUT,因此一定要设置。Long CONN_MANAGER_TIMEOUT = 500L; //在httpclient4.2.3中我记得它被改成了一个对象导致直接用long会报错,后来又改回来了 params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, CONNECTION_TIMEOUT);params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SO_TIMEOUT);params.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT, CONN_MANAGER_TIMEOUT);//在提交请求之前 测试连接是否可用params.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, true); //另外设置http client的重试次数,默认是3次;当前是禁用掉(如果项目量不到,这个默认即可)httpClient.setHttpRequestRetryHandler(newDefaultHttpRequestRetryHandler(0,false));4.2 如果配置了nginx的话,nginx也要设置面向两端的keep-alive现在的业务里,没有nginx的情况反而比较稀少。nginx默认和client端打开长连接而和server端使用短链接。注意client端的keepalive_timeout和keepalive_requests参数,以及upstream端的keepalive参数设置,这三个参数的意义在此也不再赘述。以上就是我的全部设置。通过这些设置,成功地将原本每次请求250ms的耗时降低到了80左右,效果显著。JAR包如下:<!-- httpclient --><dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.6</version></dependency>代码如下://Basic认证private static final CredentialsProvider credsProvider = new BasicCredentialsProvider();//httpClientprivate static final CloseableHttpClient httpclient;//httpGet方法private static final HttpGet httpget;//private static final RequestConfig reqestConfig;//响应处理器private static final ResponseHandler<String> responseHandler;//jackson解析工具private static final ObjectMapper mapper = new ObjectMapper(); static { System.setProperty("http.maxConnections","50"); System.setProperty("http.keepAlive", "true"); //设置basic校验 credsProvider.setCredentials( new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM), new UsernamePasswordCredentials("", "")); //创建http客户端 httpclient = HttpClients.custom() .useSystemProperties() .setRetryHandler(new DefaultHttpRequestRetryHandler(3,true)) .setDefaultCredentialsProvider(credsProvider) .build(); //初始化httpGet httpget = new HttpGet(); //初始化HTTP请求配置 reqestConfig = RequestConfig.custom() .setContentCompressionEnabled(true) .setSocketTimeout(100) .setAuthenticationEnabled(true) .setConnectionRequestTimeout(100) .setConnectTimeout(100).build(); httpget.setConfig(reqestConfig); //初始化response解析器 responseHandler = new BasicResponseHandler();}/* * 功能:返回响应 * @author zhangdaquan * @param [url] * @return org.apache.http.client.methods.CloseableHttpResponse * @exception */public static String getResponse(String url) throws IOException { HttpGet get = new HttpGet(url); String response = httpclient.execute(get,responseHandler); return response;} /* * 功能:发送http请求,并用net.sf.json工具解析 * @author zhangdaquan * @param [url] * @return org.json.JSONObject * @exception */public static JSONObject getUrl(String url) throws Exception{ try { httpget.setURI(URI.create(url)); String response = httpclient.execute(httpget,responseHandler); JSONObject json = JSONObject.fromObject(response); return json; } catch (IOException e) { e.printStackTrace(); } return null;} /* * 功能:发送http请求,并用jackson工具解析 * @author zhangdaquan * @param [url] * @return com.fasterxml.jackson.databind.JsonNode * @exception */public static JsonNode getUrl2(String url){ try { httpget.setURI(URI.create(url)); String response = httpclient.execute(httpget,responseHandler); JsonNode node = mapper.readTree(response); return node; } catch (IOException e) { e.printStackTrace(); } return null;}/* * 功能:发送http请求,并用fastjson工具解析 * @author zhangdaquan * @param [url] * @return com.fasterxml.jackson.databind.JsonNode * @exception */public static com.alibaba.fastjson.JSONObject getUrl3(String url){ try { httpget.setURI(URI.create(url)); String response = httpclient.execute(httpget,responseHandler); com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(response); return jsonObject; } catch (IOException e) { e.printStackTrace(); } return null;}
  • [技术干货] MVC小知识
    (1) 什么是MVC?       MVC是一种设计思想,根据职责不同将程序中的组件分成以下3个部分。       V(View视图):负责与用户交互。将数据展现,或者是接收数据       M(Model模型):负责业务处理。业务模型,数据模型       C(Controller控制器):负责协同模型和视图工作。视图有请求调用模型处理,模型处理完毕调用视图响应。(2)为什么使用MVC?      MVC是一个非常优秀的设计思想,基于该思想架构程序,可以提高程序的结构灵活性,便于日后维护、扩展和升级。注意:下面内容助于理解:1)  一个模型可以被多个视图共享模型只负责输出数据,不关心数据的表现形式,同一仹数据,可以使用多个不同的视图展现给用户。模型只负责处理数据,不关心是谁在调用,可以使用多种不同的界面来调用模型。2)  方便测试    模型一般使用java 类来开发,在开发完成之后,可以立即测试。如果业务逻辑直接写在servlet里面,则需要部署在服务器上面才能测试,比较麻烦。3)  组件复用    控制器可以做成一个通用的模块。4)  代码好维护,利于分工协作。    按照 mvc 的思想,可以对程序迚行分层,一般划分成表示层(包括 v,c)、业务层(m中的业务逻辑部分)、持久层(m中的数据访问逻辑部分)。下一层的代码发生改变,只要接口不变,不会影响到上一层的代码。mvc的缺点1)  采用 mvc 以后,会增加代码量,相应的开发周期以及开发的成本会相应增加。2)  使用 mvc,需要良好的设计。如果设计不当,会增加开发的难度。在表示层Servlet中调用业务层代码的接口,当业务层发生改变时不影响Servelt ;在业务层Service中调用DAO的接口,DAO发生改变不影响Service和其上层 结论一般来说,如果一个程序需要良好的架构,需要良好的代码的可维护性及可扩展性,需要使用mvc思想来架构。反之,则不必使用。
  • [技术干货] AJAX开发小知识
    AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。AJAX 不是新的编程语言,而是一种使用现有标准的新方法。AJAX 是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下。AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。AJAX编程步骤?1) 获得 XmlHttpRequest对象2) 使用 XmlHttpRequest向服务器发请求。                  a.发送get请求:                  /* open(请求方式,请求地址,同步/异步)  * 请求方式: get/post   * 请求地址:如果是get请求,请求参数添加到地址之后。                 * 比如  check_user.do?username=zs                 * 同步/异步:true 表示异步。*/xhr.open('get','check_user.do',true);                  b. 发送 post 请求:xhr.open('post','check_username.do',true); //#必须添加一个消息头content-type     xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");3). 在服务器端,处理请求。4).在监听器当中,处理服务器返回的响应。xhr.onreadystatechange=function(){  //编写相应的处理代码  if(xhr.readyState == 4){    //只有 readyState 等亍 4,xhr 才完整地接收到了服务器返回的数据。    //获得文本数据    var txt = xhr.responseText;    //获得一个xml dom对象。    var xml = xhr.responseXML;    //dom操作、更新页面  }   };5.xhr.send(null)AJAX技术的优点?   1.页面无刷新   2.不打断用户的操作,用户的体验好   3.按需获取数据,浏览器和服务器之间数据的传输量减少   4.是一个标准技术,不需要下载任何的插件   5.可以利用客户端(浏览器)的计算能力
  • Swagger 介绍和使用
    使用 Swagger 你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面官网: swagger.io/ Knife4j 是为 Java MVC 框架集成 Swagger 生成 Api 文档的增强解决方案  使用方式 1. 导入坐标 <dependency>     <groupId>com.github.xiaoymin</groupId>     <artifactId>knife4j-spring-boot-starter</artifactId> </dependency> 2. 在配置类中加入 knife4j 相关配置     /**      * 通过knife4j生成接口文档      * @return      */     @Bean     public Docket docket() {         ApiInfo apiInfo = new ApiInfoBuilder()                 .title("苍穹外卖项目接口文档")                 .version("2.0")                 .description("苍穹外卖项目接口文档")                 .build();         Docket docket = new Docket(DocumentationType.SWAGGER_2)                 .apiInfo(apiInfo)                 .select()                 .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))                 .paths(PathSelectors.any())                 .build();         return docket;     } 3. 设置静态资源映射,否则接口文档页面无法访问     /**      * 设置静态资源映射      * @param registry      */     protected void addResourceHandlers(ResourceHandlerRegistry registry) {         registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");         registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");     } 常用注解  查看效果 启动项目后访问如下地址: localhost:8080/doc.html  成功 附完整配置类代码: package com.sky.config;import com.sky.interceptor.JwtTokenAdminInterceptor;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;import springfox.documentation.builders.ApiInfoBuilder;import springfox.documentation.builders.PathSelectors;import springfox.documentation.builders.RequestHandlerSelectors;import springfox.documentation.service.ApiInfo;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spring.web.plugins.Docket;/** * 配置类,注册web层相关组件 */@Configuration@Slf4jpublic class WebMvcConfiguration extends WebMvcConfigurationSupport { @Autowired private JwtTokenAdminInterceptor jwtTokenAdminInterceptor; /** * 注册自定义拦截器 * * @param registry */ protected void addInterceptors(InterceptorRegistry registry) { log.info("开始注册自定义拦截器..."); registry.addInterceptor(jwtTokenAdminInterceptor) .addPathPatterns("/admin/**") .excludePathPatterns("/admin/employee/login"); } /** * 通过knife4j生成接口文档 * @return */ @Bean public Docket docket() { log.info("准备生成接口文档..."); ApiInfo apiInfo = new ApiInfoBuilder() .title("苍穹外卖项目接口文档") .version("2.0") .description("苍穹外卖项目接口文档") .build(); Docket docket = new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo) .select() .apis(RequestHandlerSelectors.basePackage("com.sky.controller")) .paths(PathSelectors.any()) .build(); return docket; } /** * 设置静态资源映射 * @param registry */ protected void addResourceHandlers(ResourceHandlerRegistry registry) { log.info("开始设置静态资源映射..."); registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); }} 转载自https://learnku.com/articles/85427
  • [技术干货] Spring家族
    大家应该都知道,按照出现的顺序,spring全家桶大概包含了spring、springmvc、springboot以及springcloud,从开胃小菜spring到满汉全席springcloud,spring全家桶可谓Java工程师的必备大餐,那么,我们不妨先来看看,spring全家桶是如何从光杆司令spring发展到如今的庞大家族的。 想要精通Spring的你,不妨来翻一翻这份大神整理出来的367页PDF,我想这应该是对“Spring家族”最完美的诠释了。  Key1:攻克Spring5 ①Spring-手绘脑图 (基本概念+AOP+事务管理+IOC+MVC+Spring类等) ②Spring5高级编程 ③Spring源码解析 第一部分:核心实现(Spring整体架构和环境搭建+容器的基本实现+默认标签的解析+自定义标签的解析+bean的加载+容器的功能扩展+AOP) 第二部分:企业应用(数据库连接JDBC+整合MyBatis+事务+SpringMVC+远程服务+Spring消息) ④Spring源码笔记 (Spring概述+核心思想+手写实现IOC和AOP+SpringIOC应用+SpringIOC源码深度剖析+SpringAOP应用+SpringAOP源码深度剖析) ⑤Spring面试题(高级应用篇) 什么是Spring 框架?Spring 框架有哪些主要模块? 使用 Spring 框架能带来哪些好处? 什么是控制反转(IOC)?什么是依赖注入? 请解释下 Spring 框架中的 IoC? BeanFactory 和 ApplicationContext 有什么区别? Spring 有几种配置方式? 如何用基于 XML 配置的方式配置 Spring? 如何用基于 Java 配置的方式配置 Spring? 怎样用注解的方式配置 Spring? 请解释 Spring Bean 的生命周期? Spring Bean 的作用域之间有什么区别? 什么是 Spring inner beans? Spring 框架中的单例 Beans 是线程安全的么? 请举例说明如何在 Spring 中注入一个 Java Collection? 如何向 Spring Bean 中注入一个 Java.util.Properties? 请解释 Spring Bean 的自动装配? 请解释一下自动装配模式的区别? 如何开启基于注解的自动装配? 请举例解释@Required 注解? 请举例解释@Autowired 注解? 请举例说明@Qualifier 注解? 构造方法注入和设值注入有什么区别? Spring 框架中有哪些不同类型的事件? FileSystemResource 和 ClassPathResource 有何区别? Spring 框架中都用到了哪些设计模式? 开发中主要使用 Spring 的什么技术 ? 简述 AOP 和 IOC 概念 AOP 在 Spring 中如何配置 Bean ? IOC 容器对 Bean 的生命周期 答案: Key2:攻克Spring Boot ①Spring Boot-手绘脑图 ②Spring Boot实战 (入门+开发的第一个应用程序+自定义配置+测试+Groovy与Spring Boot CLI+在Spring Boot中使用Grails+深入Actuator+部署Spring Boot应用程序) ③Spring Boot 学习笔记-核心部分 (Spring Boot入门+配置文件+日志+Web开发+Docker+SpringBoot与数据访问+启动配置原理+自定义starter) ④Spring Boot面试题(高级应用篇) 什么是 Spring Boot? Spring Boot 有哪些优点? 什么是 JavaConfig? 如何重新加载 Spring Boot 上的更改,而无需重新启动服务器? Spring Boot 中的监视器是什么? 如何在 Spring Boot 中禁用 Actuator 端点安全性? 如何在自定义端口上运行 Spring Boot 应用程序? YAML 是一种人类可读的数据序列化语言。它通常用于配置文件。 如何实现 Spring Boot 应用程序的安全性? 如何集成 Spring Boot 和 ActiveMQ? 如何使用 Spring Boot 实现分页和排序? 什么是 Swagger?你用 Spring Boot 实现了它吗? 什么是 Spring Profiles? 什么是 Spring Batch? 什么是 FreeMarker 模板? 如何使用 Spring Boot 实现异常处理? 您使用了哪些 starter maven 依赖项? 什么是 CSRF 攻击? 什么是 WebSockets? 什么是 AOP? 什么是 Apache Kafka? 我们如何监视所有 Spring Boot 微服务? 答案: Key3:攻克Spring MVC ①Spring MVC-手绘脑图 ②Spring MVC源码分析与实践 (Spring框架+模型2和MVC模式+SpringMVC介绍+基于注解的控制器+数据绑定和表单标签库+转换器和格式化+验证器+表达式语言+JSTL+国际化+上传文件+下载文件+应用测试) ③Spring MVC学习笔记 ④Spring MVC面试题(高级应用篇) 什么是 SpringMVC? 说说Spring MVC 的优点 SpringMVC 工作原理 SpringMVC 流程 SpringMvc 的控制器是不是单例模式,如果是,有什么问题,怎么解决? 如果你也用过 struts2.简单介绍下 springMVC 和 struts2 的区别有哪些? SpingMVC 中的控制器的注解一般用哪个,有没有别的注解可以替代? @RequestMapping 注解用在类上面有什么作用? 怎么样把某个请求映射到特定的方法上面? 如果在拦截请求中,我想拦截 get 方式提交的方法,怎么配置? 怎么样在方法里面得到 Request,或者 Session? 我想在拦截的方法里面得到从前台传入的参数,怎么得到? 如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象? SpringMVC 中函数的返回值是什么? SpringMVC 怎么样设定重定向和转发的? SpringMVC 用什么对象从后台向前台传递数据的? SpringMVC 中有个类把视图和数据都合并的一起的,叫什么? 怎么样把 ModelMap 里面的数据放入 Session 里面? SpringMVC 怎么和 AJAX 相互调用的? 当一个方法向 AJAX 返回特殊对象,比如 Object,List 等,需要做什么处理? SpringMVC 里面拦截器是怎么写的? 讲下 SpringMVC 的执行流程  面试: Key4:攻克Spring Cloud ①Spring Cloud-手绘脑图 ②Spring Cloud参考指南 ③Spring Cloud学习笔记  第一篇:基础服务篇(微服务与SpringCloud+服务发现+配置中心+客户端负载均衡+熔断器+Zuul+网关新选择+调用链追踪+加密管理+公共子项目) 第二篇:任务与消息篇(消息驱动+消息总线+批处理) 第三篇:微服务实战篇(利用Docker进行编排与整合) ④Spring Cloud面试题(高级应用篇) 什么是 Spring Cloud? 使用 Spring Cloud 有什么优势? 服务注册和发现是什么意思?Spring Cloud 如何实现? 负载平衡的意义什么? 什么是 Hystrix?它如何实现容错? 什么是 Hystrix 断路器?我们需要它吗? 什么是 Netflix Feign?它的优点是什么? 什么是 Spring Cloud Bus?我们需要它吗? ———————————————— 原文链接:https://blog.csdn.net/weixin_66896902/article/details/126464926 
  • [技术干货] spring全家桶
    1、spring是一个容器、生态、框架 Spring框架为开发提供了一系列的解决方案,比如利用控制反转的核心特性,并通过依赖注入实现控制反转来实现管理对象生命周期容器化,利用面向切面编程进行声明式的事务管理,整合多种持久化技术管理数据访问,提供大量优秀的Web框架方便开发等等。Spring框架具有控制反转(IOC)特性,IOC旨在方便项目维护和测试,它提供了一种通过Java的反射机制对Java对象进行统一的配置和管理的方法。Spring框架利用容器管理对象的生命周期,容器可以通过扫描XML文件或类上特定Java注解来配置对象,开发者可以通过依赖查找或依赖注入来获得对象。Spring框架具有面向切面编程(AOP)框架,SpringAOP框架基于代理模式,同时运行时可配置;AOP框架主要针对模块之间的交叉关注点进行模块化。Spring框架下的事务管理、远程访问等功能均可以通过使用SpringAOP技术实现。  spring首先是一个框架,在我们整个开发流程中,所有的框架生产几乎全都依赖于spring,spring帮我们起到了一个IOC容器的作用,用来承载整体的bean对象,帮我们进行了对象的创建到销毁的整个生命周期的管理。在使用spring的时候可以使用配置文件也可以使用注解的方式来进行相关实现,在程序启动后,把配置文件或者注解定义好的那些bean对象转换成beanDefination,要完成整个beanDefination的解析到加载的过程,获得完整的对象之后,下一步要对beanDefination进行实例化操作,在进行实例化的时候最简单的方式是使用反射的方式来创建对象,当对象创建完成之后,之后要实现Aware、BeanPostProcessor接口的一些操作,初始化对象的一些操作  1.1IOC容器(存放bean对象) IOC(Inversion of Control): IOC容器控制管理bean对象,实现解耦。 控制翻转,其根本是依赖注入(Dependecy Injection),不会直接创建对象,只是把对象声明出来,在代码 中不直接与对象和服务进行连接,但是在配置文件中描述了哪一项组件需要哪一项服务,容器将他们组合起来。在一般的IOC场景中容器创建了所有的对象,并设置了必要的属性将他们联系在一起,等到需要使用的时候才把他们声明出来,使用注解就更方便了,容器会自动根据注 解把对象组合起来。  是我们在使用spring中最重要的一个方面,spring容器里面放的是一个一个的bean对象,spring在启动时通过BeanDefinitionReader读取XML配置文件或者注解(bean的定义信息)获取bean对象:  下图说明: BeanDefinitionReader将配置文件信息解析成BeanDefinition(有$占位符), BeanFactoryPostProcessor将此信息在解析成完整的BeanDefinition对象(将信息传入,去掉$占位符) 1 2 3  1.1.2 bean生命周期(复杂) 1)Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化,在堆开辟新空间,使用反射实现对象的实例化  2)Bean实例化后对将Bean的引入和值注入到Bean的属性中  3)如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法  4)如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入  5)如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。  6)如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。  7)如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用  8)如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。  9)此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。  10)如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。  参考文档:https://www.cnblogs.com/javazhiyin/p/10905294.html  1.1.3 bean生命周期(简单): 实例化 Instantiation 属性赋值 Populate 初始化 Initialization 销毁 Destruction 1)实例化(Instantiation) //实例化是指Bean 从Bean到Object Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);  2)属性赋值(Populate)  3)初始化(Initialization) 初始化前: org.springFrameWork.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization 初始化中: org.springFrameWork.bean.InitializingBean#afterPropertiesSet 初始化后:org.springFrameWork.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization  4)销毁  参考文档:https://www.jianshu.com/p/1dec08d290c1  1.2 AOP面向切面编程 AOP:面向切面编程,底层实现是动态代理(JDK、CGlib)  web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。 将方法注入到接口调用的某个地方(切点)。  1.2.1 相关概念: 1)Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。 2)Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。 3)Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。 4)Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。 5)Target(目标对象):织入 Advice 的目标对象.。 6)Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程  参考文档:https://blog.csdn.net/q982151756/article/details/80513340  1.3 JdbcTemplate JdbcTemplate是Spring对JDBC的封装,目的是使JDBC更加易于使用。JdbcTemplate是Spring的一部分。JdbcTemplate处理了资源的建立和释放。  在JdbcTemplate中执行SQL语句的方法大致分为3类: 1)execute:可以执行所有SQL语句,一般用于执行DDL语句。 2)update:用于执行INSERT、UPDATE、DELETE等DML语句。 3)queryXxx:用于DQL数据查询语句。  1.4 事物 1.4.1、事物的特性 1.4.1.1、原子性(Atomicity): 事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。  1.4.1.2、一致性(Consistency): 一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。  1.4.1.3、隔离性(Isolation): 可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。  1.4.1.4、持久性(Durability): 一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。  1.4.2、spring事物配置方式 1)编程式事物管理:是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。 2)声明式事物管理:建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。  声明式事务管理要优于编程式事务管理:声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。  1.4.3、事物的隔离级别  参考文档:https://www.cnblogs.com/mseddl/p/11577846.html  1.5 循环依赖 循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。  Spring中循环依赖场景有: (1)构造器的循环依赖 (2)field属性的循环依赖。  1.5.1、检测是否存在循环依赖 Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。  1.5.2、spring解决循环依赖 Spring为了解决单例的循环依赖问题,使用了三级缓存。  这三级缓存分别指: singletonFactories : 单例对象工厂的cache earlySingletonObjects :提前暴光的单例对象的Cache singletonObjects:单例对象的cache。  参考文档:https://blog.csdn.net/u010853261/article/details/77940767  2、SpringMVC 2.1、springMVC简介 M:model,模型层,模型就是数据 V:view,网页、jsp,显示数据 C:controller,控制层,控制器的作用就是把不同的数据(Model),显示在不同的视图(View)上,Servlet 扮演的就是这样的角色。  Spring MVC主要由DispatcherServlet、处理器映射、处理器(控制器)、视图解析器、视图组成。 他的两个核心是两个核心: 1)处理器映射:选择使用哪个控制器来处理请求 2)视图解析器:选择结果应该如何渲染  2.1.1、运行原理  (1) Http请求:客户端请求提交到DispatcherServlet。 (2) 寻找处理器:由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller。 (3) 调用处理器:DispatcherServlet将请求提交到Controller。 (4)调用业务处理和返回结果:Controller调用业务逻辑处理后,返回ModelAndView。 (5)处理视图映射并返回模型: DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图。 (6) Http响应:视图负责将结果显示到客户端。  2.1.2、SpringMVC接口解释 (1)DispatcherServlet接口: Spring提供的前端控制器,所有的请求都有经过它来统一分发。在DispatcherServlet将请求分发给Spring Controller之前,需要借助于Spring提供的HandlerMapping定位到具体的Controller。  (2)HandlerMapping接口: 能够完成客户请求到Controller映射。  (3)Controller接口: 需要为并发用户处理上述请求,因此实现Controller接口时,必须保证线程安全并且可重用。 Controller将处理用户请求,这和Struts Action扮演的角色是一致的。一旦Controller处理完用户请求,则返回ModelAndView对象给DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和视图(View)。 从宏观角度考虑,DispatcherServlet是整个Web应用的控制器;从微观考虑,Controller是单个Http请求处理过程中的控制器,而ModelAndView是Http请求过程中返回的模型(Model)和视图(View)。  (4)ViewResolver接口: Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。  2.1.3、DispatcherServlet: 是整个Spring MVC的核心。它负责接收HTTP请求组织协调Spring MVC的各个组成部分。其主要工作有以下三项: (1)截获符合特定格式的URL请求。 (2)初始化DispatcherServlet上下文对应WebApplicationContext,并将其与业务层、持久化层的WebApplicationContext建立关联。 (3)初始化Spring MVC的各个组成组件,并装配到DispatcherServlet中。  参考文档:https://blog.csdn.net/jianyuerensheng/article/details/51258942  3、springBoot 3.1springboot简介 用来简化spring初始搭建的过程,将配置文件转换成注解的形式,方便迅速。Spring Boot 提供了大量开箱即用(out-of-the-box)的依赖模块,例如 spring-boot-starter-redis、spring-boot-starter-data-mongodb 和 spring-boot-starter-data-elasticsearch 等。这些依赖模块为 Spring Boot 应用提供了大量的自动配置,使得 Spring Boot 应用只需要非常少量的配置甚至零配置,便可以运行起来。  3.2、特点 1)独立运行的 Spring 项目 Spring Boot 可以以 jar 包的形式独立运行,Spring Boot 项目只需通过命令“ java–jar xx.jar” 即可运行。 2) 内嵌 Servlet 容器 Spring Boot 使用嵌入式的 Servlet 容器(例如 Tomcat、Jetty 或者 Undertow 等),应用无需打成 WAR 包 。 3) 提供 starter 简化 Maven 配置 Spring Boot 提供了一系列的“starter”项目对象模型(POMS)来简化 Maven 配置。 4) 提供了大量的自动配置 Spring Boot 提供了大量的默认自动配置,来简化项目的开发,开发人员也通过配置文件修改默认配置。 5) 自带应用监控 Spring Boot 可以对正在运行的项目提供监控。 6) 无代码生成和 xml 配置 Spring Boot 不需要任何 xml 配置即可实现 Spring 的所有配置。 参考文档:http://c.biancheng.net/spring_boot/overview.html  4、springCloud Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性简化了分布式系统的开发,比如服务发现、服务网关、服务路由、链路追踪等。Spring Cloud 并不重复造轮子,而是将市面上开发得比较好的模块集成进去,进行封装,从而减少了各模块的开发成本。换句话说:Spring Cloud 提供了构建分布式系统所需的“全家桶”。  4.1、优点: 1)集大成者,Spring Cloud 包含了微服务架构的方方面面。 2)约定优于配置,基于注解,没有配置文件。 3)轻量级组件,Spring Cloud 整合的组件大多比较轻量级,且都是各自领域的佼佼者。 4)开发简便,Spring Cloud 对各个组件进行了大量的封装,从而简化了开发。 5)开发灵活,Spring Cloud 的组件都是解耦的,开发人员可以灵活按需选择组件。  4.2、Spring Cloud 模块的相关介绍: Eureka:服务注册中心,用于服务管理。 Ribbon:基于客户端的负载均衡组件,默认的负载策略是轮询。 Hystrix:容错框架,能够防止服务的雪崩效应。 Feign:Web 服务客户端,能够简化 HTTP 接口的调用。 Zuul:核心是过滤器,API 网关,提供路由转发、请求过滤等功能,是一个基于 JVM 路由和服务端的负载均衡器。 Config:分布式配置管理。 Sleuth:服务跟踪。 Stream:构建消息驱动的微服务应用程序的框架。 Bus:消息代理的集群消息总线。  4.2.1、 服务治理 Eureka 和 Zookeeper 区别: Eureka 是基于 AP 原则构建的,而 ZooKeeper 是基于 CP 原则构建的。  注: 在分布式系统领域有个著名的 CAP 定理,即 C 为数据一致性;A 为服务可用性;P 为服务对网络分区故障的容错性。这三个特性在任何分布式系统中都不能同时满足,最多同时满足两个。  服务治理就是服务的自动化管理,其核心是服务的自动注册与发现 1)服务注册:服务实例将自身服务信息注册到注册中心 2)服务发现:服务实例通过注册中心,获取注册到其中的服务实例的信息,通过这些信息去请求题目提供的服务 3)服务剔除:服务注册中心将出问题的服务自动剔除可使用服务列表,使其不会被调用 参考文档:http://c.biancheng.net/spring_cloud/ 5、SSM Java 企业开发框架 SSM,即 Spring、SpringMVC、MyBatis 。 Spring框架:是一个轻量级 Java 开发框架,主要是为了解决企业应用开发的复杂性而创建的。 SpringMVC框架:SpringMVC 分离了 控制器、模型对象、分派器,让我们更容易进行开发定制 MyBatis框架:是一个 Java 持久层框架,用于操作数据库,消除了几乎所有的 JDBC 代码,使用简单的 XML 或 注解即可完成数据库操作。 ————————————————       原文链接:https://blog.csdn.net/weixin_47268011/article/details/118188608 
总条数:764 到第
上滑加载中