Kafka-Consumer 源码解析 -- listener 注册和启动

2023-10-27

前言

    本文主要针对KafkaListener注解的解析和运行过程进行简单的分析,对消费者的启动和listener的注册过程加以说明
    本文不涉及consumer的分区确定和rebalance等问题的说明。

1、KafkaListener注解说明

KafkaListener.java

public @interface KafkaListener {
   String id() default "";
   String containerFactory() default "";
   String[] topics() default {};
   String topicPattern() default "";
   TopicPartition[] topicPartitions() default {};
   String containerGroup() default "";
   String errorHandler() default "";
   String groupId() default "";
   boolean idIsGroup() default true;
   String clientIdPrefix() default "";
   String beanRef() default "__listener";
   String concurrency() default "";
   String autoStartup() default "";
   String[] properties() default {};
}

常用参数说明:

  • id:消费者的id,当GroupId没有被配置的时候,默认id为GroupId
  • containerFactory:配置BeanName,listener容器工厂,为KafkaListenerContainerFactory的实现,spring默认实现类ConcurrentKafkaListenerContainerFactory,用于生成MessageListenerContainer实例,ConcurrentKafkaListenerContainerFactory所对应的MessageListenerContainer实现为ConcurrentMessageListenerContainer
  • topics:需要监听的Topic,可监听多个。
  • concurrency:监听当前Topic所运行的线程数,会覆盖spring.kafka.listener.concurrency配置。当线程数多于Topic 分区数,那么将会有空闲线程存在。
  • topicPartitions:可配置更加详细的监听信息,监听某个Topic中的指定分区。同一topic的同一partition确定一个线程,与concurrency存在关系,当配置了topicPartitions且concurrency大于此部分确定的线程数,那么concurrency就不再起作用。
  • errorHandler:监听异常处理器,配置BeanName
  • groupId:消费组ID,默认为配置文件中配置spring.kafka.consumer.group-id

2、listener注册

2.1、KafkaListenerAnnotationBeanPostProcessor

    KafkaListener解析由KafkaListenerAnnotationBeanPostProcessor完成,这个类实现了BeanPostProcessor接口,这个接口会扫描所有的bean注册。
BeanPostProcessor.java

public interface BeanPostProcessor {
   /**
    * 在bean初始化之前执行
    */
   @Nullable
   default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }
   /**
    * 在bean初始化之后执行
    */
   @Nullable
   default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }
}

    KafkaListenerAnnotationBeanPostProcessor主要实现了postProcessAfterInitialization方法,在每个bean初始化完成之后再进行相应的处理操作,主要为检查bean是否有KafkaListener注解,如果存在,则会执行后续的注册listener的操作。
postProcessAfterInitialization实现:

public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
   // nonAnnotatedClasses是 类中没有KafkaListener注解标注的方法 的类集合
   // 此步主要处理一个类中无KafkaListener方法,且存在多实例,后续的实例便可不操作
   if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
      Class<?> targetClass = AopUtils.getTargetClass(bean);
      // 得到class上的KafkaListener集合
      // class上可使用KafkaListener和KafkaListeners注解,这里会将KafkaListeners解析成KafkaListener集合
      Collection<KafkaListener> classLevelListeners = findListenerAnnotations(targetClass);
      final boolean hasClassLevelListeners = classLevelListeners.size() > 0;
      final List<Method> multiMethods = new ArrayList<>();
      // 得到当前class下所有被KafkaListener和KafkaListeners注解修饰的method,并建立映射关系
      Map<Method, Set<KafkaListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
            (MethodIntrospector.MetadataLookup<Set<KafkaListener>>) method -> {
               Set<KafkaListener> listenerMethods = findListenerAnnotations(method);
               return (!listenerMethods.isEmpty() ? listenerMethods : null);
            });
      if (hasClassLevelListeners) {
         // 如果类找到KafkaListener,则在当前类中找到被 KafkaHandler 注解修饰的方法集合
         // 也就是如果类上被KafkaListener修饰,那么类方法要使用 KafkaHandler 来配合实现
         Set<Method> methodsWithHandler = MethodIntrospector.selectMethods(targetClass,
               (ReflectionUtils.MethodFilter) method ->
                     AnnotationUtils.findAnnotation(method, KafkaHandler.class) != null);
         multiMethods.addAll(methodsWithHandler);
      }
      if (annotatedMethods.isEmpty()) {
         // 如果当前类中没有被KafkaListener和KafkaListeners注解修饰的method,将class添加至nonAnnotatedClasses
         // 这样做是避免后续这样无解析意义的同class的不同实例的重复解析加载
         this.nonAnnotatedClasses.add(bean.getClass());
      }
      else {
         // 遍历annotatedMethods,根据映射关系执行processKafkaListener
         for (Map.Entry<Method, Set<KafkaListener>> entry : annotatedMethods.entrySet()) {
            Method method = entry.getKey();
            for (KafkaListener listener : entry.getValue()) {
               processKafkaListener(listener, method, bean, beanName);
            }
         }
      }
      if (hasClassLevelListeners) {
         // 处理类级别的classLevelListeners和multiMethods映射关系
         // processMultiMethodListeners方法内会在处理完成映射关系后,执行使用第一个参数是MethodKafkaListenerEndpoint的processKafkaListener方法重载,上述的processKafkaListener的内部也是使用此重载
         processMultiMethodListeners(classLevelListeners, multiMethods, bean, beanName);
      }
   }
   return bean;
}

