Java自定义注解实现

目前有个需求,需要在项目中使用Pulsar-java-api,正常使用需要新建client,新建消费者,并逐一维护他们的配置。为了简便,我们可以使用Java自定义注解来简化开发过程。

先列出正常建消费者的步骤

public class ConsumerDemo {
    public static void main(String[] args) throws PulsarClientException {
        PulsarClient client = PulsarClient.builder()
                .serviceUrl("pulsar://10.0.0.27:30980")
                .listenerThreads(200)
                .build();
        MessageListener myMessageListener = new MessageListener() {
            @Override
            public void received(Consumer consumer, Message msg) {
                try {
                    System.out.println("收到消息: " + new String(msg.getData())+"当前条数"+adder.toString());
                    consumer.acknowledge(msg);
                } catch (Exception e) {
                    consumer.negativeAcknowledge(msg);
                }
            }
        };
        Consumer consumer = client.newConsumer(Schema.STRING)
                .topic("persistent://gpsTenant/positionNamespace/positionTopic")
                .subscriptionName("devicePositionMsgTopicSubscription")
                .subscriptionType(SubscriptionType.Shared)
                .messageListener(myMessageListener)
                .subscribe();
    }
}

下面是使用自定义注解实现的方法

1、新建注解类

@Retention(RetentionPolicy.RUNTIME)  // 生命周期是在项目运行过程中
@Target(ElementType.METHOD)        // 作用域在方法上
public @interface PulsarConsumer {
    String topic();

    Class<?> clazz() default byte[].class;

    Serialization serialization() default Serialization.STRING;

    /**
     * 订阅类型
     */
    SubscriptionType[] subscriptionType() default {};

    /**
     * 消费者名称,为空pulsar会自动生成id
     */
    String consumerName() default "";

    /**
     * 订阅名称,为空pulsar会自动生成id
     * 在共享模式(Shared)中,subscriptionName会决定是不是同一个消费者租
     * subscriptionName一样,消息会按照round-robin(轮询)机制或其他自定义策略分发给消费者组内的各个消费者。
     * subscriptionName不一样,每个消费者组都会独立地接收Topic中的所有消息。
     */
    String subscriptionName() default "";

    /***
     * 消息重试时间
     */
    int negativeAckRedeliveryDelay() default -1;

    int ackTimeout() default 0;

    /**
     * 发送到死信队列之前重试的最大次数
     */
    int maxRedeliverCount() default -1;

    /**
     * 死信队列topic名称
     */
    String deadLetterTopic() default "";

    /**
     * 项目启动时是否加载该消费者,默认加载
     */
    boolean autoStart() default true;

    /**
     * 命名空间名称,默认加载配置文件
     */
    String namespace() default "";

    /**
     * 消费消息的初始位置,默认为Latest最新的, Earliest最旧的
     */
    SubscriptionInitialPosition initialPosition() default SubscriptionInitialPosition.Latest;
}

2、收集使用了注解的方法,利用spring的bean来筛选


/**
 * 消费者收集器
 */
@Configuration
public class ConsumerCollector implements BeanPostProcessor {


    private final UrlBuildService urlBuildService;

    private final Map<String, ConsumerHolder> consumers = new ConcurrentHashMap<>();
    private final Set<Class<?>> nonAnnotatedClasses = Sets.newHashSet();

    public ConsumerCollector(UrlBuildService urlBuildService) {
        this.urlBuildService = urlBuildService;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        final Class<?> beanClass = bean.getClass();

        // 重复bean校验
        if (this.nonAnnotatedClasses.contains(beanClass)) {
            return bean;
        }

        // 检查是否有注解 @PulsarConsumer
        consumers.putAll(Arrays.stream(beanClass.getDeclaredMethods())
                .filter($ -> {
                    if (!$.isAnnotationPresent(PulsarConsumer.class)) {
                        this.nonAnnotatedClasses.add(beanClass);
                        LoggerBuilder.busLogger().trace("No @PulsarConsumer annotations found on bean type:{} ", bean.getClass());
                        return false;
                    }
                    return true;
                })
                .collect(Collectors.toMap(
                        method -> urlBuildService.buildConsumerName(beanClass, method),
                        method -> new ConsumerHolder(method.getAnnotation(PulsarConsumer.class), method, bean, SchemaUtils.getParameterType(method)))));

        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }

