listener,filter区别--ZT

本文详细解释了Filter(过滤器)和Listener(监听器)的区别与联系。Filter主要用于处理请求前后的过滤工作,如权限控制、乱码处理等;而Listener则用于监听应用中的特定事件,如session创建和销毁等。


web.xml里面可以配置Filter和Listener,他们有什么区别,有什么共同点吗?应该在什么情况下使用他们?
网友回复:Filter是过滤器,过滤一些不友好或是你不想要的东东, 

Listener是Servlet的监听器,可以监听客户端的请求、服务端的操作等。 
通过监听器,可以自动激发一些操作。
网友回复:Filter可以实现对请求的过滤和重定向等,也就是说可以操作request和response,session等对象,listner只能监听到以上对象的属性的修改。
网友回复:Filter 是struts的核心控制器,负责拦截所有用户请求。 
listener是监听器,通常都是监听并加载一些插件用的,比如spring。log4j等
网友回复:Filter(过滤器):你可以写个类实现Filter接口,然后配置到web.xml中,那么Tomcat在接受到Http请求后首先会调用FilterChain中的第一个过滤器,为了能调用下个过滤器或真正的请求(servlet or jsp),所以你的实现类所实现的方法中必须要调用chain.doFilter(request,response),不然会得到空白页面!过滤器通常用做处理乱码,权限控制,也可以治理Hibernate中的session! 
Listener(监听器):利用观察者模式实现,在Tomcat中,假如要对application,session,request,response等对象监听,要实现类似****Listener接口,再配置到web.xml中,那么Tomcat在启动的时候就会把你写的这些监听类实例化成对象加入到一个集合中,此后对你要监听的对象操作时,都将会调用listener中的方法,比如HttpSessionListener 可以用来监听当前在线的所有用户!
网友回复:Filter和servlet基本类似 可以用做servlet来用(struts2.0就是这么干的) 有reqeust请求才能运行 

listener是监听器 是系统启动就运行 一般监听都是用来事先加载系统文件的
网友回复:
Filter是过滤器,过滤一些不友好或是你不想要的东东, 

Listener是Servlet的监听器,可以监听客户端的请求、服务端的操作等。 
通过监听器,可以自动激发一些操作。 

也是我地看法
网友回复:本身从字面上都比较好理解区分的 
Filter 过滤器, 

Listener 监听器