processMultiMethodListeners实现:

private void processMultiMethodListeners(Collection<KafkaListener> classLevelListeners, List<Method> multiMethods,
      Object bean, String beanName) {
   List<Method> checkedMethods = new ArrayList<>();
   // 被 KafkaHandler 修饰为默认的方法
   Method defaultMethod = null;
   for (Method method : multiMethods) {
      Method checked = checkProxy(method, bean);
      KafkaHandler annotation = AnnotationUtils.findAnnotation(method, KafkaHandler.class);
      if (annotation != null && annotation.isDefault()) {
         defaultMethod = checked;
      }
      checkedMethods.add(checked);
   }
   // 遍历 classLevelListeners,实例化MultiMethodKafkaListenerEndpoint执行注册
   for (KafkaListener classLevelListener : classLevelListeners) {
      MultiMethodKafkaListenerEndpoint<K, V> endpoint =
            new MultiMethodKafkaListenerEndpoint<>(checkedMethods, defaultMethod, bean);
      processListener(endpoint, classLevelListener, bean, bean.getClass(), beanName);
   }
}

processKafkaListener -- postProcessAfterInitialization方法中的调用实现:

/**
 * 同 processMultiMethodListeners 中的遍历操作一致
 * 实例化MethodKafkaListenerEndpoint执行注册
 */
protected void processKafkaListener(KafkaListener kafkaListener, Method method, Object bean, String beanName) {
   Method methodToUse = checkProxy(method, bean);
   MethodKafkaListenerEndpoint<K, V> endpoint = new MethodKafkaListenerEndpoint<>();
   endpoint.setMethod(methodToUse);
   processListener(endpoint, kafkaListener, bean, methodToUse, beanName);
}

processKafkaListener -- 处理 MethodKafkaListenerEndpoin t执行注册实现:

protected void processListener(MethodKafkaListenerEndpoint<?, ?> endpoint, KafkaListener kafkaListener,
      Object bean, Object adminTarget, String beanName) {

   String beanRef = kafkaListener.beanRef();
   if (StringUtils.hasText(beanRef)) {
      this.listenerScope.addListener(beanRef, bean);
   }
   // 设置 endpoint 属性
   endpoint.setBean(bean);
   endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
   endpoint.setId(getEndpointId(kafkaListener));
   endpoint.setGroupId(getEndpointGroupId(kafkaListener, endpoint.getId()));
   endpoint.setTopicPartitions(resolveTopicPartitions(kafkaListener));
   endpoint.setTopics(resolveTopics(kafkaListener));
   endpoint.setTopicPattern(resolvePattern(kafkaListener));
   endpoint.setClientIdPrefix(resolveExpressionAsString(kafkaListener.clientIdPrefix(), "clientIdPrefix"));
   String group = kafkaListener.containerGroup();
   if (StringUtils.hasText(group)) {
      Object resolvedGroup = resolveExpression(group);
      if (resolvedGroup instanceof String) {
         endpoint.setGroup((String) resolvedGroup);
      }
   }
   String concurrency = kafkaListener.concurrency();
   if (StringUtils.hasText(concurrency)) {
      endpoint.setConcurrency(resolveExpressionAsInteger(concurrency, "concurrency"));
   }
   String autoStartup = kafkaListener.autoStartup();
   if (StringUtils.hasText(autoStartup)) {
      endpoint.setAutoStartup(resolveExpressionAsBoolean(autoStartup, "autoStartup"));
   }
   resolveKafkaProperties(endpoint, kafkaListener.properties());
   // listener 容器工厂,生产MessageListenerContainer消息监听容器
   KafkaListenerContainerFactory<?> factory = null;
   String containerFactoryBeanName = resolve(kafkaListener.containerFactory());
   if (StringUtils.hasText(containerFactoryBeanName)) {
      try {
         // 如果手动配置了 containerFactory ,则根据 beanName 从 beanFactory 获取对应实例
         factory = this.beanFactory.getBean(containerFactoryBeanName, KafkaListenerContainerFactory.class);
      }
      catch (NoSuchBeanDefinitionException ex) {
         throw new BeanInitializationException();
      }
   }

   endpoint.setBeanFactory(this.beanFactory);
   String errorHandlerBeanName = resolveExpressionAsString(kafkaListener.errorHandler(), "errorHandler");
   if (StringUtils.hasText(errorHandlerBeanName)) {
      endpoint.setErrorHandler(this.beanFactory.getBean(errorHandlerBeanName, KafkaListenerErrorHandler.class));
   }
   // 使用 registrar 注册 endpoit,register为KafkaListenerEndpointRegistrar实例
   this.registrar.registerEndpoint(endpoint, factory);
   if (StringUtils.hasText(beanRef)) {
      this.listenerScope.removeListener(beanRef);
   }
}