    public Map<String, ConsumerHolder> getConsumers() {
        return consumers;
    }

    public Optional<ConsumerHolder> getConsumer(String methodDescriptor) {
        return Optional.ofNullable(consumers.get(methodDescriptor));
    }
}

3、使用收集到的消费者,来进行构建。

pulsarClient这个bean在这里就忽略不写了。

@DependsOn保证这个bean构建时在pulsarClient和consumerCollector之后构建

@EventListener(ApplicationReadyEvent.class) 注解保证该方法在ApplicationReadyEvent事件触发后调用, 该事件在Spring 应用程序完成所有初始化工作并准备好开始接收请求后发布。

@Component
@DependsOn({"pulsarClient", "consumerCollector"})
public class ConsumerAggregator implements EmbeddedValueResolverAware {

    //    private Sinks.Many<GpsException> sink = Sinks.many().multicast().onBackpressureBuffer(Queues.SMALL_BUFFER_SIZE, false);
    private final ConsumerCollector consumerCollector;
    private final PulsarClient pulsarClient;
    private final PulsarProperties pulsarProperties;
    private final UrlBuildService urlBuildService;
    private final ConsumerInterceptor consumerInterceptor;

    private StringValueResolver stringValueResolver;
    private List<Consumer> consumers;

    public ConsumerAggregator(ConsumerCollector consumerCollector, PulsarClient pulsarClient,
                              PulsarProperties pulsarProperties, UrlBuildService urlBuildService,
                              ConsumerInterceptor consumerInterceptor) {
        this.consumerCollector = consumerCollector;
        this.pulsarClient = pulsarClient;
        this.pulsarProperties = pulsarProperties;
        this.urlBuildService = urlBuildService;
        this.consumerInterceptor = consumerInterceptor;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void init() {
        //@FIXME 这里是每个消费者的配置,需要再细化到消费者进行判断
        if (pulsarProperties.isAutoStart()) {
            consumers = consumerCollector.getConsumers().entrySet().stream()
                    .filter(holder -> holder.getValue().getAnnotation().autoStart())
                    .map(holder -> subscribe(holder.getKey(), holder.getValue()))
                    .collect(Collectors.toList());
        }
    }

    private Consumer<?> subscribe(String generatedConsumerName, ConsumerHolder holder) {
        try {
            final String consumerName = stringValueResolver.resolveStringValue(holder.getAnnotation().consumerName());
            final String subscriptionName = stringValueResolver.resolveStringValue(holder.getAnnotation().subscriptionName());
            final String topicName = stringValueResolver.resolveStringValue(holder.getAnnotation().topic());
            final String namespace = stringValueResolver.resolveStringValue(holder.getAnnotation().namespace());
            final SubscriptionType subscriptionType = urlBuildService.getSubscriptionType(holder);
            final ConsumerBuilder<?> consumerBuilder = pulsarClient
                    .newConsumer(SchemaUtils.getSchema(holder.getAnnotation().serialization(),
                            holder.getAnnotation().clazz()))
                    .consumerName(urlBuildService.buildPulsarConsumerName(consumerName, generatedConsumerName))
                    .subscriptionName(urlBuildService.buildPulsarSubscriptionName(subscriptionName, generatedConsumerName))
                    .topic(urlBuildService.buildTopicUrl(topicName, namespace))
                    .subscriptionType(subscriptionType)
                    .subscriptionInitialPosition(holder.getAnnotation().initialPosition())
                    .messageListener((consumer, msg) -> {
                        try {
                            final Method method = holder.getHandler();
                            method.setAccessible(true);
                            //可以将代理类注入到包装处理的
                            if (holder.isWrapped()) {
                                method.invoke(holder.getBean(), wrapMessage(msg));
                            } else {
                                method.invoke(holder.getBean(), msg.getValue());
                            }

                            consumer.acknowledge(msg);
                        } catch (Exception e) {
                            LoggerBuilder.busLogger().error(msg.getValue() + "-->消费失败", e);
                            consumer.negativeAcknowledge(msg);
//                        sink.tryEmitNext(new GpsException(PulsarError.PULSAR_CONSUMER_ERROR,e));
                        }
                    });

            if (holder.getAnnotation().negativeAckRedeliveryDelay() > 0) {
                consumerBuilder.negativeAckRedeliveryDelay(holder.getAnnotation().negativeAckRedeliveryDelay(), TimeUnit.SECONDS);
            }

            // 设置消费者确认消息超时时间
            if (holder.getAnnotation().ackTimeout() > 0) {
                consumerBuilder.ackTimeout(holder.getAnnotation().ackTimeout(), TimeUnit.SECONDS);
            }

            if (pulsarProperties.isAllowInterceptor()) {
                consumerBuilder.intercept(consumerInterceptor);
            }


            urlBuildService.buildDeadLetterPolicy(
                    holder.getAnnotation().maxRedeliverCount(),
                    holder.getAnnotation().deadLetterTopic(),
                    consumerBuilder);

            return consumerBuilder.subscribe();
        } catch (PulsarClientException e) {
            LoggerBuilder.busLogger().error(e.getLocalizedMessage());
            throw new PulsarException(PulsarError.PULSAR_CONSUMER_INIT_ERROR, e.getMessage());
        }
    }

    public <T> PulsarMessage<T> wrapMessage(Message<T> message) {
        final PulsarMessage<T> pulsarMessage = new PulsarMessage<T>();

        pulsarMessage.setValue(message.getValue());
        pulsarMessage.setMessageId(message.getMessageId());
        pulsarMessage.setSequenceId(message.getSequenceId());
        pulsarMessage.setProperties(message.getProperties());
        pulsarMessage.setTopicName(message.getTopicName());
        pulsarMessage.setKey(message.getKey());
        pulsarMessage.setEventTime(message.getEventTime());
        pulsarMessage.setPublishTime(message.getPublishTime());
        pulsarMessage.setProducerName(message.getProducerName());

        return pulsarMessage;
    }

    public List<Consumer> getConsumers() {
        return consumers;
    }

//    public Disposable onError(java.util.function.Consumer<? super GpsException> consumer) {
//        return sink.asFlux().subscribe(consumer);
//    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
        this.stringValueResolver = stringValueResolver;
    }
}

到目前为止,我们基本工作已经做完了,里面有挺多细节代码没列出来。

思路就是新建注解,利用spring+反射来加载出来消费者们,并将他们初始化。

至此,我们可以愉快的使用注解来创建消费者了,需要注意的是:消费者的类需要被spring来维护

    @PulsarConsumer(topic = "delayQueueTopic",
            clazz = String.class,
            serialization = Serialization.STRING,
            subscriptionType = SubscriptionType.Shared, // 订阅模式,默认为独占模式
            subscriptionName = "delayQueueTopicSubscription",
            maxRedeliverCount = -1, // 最大重试次数
            deadLetterTopic = "delayQueueTopic_DLT", // 死信topic名称
            negativeAckRedeliveryDelay = 20 // 重试间隔时间 30s
    )
    // 如果消费失败,请抛出异常,这样消息会进入重试队列,之后可以重新消费,直到达到最大重试次数之后,进入死信队列。
    public void received(String str) {
        GpsLogger.busLogger().info("收到车队列消息,消息内容message={}", str);

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值