package com.jsfj.evcall.biz.task.listener; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.toolkit.BeanUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.jsfj.common.util.RedisUtil; import com.jsfj.evcall.biz.common.conf.TaskEmergencyConfig; import com.jsfj.evcall.biz.common.constants.task.CarOutStatusEnum; import com.jsfj.evcall.biz.common.util.HttpUtils; import com.jsfj.evcall.biz.task.dao.mapper.ICarOutMapper; import com.jsfj.evcall.biz.task.entity.DispatchInfoDto; import com.jsfj.evcall.biz.task.entity.po.CarOutPo; import com.jsfj.evcall.biz.task.event.DispatchTimeNodeEvent; import com.jsfj.evcall.biz.task.push.CarOutInfoDto; import com.jsfj.evcall.biz.unit.dao.mapper.IAmbMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationListener; import org.springframework.data.redis.connection.RedisServer; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; /** * 推送调度信息 */ @Component @Async @Slf4j @ConditionalOnProperty(prefix = "push.suining", name = "enable", havingValue = "true", matchIfMissing = false) public class DispatchTimeNodeEventListener implements ApplicationListener<DispatchTimeNodeEvent> { @Autowired private ICarOutMapper iCarOutMapper; @Autowired private IAmbMapper iAmbMapper; @Autowired private RedisUtil redisUtil; @Autowired private TaskEmergencyConfig taskEmergencyConfig; @Override public void onApplicationEvent(DispatchTimeNodeEvent event) { //获取调度id DispatchInfoDto dto = (DispatchInfoDto) event.getSource(); Long dispatchId = dto.getDispatchId(); log.info("接受到的参数:{}", dispatchId); //查询调度出车信息 List<CarOutPo> carOutPos = iCarOutMapper.selectList(Wrappers.<CarOutPo>lambdaQuery().eq(CarOutPo::getTaskDispatchId, dispatchId)); if (CollectionUtil.isEmpty(carOutPos)) { return; } List<Integer> carOutStatus = CarOutStatusEnum.pushSH(); //过滤满足条件的出车状态 carOutPos = carOutPos.stream().filter(carOut -> carOutStatus.contains(carOut.getStatus()) || CarOutStatusEnum.isCancel(carOut.getStatus())).collect(Collectors.toList()); //不过滤状态,保证当前状态下有值,时间有值,及时补偿,redis只保存推送成功的 List<CarOutPo> newCarOut = new ArrayList<>(); //判断该阶段是否已经推送过数据 for (CarOutPo carOutPo : carOutPos) { //组装redisKey String redisKey = String.format("PushNode:%s:%s", dispatchId, carOutPo.getAmbId()); List<Integer> status = (List<Integer>) redisUtil.get(redisKey); log.info("redis缓冲的数据:{}",JSONArray.toJSONString(status)); if (CollectionUtils.isEmpty(status) || !status.contains(carOutPo.getStatus())) { log.info("节点状态:{},{}",dispatchId,status); List<Integer> integers = status == null ? new ArrayList<>() : status; integers.add(carOutPo.getStatus()); redisUtil.set(redisKey, integers, 24 * 60 * 60 * 2); newCarOut.add(carOutPo); } // 判断是否是最后一个节点:做补偿机制 if (Objects.equals(carOutPo.getStatus(), CarOutStatusEnum.CAR_ARRIVED_HOSPITAL.getCode())) { log.info("是否进入补偿机制"); List<Integer> pushSH = CarOutStatusEnum.pushSH(); List<Integer> redisStatus = (List<Integer>) redisUtil.get(redisKey); log.info("进入缓冲机制的redis里面的数据:{}",JSONArray.toJSONString(redisStatus)); pushSH.removeAll(redisStatus); log.info("需要补偿的节点数据:{}",JSONArray.toJSONString(pushSH)); if(CollectionUtil.isNotEmpty(pushSH)){ for (Integer zt : pushSH) { CarOutPo carOutPo1 = BeanUtil.copyProperties(carOutPo, CarOutPo.class); log.info("补偿节点状态:{},{}",dispatchId,zt); List<Integer> integers = new ArrayList<>(redisStatus); integers.add(zt); redisUtil.set(redisKey, integers, 24 * 60 * 60 * 2); carOutPo1.setStatus(zt); newCarOut.add(carOutPo1); } } } } if (CollectionUtil.isEmpty(newCarOut)) { return; } List<CarOutPo> collect = newCarOut.stream().sorted(Comparator.comparing(CarOutPo::getStatus)).collect(Collectors.toList()); collect.forEach(carOutPo -> { CarOutInfoDto build = CarOutInfoDto.builder() .jz_dddh(dispatchId.toString()) .ztdm(Optional.ofNullable(carOutPo.getAmbId()).map(Object::toString).orElse(null)) .ztpz(Optional.ofNullable(carOutPo.getPlateNumber()).filter(StringUtils::isNotBlank).orElse(null)) // .czsj(Optional.ofNullable(carOutPo.getLeaveTime()).map(item -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item)).orElse(null)) // .ddxcsj(Optional.ofNullable(carOutPo.getArriveTime()).map(item -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item)).orElse(null)) // .jzhzsj(null) // .fcsj(Optional.ofNullable(carOutPo.getReturnTime()).map(item -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item)).orElse(null)) // .rwwcsj(Optional.ofNullable(carOutPo.getCompletedTime()).map(item -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item)).orElse(null)) // .sfzcjs(carOutPo.getCompletedTime() != null ? 1 : 0) .jdmc(Optional.ofNullable(carOutPo.getStatus()).map(CarOutStatusEnum::carOutStatusName).orElse(null)) .ycyy(Optional.ofNullable(carOutPo.getCancelReason()).filter(StringUtils::isNotBlank).orElse(null)).build(); if (carOutPo.getStatus() != null) { if (Objects.equals(carOutPo.getStatus(), CarOutStatusEnum.CAR_DEPARTED.getCode())) { build.setJlsj(Optional.ofNullable(carOutPo.getLeaveTime()).map(item -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item)).orElse(null)); } else if (Objects.equals(carOutPo.getStatus(), CarOutStatusEnum.CAR_ARRIVED_SITE.getCode())) { build.setJlsj(Optional.ofNullable(carOutPo.getArriveTime()).map(item -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item)).orElse(null)); } else if (Objects.equals(carOutPo.getStatus(), CarOutStatusEnum.CAR_RETURN.getCode())) { build.setJlsj(Optional.ofNullable(carOutPo.getReturnTime()).map(item -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item)).orElse(null)); } else if (Objects.equals(carOutPo.getStatus(), CarOutStatusEnum.CAR_ARRIVED_HOSPITAL.getCode())) { build.setJlsj(Optional.ofNullable(carOutPo.getCompletedTime()).map(item -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item)).orElse(null)); } else if (CarOutStatusEnum.isCancel(carOutPo.getStatus())) { build.setJlsj(Optional.ofNullable(carOutPo.getCancelTime()).map(item-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item)).orElse(null)); } } String param = JSONObject.toJSONString(build); retryPush( param,dispatchId); }); } private static final int MAX_RETRIES = 3; private static final long RETRY_INTERVAL_MS = 1000; // 每次重试间隔1秒 public boolean retryPush( String param,Long dispatchId) { int attempt = 0; while (attempt < MAX_RETRIES) { try { if (executePush(param,dispatchId)) { return true; // 推送成功,直接返回 } } catch (Exception e) { log.warn("推送失败(第{}次尝试): {}", attempt + 1, e.getMessage()); } attempt++; if (attempt < MAX_RETRIES) { try { Thread.sleep(RETRY_INTERVAL_MS); // 等待一段时间后重试 } catch (InterruptedException ie) { Thread.currentThread().interrupt(); log.error("线程中断:{}", ie.getMessage()); } } } return false; // 所有重试均失败 } private boolean executePush(String param,Long dispatchId) { log.info("推送的参数:{}", param); log.info("推送地址:{}", taskEmergencyConfig.getBaseUrl() + taskEmergencyConfig.getPushDispatchInfoUrl()); log.info("推送header参数:{}", taskEmergencyConfig.getThirdInstitutionID()); String resultJson = HttpUtils.sendHttps(taskEmergencyConfig.getBaseUrl() + taskEmergencyConfig.getPushDispatchInfoUrl(), taskEmergencyConfig.getThirdInstitutionID(), param); log.info("响应数据:{}", resultJson); if (StringUtils.isNotBlank(resultJson)) { JSONObject jsonObject = JSONObject.parseObject(resultJson); if (!Objects.equals(jsonObject.getString("resultcode"), "0")) { log.error("响应错误提示:{}", jsonObject.getString("message")); //失败的要贴出并补偿 //并将发送数据存入redis中做备份 String key = String.format("TimeNode:%s", dispatchId); redisUtil.set(key, param); return false; } return true; } return false; } } 以上为源代码,根据源代码优化为CAR_DEPARTED ,CAR_DEPARTED,CAR_RETURN,CAR_ARRIVED_HOSPITAL 有序发送, retryPush失败不存redis
05-28
<think>我们正在讨论RabbitMQ中的两个不同概念:`reply-listener``publisher-confirms`。它们分别用于不同的场景,下面我将详细解释它们的区别。 ### 1. **reply-listener** `reply-listener`是Spring AMQP中用于RPC(远程过程调用)模式的一个组件。当使用`RabbitTemplate`的`convertSendAndReceive()`方法发送消息时,它会自动设置一个临时回复队列,并监听该队列以接收来自服务端的响应。核心特点包括: - **同步通信**:客户端发送消息后,会阻塞等待响应(可设置超时)。 - **临时队列**:为每个RPC请求创建一个临时的、独占的回复队列(通常以`amq.rabbitmq.reply-to`为前缀)。 - **关联ID**:通过`correlationId`匹配请求响应,确保响应的正确性。 - **线程管理**:使用线程池(可配置)处理响应,避免阻塞主线程。 **示例配置**(XML): ```xml <rabbit:template id="rpcTemplate" connection-factory="connectionFactory"> <rabbit:reply-listener concurrency="2-4" task-executor="taskExecutor"/> </rabbit:template> ``` **应用场景**:需要同步获取结果的远程调用,如订单创建后立即返回订单号。 --- ### 2. **publisher-confirms** `publisher-confirms`(也称为Publisher Confirms)是RabbitMQ提供的一种消息可靠性保证机制,用于**生产者确认消息是否成功到达Broker**。核心特点包括: - **异步确认**:生产者发送消息后,Broker异步返回确认(ack)或失败(nack)。 - **消息可靠性**:确保消息不丢失(结合持久化队列消息)。 - **回调处理**:通过`ConfirmCallback`接口处理确认结果。 **配置示例**(YAML): ```yaml spring: rabbitmq: publisher-confirms: true # 启用确认模式 publisher-returns: true # 启用返回模式(当消息无法路由时返回) ``` **代码示例**(确认回调): ```java @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); template.setConfirmCallback((correlationData, ack, cause) -> { if (ack) { System.out.println("消息成功到达Broker"); } else { System.out.println("消息发送失败: " + cause); } }); return template; } ``` **应用场景**:金融交易等需要高可靠性的场景,确保消息不丢失。 --- ### 3. 核心区别对比 | **特性** | **reply-listener** | **publisher-confirms** | |------------------------|----------------------------------------|--------------------------------------| | **目的** | 实现RPC同步响应 | 保证生产者消息可靠投递 | | **通信方向** | 消费者→生产者的响应(反向) | 生产者→Broker的确认(正向) | | **阻塞性** | 同步阻塞(等待响应) | 异步非阻塞(回调通知) | | **使用场景** | 需要立即获取结果的调用 | 需要保证消息不丢失的场景 | | **配置位置** | Spring AMQP的`<rabbit:reply-listener>` | RabbitMQ连接工厂的`publisher-confirms` | | **技术实现** | 临时回复队列+关联ID | Broker确认机制+回调接口 | --- ### 4. 常见问题与解决 #### 问题1:`reply-listener`收不到响应 - **原因**:临时队列被防火墙拦截或未正确设置`correlationId`。 - **解决**:检查网络策略,确保服务端将响应发送到`replyTo`队列并携带相同的`correlationId`[^1]。 #### 问题2:`publisher-confirms`未触发回调 - **原因**:未启用配置或未注册`ConfirmCallback`。 - **解决**: 1. 确认YAML中`publisher-confirms: true`[^2]。 2. 检查`RabbitTemplate`是否设置了`setConfirmCallback()`。 #### 问题3:`PRECONDITION_FAILED`错误 - **原因**:队列/交换机属性不匹配(如持久化设置冲突)[^2][^3]。 - **解决**:删除旧队列或保持声明属性一致。 --- ### 5. 总结 - **`reply-listener`** → 用于**RPC响应**,解决“如何同步获取远程服务结果”的问题。 - **`publisher-confirms`** → 用于**消息可靠投递**,解决“如何确保消息不丢失”的问题。 > 两者可同时使用: > - 生产者用`publisher-confirms`确保请求消息到达Broker > - 消费者用`reply-listener`确保RPC响应返回给客户端 --- ### 相关问题 1. RabbitMQ中如何同时保证消息发送可靠性RPC响应可靠性? 2. 如何为不同的RPC服务配置独立的响应队列? 3. `publisher-confirms`事务(transaction)模式有何优劣? 4. RabbitMQ的临时队列(`amq.rabbitmq.reply-to`)有哪些限制? 5. Spring AMQP中如何实现异步RPC(非阻塞等待响应)? 6. 如何监控RabbitMQ的生产者确认RPC响应延迟?</think>### RabbitMQ `reply-listener` vs `publisher-confirms` 核心区别详解 #### 一、本质功能对比 | **特性** | **reply-listener** | **publisher-confirms** | |------------------------|----------------------------------------|--------------------------------------| | **目的** | 实现RPC同步响应机制 | 保证生产者消息可靠投递 | | **通信方向** | 消费者→生产者的响应(反向) | 生产者→Broker的确认(正向) | | **阻塞性** | 同步阻塞(等待响应) | 异步非阻塞(回调通知) | | **使用场景** | 需要立即获取结果的调用 | 需要保证消息不丢失的场景 | | **配置位置** | Spring AMQP的`<rabbit:reply-listener>` | RabbitMQ连接工厂的`publisher-confirms` | | **技术实现** | 临时回复队列+关联ID | Broker确认机制+回调接口 | --- #### 二、工作流程详解 ##### 1. `reply-listener` (RPC响应监听器) ```mermaid sequenceDiagram participant P as 生产者 participant B as Broker participant C as 消费者 P->>B: 发送请求(含reply-to头) B->>C: 路由到目标队列 C->>B: 返回响应到reply-to队列 B->>P: 通过reply-listener推送响应 ``` - **关键点**: - 自动创建临时队列(如`amq.rabbitmq.reply-to.*`) - 通过`correlationId`匹配请求响应[^1] - 线程阻塞直到收到响应或超时 ##### 2. `publisher-confirms` (生产者确认) ```mermaid sequenceDiagram participant P as 生产者 participant B as Broker P->>B: 发送消息 alt 消息持久化成功 B-->>P: 发送ACK确认 else 持久化失败 B-->>P: 发送NACK拒绝 end ``` - **关键点**: - 异步回调机制(不阻塞生产者线程) - 需实现`ConfirmCallback`接口处理结果 - 需配合`publisher-returns`处理路由失败 --- #### 三、配置方式对比 ##### `reply-listener`配置(XML示例) ```xml <rabbit:template id="rpcTemplate"> <rabbit:reply-listener concurrency="3-5" task-executor="rpcExecutor" requeue-rejected="false"/> </rabbit:template> ``` ##### `publisher-confirms`配置(YAML示例) ```yaml spring: rabbitmq: publisher-confirms: true # 启用确认模式 publisher-returns: true # 启用返回模式 ``` ##### Java回调配置 ```java // 生产者确认回调 template.setConfirmCallback((correlationData, ack, cause) -> { if(ack) { System.out.println("消息到达Broker"); } else { System.out.println("消息丢失: " + cause); } }); // 不可路由消息处理 template.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { System.out.println("消息无法路由: " + replyText); }); ``` --- #### 四、运行效果对比 ##### `reply-listener`使用示例 ```java // 同步RPC调用(阻塞线程) OrderResponse response = (OrderResponse) template.convertSendAndReceive( "order.exchange", "order.create", request ); ``` **输出**: ``` [main] 发送订单请求... [ReplyListener-1] 收到响应: ORD-123 [main] 收到响应: ORD-123 ``` ##### `publisher-confirms`使用示例 ```java // 异步发送消息 template.convertAndSend("payment.exchange", "payment.process", payment); ``` **输出**: ``` [AMQP Connection] 消息确认回调: ACK 或 [AMQP Connection] 消息丢失: NO_ROUTE ``` --- #### 五、常见问题解决 | **问题现象** | **原因及解决方案** | |-------------------------------|----------------------------------------------------------------------------------| | `reply-listener`收不到响应 | 检查防火墙是否阻塞临时队列[^2],确保服务端正确设置`correlationId` | | `PRECONDITION_FAILED`错误 | 队列属性冲突(如持久化设置不一致),删除重建队列或统一配置[^2] | | `unknown delivery tag` | 手动ACK模式下重复确认,需确保每个消息只ACK一次[^3] | | 生产者确认回调未触发 | 检查`publisher-confirms: true`配置,确认网络可达[^2] | | 响应超时 | 增加`reply-timeout`值或优化消费者性能 | --- #### 六、典型应用场景 1. **`reply-listener`适用场景**: - 订单创建后立即返回订单ID - 支付服务同步验证 - 需要事务性响应的操作 2. **`publisher-confirms`适用场景**: - 金融交易消息 - 日志审计系统 - 重要状态更新通知 > ⚠️ **关键区别**: > `reply-listener`解决的是**如何获取远程服务结果**的问题,而`publisher-confirms`解决的是**如何确保消息不丢失**的问题。 --- ### 相关问题 1. 如何同时使用`publisher-confirms``reply-listener`实现端到端可靠通信? 2. RabbitMQ临时队列(`amq.rabbitmq.reply-to`)有哪些限制注意事项? 3. 在手动ACK模式下如何避免`unknown delivery tag`错误? 4. 如何为不同的RPC服务配置独立的响应线程池? 5. `publisher-confirms`事务(transaction)模式有何性能差异? 6. 如何监控RabbitMQ的生产者确认率RPC响应延迟?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值