2.2、KafkaListenerEndpointRegistrar

    KafkaListenerEndpoint的注册由KafkaListenerEndpointRegistrar完成。2.1节的结尾this.registrar.registerEndpoint(endpoint, factory);是注册调用入口。
registerEndpoint实现:

public void registerEndpoint(KafkaListenerEndpoint endpoint, KafkaListenerContainerFactory<?> factory) {
   // 创建 KafkaListenerEndpoint 描述信息对象,并将此对象加入至 endpointDescriptors 中
   KafkaListenerEndpointDescriptor descriptor = new KafkaListenerEndpointDescriptor(endpoint, factory);
   synchronized (this.endpointDescriptors) {
      if (this.startImmediately) { // Register and start immediately
         this.endpointRegistry.registerListenerContainer(descriptor.endpoint,
               resolveContainerFactory(descriptor), true);
      }
      else {
         this.endpointDescriptors.add(descriptor);
      }
   }
}

    registerEndpoint执行之后,由KafkaListenerAnnotationBeanPostProcessor为入口的解析工作已经完成,但是我们发现registerEndpoint方法只是将descriptor 加入至endpointDescriptors,并没有做一些注册和listener container的创建。
    KafkaListenerEndpointRegistrar类中registerAllEndpoints方法来执行注册所有endpoint的操作。
registerAllEndpoints实现:

protected void registerAllEndpoints() {
   synchronized (this.endpointDescriptors) {
      // 遍历 endpointDescriptors,使用 KafkaListenerEndpointRegistry 的实例 endpointRegistry 执行listener container 的注册
      for (KafkaListenerEndpointDescriptor descriptor : this.endpointDescriptors) {
         this.endpointRegistry.registerListenerContainer(
               descriptor.endpoint, resolveContainerFactory(descriptor));
      }
      this.startImmediately = true;  // trigger immediate startup
   }
}

    registerAllEndpointsafterPropertiesSet调用。afterPropertiesSetKafkaListenerEndpointRegistrarInitializingBean接口的实现,实现了InitializingBean接口的bean会在所有属性都完成注入之后,由spring自动调用afterPropertiesSet方法。
    但是对于KafkaListenerEndpointRegistrar来说,spring并不会自动调用afterPropertiesSet方法,这是因为KafkaListenerEndpointRegistrar的实例并不是交给spring容器管理的,而是在KafkaListenerAnnotationBeanPostProcessor中new出来的,所以afterPropertiesSet需要手动调用执行。
    我们发现在KafkaListenerAnnotationBeanPostProcessor中的afterSingletonsInstantiated方法中调用了registrar.afterPropertiesSet();
afterSingletonsInstantiated实现:

public void afterSingletonsInstantiated() {
   this.registrar.setBeanFactory(this.beanFactory);
   
   if (this.beanFactory instanceof ListableBeanFactory) {
      Map<String, KafkaListenerConfigurer> instances =
            ((ListableBeanFactory) this.beanFactory).getBeansOfType(KafkaListenerConfigurer.class);
      for (KafkaListenerConfigurer configurer : instances.values()) {
         configurer.configureKafkaListeners(this.registrar);
      }
   }

   // registrar 设置 endpointRegistry
   // 在 KafkaListenerEndpointRegistrar 中的方法 registerAllEndpoints 内是使用 KafkaListenerEndpointRegistry 进行注册操作 
   if (this.registrar.getEndpointRegistry() == null) {
      if (this.endpointRegistry == null) {
         this.endpointRegistry = this.beanFactory.getBean(
               KafkaListenerConfigUtils.KAFKA_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME,
               KafkaListenerEndpointRegistry.class);
      }
      this.registrar.setEndpointRegistry(this.endpointRegistry);
   }

   if (this.defaultContainerFactoryBeanName != null) {
      this.registrar.setContainerFactoryBeanName(this.defaultContainerFactoryBeanName);
   }

   // MessageHandlerMethodFactory 用于创建 InvocableHandlerMethod 
   // InvocableHandlerMethod 是 listener 对应的 执行器,也就是 在拉取到数据之后,会调用 HandlerAdapter 的对应方法,执行消费的过程(执行KafkaListener注解修饰的方法)
   // InvocableHandlerMethod 的执行过程是借助 HandlerAdapter 实现
   MessageHandlerMethodFactory handlerMethodFactory = this.registrar.getMessageHandlerMethodFactory();
   if (handlerMethodFactory != null) {
      this.messageHandlerMethodFactory.setHandlerMethodFactory(handlerMethodFactory);
   }
   else {
      addFormatters(this.messageHandlerMethodFactory.defaultFormattingConversionService);
   }

   // 调用 registrar.afterPropertiesSet() 执行注册过程
   this.registrar.afterPropertiesSet();
}

    afterSingletonsInstantiated是对接口SmartInitializingSingleton的实现。
    SmartInitializingSingleton会在spring容器加载过所有bean之后自动调用,前提是实现这个接口的bean交予spring管理,KafkaListenerAnnotationBeanPostProcessor满足此条件,也是说在spring所有bean加载完成之后会自动调用KafkaListenerEndpointRegistrarregisterAllEndpoints
    KafkaListenerEndpointRegistrarregisterAllEndpoints是使用KafkaListenerEndpointRegistryregisterListenerContainer方法完成注册。

2.3、KafkaListenerEndpointRegistry

registerListenerContainer实现:

public void registerListenerContainer(KafkaListenerEndpoint endpoint, KafkaListenerContainerFactory<?> factory) {
   registerListenerContainer(endpoint, factory, false);
}

public void registerListenerContainer(KafkaListenerEndpoint endpoint, KafkaListenerContainerFactory<?> factory,
      boolean startImmediately) {
   String id = endpoint.getId();
   synchronized (this.listenerContainers) {
      // 根据 endpoint 创建 message listener 容器
      // message listener 容器 用于承载 消息的监听和消费 
      // 这里创建的是 ConcurrentMessageListenerContainer 实例,是因为createListenerContainer中使用ConcurrentKafkaListenerContainerFactory进行创建container 
      MessageListenerContainer container = createListenerContainer(endpoint, factory);
      // 保存 container  ,用于后续的启动
      this.listenerContainers.put(id, container);
      if (StringUtils.hasText(endpoint.getGroup()) && this.applicationContext != null) {
         List<MessageListenerContainer> containerGroup;
         if (this.applicationContext.containsBean(endpoint.getGroup())) {
            containerGroup = this.applicationContext.getBean(endpoint.getGroup(), List.class);
         }
         else {
            containerGroup = new ArrayList<MessageListenerContainer>();
            this.applicationContext.getBeanFactory().registerSingleton(endpoint.getGroup(), containerGroup);
         }
         containerGroup.add(container);
      }
      // 查看调用此方法的参数,startImmediately为 false
      // 所以此步并不会执行 start
      if (startImmediately) {
         startIfNecessary(container);
      }
   }
}

查看上述代码,registerListenerContainer在注册完成后并不会启动容器的内容,实际的启动交给 SmartLifecycle接口处理,这里KafkaListenerEndpointRegistry类实现了此接口,会在项目所有bean加载和初始化完毕执行start方法。
start实现:

public void start() {
   // 遍历容器,执行start
   for (MessageListenerContainer listenerContainer : getListenerContainers()) {
      startIfNecessary(listenerContainer);
   }
   this.running = true;
}
private void startIfNecessary(MessageListenerContainer listenerContainer) {
   if (this.contextRefreshed || listenerContainer.isAutoStartup()) {
      // 执行 listener 容器的 start
      // 这里的 listenerContainer 是 ConcurrentMessageListenerContainer 实例
      listenerContainer.start();
   }
}

下面查看 ConcurrentMessageListenerContainerstart方法。

2.4、ConcurrentMessageListenerContainer

start实现:

// 此方法在父类AbstractMessageListenerContainer中
public final void start() {
   checkGroupId();
   synchronized (this.lifecycleMonitor) {
      if (!isRunning()) {
         doStart();
      }
   }
}

protected void doStart() {
   if (!isRunning()) {
      checkTopics();
      ContainerProperties containerProperties = getContainerProperties();
      // 这里的 topicPartitions 为注解配置
      TopicPartitionOffset[] topicPartitions = containerProperties.getTopicPartitionsToAssign();
      // 如果配置了 topicPartitions ,那么为防止出现空闲线程,做以下的处理
      // 如果说 topic 实际的 partition 数 小于处理之后的 this.concurrency,还是会出现空闲线程
      if (topicPartitions != null && this.concurrency > topicPartitions.length) {
         this.concurrency = topicPartitions.length;
      }
      setRunning(true);
      // 循环创建 concurrency 个 KafkaMessageListenerContainer 用于处理 当前kafka的数据
      // KafkaMessageListenerContainer 可以看作一个线程(实际线程为内部类ListenerConsumer实现)
      for (int i = 0; i < this.concurrency; i++) {
         KafkaMessageListenerContainer<K, V> container;
         if (topicPartitions == null) {
            container = new KafkaMessageListenerContainer<>(this, this.consumerFactory, containerProperties);
         }
         else {
            container = new KafkaMessageListenerContainer<>(this, this.consumerFactory,
                  containerProperties, partitionSubset(containerProperties, i));
         }
         String beanName = getBeanName();
         container.setBeanName((beanName != null ? beanName : "consumer") + "-" + i);
         container.setApplicationContext(getApplicationContext());
         if (getApplicationEventPublisher() != null) {
            container.setApplicationEventPublisher(getApplicationEventPublisher());
         }
         container.setClientIdSuffix("-" + i);
         container.setGenericErrorHandler(getGenericErrorHandler());
         container.setAfterRollbackProcessor(getAfterRollbackProcessor());
         container.setRecordInterceptor(getRecordInterceptor());
         container.setEmergencyStop(() -> {
            stop(() -> {});
            publishContainerStoppedEvent();
         });
         if (isPaused()) {
            container.pause();
         }
         // 启动 KafkaMessageListenerContainer 容器
         container.start();
         this.containers.add(container);
      }
   }
}

下面看 KafkaMessageListenerContainerstart实现

2.5、KafkaMessageListenerContainer

start实现:

// 此方法在父类AbstractMessageListenerContainer中
public final void start() {
   checkGroupId();
   synchronized (this.lifecycleMonitor) {
      if (!isRunning()) {
         Assert.state(this.containerProperties.getMessageListener() instanceof GenericMessageListener,
               () -> "A " + GenericMessageListener.class.getName() + " implementation must be provided");
         doStart();
      }
   }
}

// doStart 执行过后,整个 listener 的注册动作完成
protected void doStart() {
   if (isRunning()) {
      return;
   }
   if (this.clientIdSuffix == null) { // stand-alone container
      checkTopics();
   }
   ContainerProperties containerProperties = getContainerProperties();
   checkAckMode(containerProperties);

   Object messageListener = containerProperties.getMessageListener();
   // 设置 TaskExecutor 用于启动 ListenerConsumer ,ListenerConsumer是实际拉取和消费数据的执行
   // ListenerConsumer 的执行为异步
   if (containerProperties.getConsumerTaskExecutor() == null) {
      SimpleAsyncTaskExecutor consumerExecutor = new SimpleAsyncTaskExecutor(
            (getBeanName() == null ? "" : getBeanName()) + "-C-");
      containerProperties.setConsumerTaskExecutor(consumerExecutor);
   }
   GenericMessageListener<?> listener = (GenericMessageListener<?>) messageListener;
   ListenerType listenerType = determineListenerType(listener);
   this.listenerConsumer = new ListenerConsumer(listener, listenerType);
   setRunning(true);
   this.startLatch = new CountDownLatch(1);
   // 使用 SimpleAsyncTaskExecutor 开启异步线程 执行 this.listenerConsumer,处理数据的拉取和消费过程
   this.listenerConsumerFuture = containerProperties
         .getConsumerTaskExecutor()
         .submitListenable(this.listenerConsumer);
         
   try {
      if (!this.startLatch.await(containerProperties.getConsumerStartTimout().toMillis(), TimeUnit.MILLISECONDS)) {
         publishConsumerFailedToStart();
      }
   }catch (@SuppressWarnings(UNUSED) InterruptedException e) {
      Thread.currentThread().interrupt();
   }
}

3、总结

注册 listener 过程:

  1. 使用 KafkaListenerAnnotationBeanPostProcessor 扫描所有bean,判断是否含有 KafkaListener 注解。
  2. 将扫描到 bean 和 method 封装,保存至 KafkaListenerEndpointRegistrar 的 endpointDescriptors。
  3. 借助 KafkaListenerAnnotationBeanPostProcessor 实现 InitializingBean 接口,对 endpointDescriptors 内的 endpoint 执行 KafkaListenerEndpointRegistry 的 registerListenerContainer 方法进行注册。
  4. KafkaListenerEndpointRegistry 的 registerListenerContainer 方法将 endpoint 封装 为 ConcurrentMessageListenerContainer存放至 containerGroup 中。
  5. 借助 KafkaListenerEndpointRegistry 实现 SmartLifecycle 接口,完成对 containerGroup 中所有 ConcurrentMessageListenerContainer 的 start。
  6. ConcurrentMessageListenerContainer 的 start 完成对当前 KafkaListener 所需实际处理对象 KafkaMessageListenerContainer 的实例化,这里的 KafkaMessageListenerContainer 可以有多个,由配置决定。每个 KafkaMessageListenerContainer 会开一个线程处理消息的拉取和消费。当前 KafkaListener 对应的 concurrency 大于 topics 的 partition,会出现空闲线程,concurrency 大于 Kafka Listener 配置的 topicPartitions ,会使用 topicPartitions 的大小确认实例化的 KafkaMessageListenerContainer 个数。
  7. KafkaMessageListenerContainer 的 start 完成对 ListenerConsumer 实例化和启动。ListenerConsumer 是实际处理消息的类,为异步处理。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Kafka-Consumer 源码解析 -- listener 注册和启动 的相关文章

  • 使类只能从特定类实例化

    假设我有 3 节课class1 class2 and class3 我怎样才能拥有它class1只能通过实例化class2 class1 object new class1 但不是 class3 或任何其他类 我认为它应该与修饰符一起使用
  • 如何作为应用程序发布到页面?

    所以 我有一个应用程序 Facebook 应用程序实体 并且我有一个页面 我想使用应用程序通过java代码 通过restfb或任何其他建议 发布到页面 看起来我错过了页面授予应用程序发布权限的阶段 不知道该怎么做 谢谢你们 乌里 您只能 作
  • Java 卡布局。多张卡中的一个组件

    一个组件 例如JLabel 在多张卡中使用CardLayout 目前看来该组件仅出现在它添加到的最后一张卡上 如果有办法做到这一点 我应该吗 这是不好的做法吗 或者有其他选择吗 你是对的 它只出现在 添加到的最后一张卡 中 但这与CardL
  • 使用 Spring 控制器处理错误 404

    I use ExceptionHandler处理我的网络应用程序抛出的异常 在我的例子中我的应用程序返回JSON回应HTTP status用于对客户端的错误响应 但是 我正在尝试弄清楚如何处理error 404返回与处理的类似的 JSON
  • 即使在轴上进行自动量程调整,我也可以保留积分刻度线吗?

    我 偷 了一些代码here http fxexperience com 2012 01 curve fitting and styling areachart 拥有一个AreaChart我在 FXML 中使用了 平滑线条 它的工作原理如下
  • 记录共享和映射的诊断上下文

    据我所知 其他人做了什么来解决 Commons Logging 项目 针对 NET 和 Java 不支持映射或嵌套诊断上下文这一事实 执行摘要 我们选择直接使用实现者日志框架 在我们的例子中为 log4j 长答案 您是否需要一个抽象日志框架
  • JUnit5 平台启动器 API - 如果没有至少一个测试引擎,则无法创建启动器

    我正在尝试升级我们的自动化测试套件的测试能力以接受 JUnit5 测试并遵循JUnit 平台启动器 API 说明 https junit org junit5 docs current user guide launcher api我收到错
  • 这个等待通知线程语义的真正目的是什么?

    我刚刚遇到一些代码 它使用等待通知构造通过其其他成员方法与类中定义的线程进行通信 有趣的是 获取锁后 同步范围内的所有线程都会在同一锁上进行定时等待 请参见下面的代码片段 随后 在非同步作用域中 线程执行其关键函数 即 做一些有用的事情1
  • 无法从资源加载图片

    So I am trying to load a image file from a resource so that when I export my application into a jar file it could be use
  • 无法在 Java 中输出正确的哈希值。怎么了?

    在我的 Android 应用程序中 我有一个 SHA256 哈希值 我必须使用 RIPEMD160 消息摘要算法进一步对其进行哈希值 我可以输出任何字符串的正确 sha256 和ripemd160 哈希值 但是当我尝试使用ripemd160
  • 确定序列化对象的类型

    我需要通过套接字发送消息 从用户到引擎的请求 以及从引擎到用户的响应 所以流程本质上是 serialized request Server lt network gt Client serialized response request r
  • 插入时的 iBatis 判别器

    我有一个抽象类Example以及与之相伴的具体子类 我使用鉴别器来提取数据out数据库的 像这样
  • 用于层次结构树角色的 Spring Security / Java EE 解决方案

    我知道 Spring Security 非常适合标准角色和基于权限的授权 我不确定的是这种情况 系统中管理着 10 000 名员工 员工被组织成组织结构图 跨部门的谁向谁报告的树 其中一些员工是用户 这些用户仅被允许访问其职责范围内的员工
  • 为什么无法从 WEB-INF 文件夹内加载 POSModel 文件?

    我在我的 Web 项目中使用 Spring MVC 我将模型文件放在 WEB INF 目录中 String taggerModelPath WEB INF lib en pos maxent bin String chunkerModelP
  • BadPaddingException:无效的密文

    我需要一些帮助 因为这是我第一次编写加密代码 加密代码似乎工作正常 但解密会引发错误 我得到的错误是 de flexiprovider api exceptions BadPaddingException 无效的密文 in the 解密函数
  • 如何使用Gson仅从Json反序列化某些特定字段?

    我有以下 JSON 字符串 channel bvmt initValues data value instrumentIds TN0007250012 TN0007500010 instruments mnemonic ADWYA marc
  • Android UnityPlayerActivity 操作栏

    我正在构建一个 Android 应用程序 其中包含 Unity 3d 交互体验 我已将 Unity 项目导入 Android Studio 但启动时该 Activity 是全屏的 并且不显示 Android 操作栏 我怎样才能做到这一点 整
  • 如何从 JavaFX 中的另一个控制器类访问 UI 元素?

    我有一个使用 NetBeans 8 编写的 JavaFX Java 8 应用程序 没有SceneBuilder 我的应用程序有一个主窗口 该窗口有自己的 FXML 文件 primary fxml 和自己的控制器类 FXMLPrimaryCo
  • Java/MongoDB 按日期查询

    我将一个值作为 java util Date 存储在我的集合中 但是当我查询以获取两个特定日期之间的值时 我最终得到的值超出了范围 这是我的代码 插入 BasicDBObject object new BasicDBObject objec
  • 如何建立与 FileZilla Server 1.2.0 的 FTPS 数据连接

    使用 Apache commons net 的 Java FTPSClient 进行会话恢复是一个已知问题 会话恢复是 FTPS 服务器数据连接所需的一项安全功能 Apache FTPSClient 不支持会话恢复 并且 JDK API 使

随机推荐

  • .net 平台下的数学库math.net(一)

    Math NET的目标是为提供一款自身包含清晰框架的符号运算和数学运算 科学运算 它是C 开发的开源类库 Math NET含了一个支持线性代数的解析器 分析复杂微分 解方程等等功能 这个项目大部分采用的是MIT X11开源软件协议 目前该组
  • ConstraintLayout实用特性

    转载自赵彦军的博客 前言 在2016年的Google I O大会上 Google 发布了Android Studio 2 2预览版 同时也发布了Android 新的布局方案 ConstraintLayout 但是最近的一年也没有大规模的使用
  • 【ABviewer从零开始教学查看器篇②】关于打开文件的快捷方式

    ABViewer是一款高质量 高效率 低成本的多功能设计及工程文档管理工具 能为您提供全面的专业的浏览及编辑功能 同时支持30多种光栅和矢量图形格式 在小编看来 ABViewer是一款非常简单且实用的CAD文档查看与编辑器 对于使用小白可能
  • 判断是否是数组

    整理了一些 留待自己复习用 1 instanceof var a name fangxiaoming age 19 var b 1 2 3 4 console log a instanceof Array false console log
  • 【翻译】我们建立了一个.NET操作员SDK(所以您不必这样做)。

    我们用C 语言构建了一个 NET操作者SDK 因此您可以用C 或任何 NET语言构建自己的Kubernetes操作者 当然也 有Go Operator SDK 还有我们的Java Operator SDK 那么为什么不为 NET社区提供一些
  • 用 Visual Studio 2019 编译 FFmpeg 简单教程

    需要的东西 Visual Studio 2019 这个自行解决吧 本人用的是社区版 MSYS 环境 去 https www msys2 org 下载 本人下载的是 msys2 x86 64 20210725 exe yasm exe 去 h
  • Security in IP-Based IoT Node and Device Authentication

    Abstract The IoT security aims for enabling IoT data protection in various interconnected nodes These frameworks require
  • 触屏fling/scroll/drag的区别及其详细过程

    原文地址 Android 触屏fling scroll drag的区别及其详细过程 作者 飘锦丹枫 Google了一下 终于搞清了touch screen下的几种操作模式 对应的是事件 对于一个view 常用的操作有点击 click 和长按
  • 原生js实现ajax请求(带请求头header)和数据传参过程代码

    一 Ajax 概述 Ajax 是 Asynchronous Javascript And XML 的简写 Ajax是一门技术 并不是一门语言 使用XHTML CSS来标准化呈现 使用XML和XSLT进行数据交换及相关操作 使用XMLHttp
  • java程序输出中文乱码解决方案

    标题java程序输出中文乱码解决方案 乱码如下 你好 在一些Java程序中我们输入的中文在输出时会出现乱码的情况 一下是解决方案 1 在编译xx java文件时使用javac encoding utf 8 xx java语句进行编译可以解决
  • sed 过滤字符文本 (一行行的)

    前面写过用sed对整个文件过滤的 代码很简单 现在这个是能够取出其中的一行行来过滤的 为了获取更多的相关信息 注意列表中的空格先变为 然后再变回来 不然会出错 bin sh i grep chenbing my c temp sed s g
  • 关于代码评审

    总结于 代码之丑 专栏 郑晔 为何要做代码评审 代码评审 也就是很多人熟悉的 Code Review Wikipedia 上定义是这样的 代码评审 是指对计算机源代码系统化地审查 常用软件同行评审的方式进行 其目的是在找出及修正在软件开发初
  • c#处理json数据

    这篇文章讲的很详细 亲测可行 此外我在添加一点注意事项 1 json转C 实体类 之前用了一个转的不行 害的我半天弄不出来 后面找到一个 JSON转C 实体类 BeJSON com 这个转出来的很不错 一下子就成功了 2 如果想在没有环境的
  • Chrome插件消息传递实例

    首先吐槽 360极速浏览器应用开发平台 的开发文档 在消息传递 http open chrome 360 cn extension dev messaging html 一节中 翻译极其低劣 Sending a request from t
  • SpringBoot通过自定义字段注解以及反射获取对象

    在Java的开发过程中 注解的应用场景是非常广泛的 Java也提供了很多内置的注解 比如 Override Deprecated SuppressWarnings等等 之前也写过一篇注解相关的文章 SpringBoot自定义注解 AOP以及
  • mysql机制_Mysql 重连机制<转载>

    连续两天早上发现服务上不去了 mysql server has gone away 然后又通过mysql客户端连了一下mysql 没问题 看来是程序写错了 我的connection没有重连机制 查了一下相关的资料 django是每次操作都重
  • MA35D1记录1-源码编译

    上面是我的微信和QQ群 欢迎新朋友的加入 今天年假结束 突然发现新唐即将发布MA35D1 去官网和git仓库查了下 新唐趁我放假又偷偷更新了一些资料 之前发布的是yocto的环境 那个我倒也用 但时不时要翻墙 对国内用户来说 多少有点恶心人
  • linux(centos7)下建立web页面

    我打算从centos7配置IP开始记录 就是记录一下我的搭建过程 1 在VMware虚拟机选择centos7镜像安装完毕后 设置用户 密码 发现进入的是图形化界面 于是通过CTRL ALT F3进入命令行界面 现在用的VMware版本导致我
  • 【搞一点AUTOSAR】基于TC397的MACL-ADC配置解读(使用EB)

    搞一点AUTOSAR 基于TC397的MACL ADC配置解读 使用EB 文章目录 搞一点AUTOSAR 基于TC397的MACL ADC配置解读 使用EB 前言 一 ADC模块介绍 1 ADC模块的功能 2 模块相关概念首字母缩略介绍 二
  • Kafka-Consumer 源码解析 -- listener 注册和启动

    Kafka Consumer 源码解析 consumer 启动 和 listener 注册和启动 前言 1 KafkaListener注解说明 2 listener注册 2 1 KafkaListenerAnnotationBeanPost