【问题标题】:Spring Boot custom annotation design not workingSpring Boot自定义注释设计不起作用
【发布时间】:2021-12-25 05:02:26
【问题描述】:

我正在关注 PacktPublishing 的教程,其中在示例中使用了一些注释, 但代码是 2018 年的,可能有一些变化。

Spring 在创建 bean 时无法识别 Annotation。

具体来说,这是一个在本地对我不起作用的注释设计: link

一些重要的代码sn-ps是:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ChannelHandler {

  /**
   * Channel patter, alias of value()
   */
  String pattern() default "";

  /**
   * The channel pattern that the handler will be mapped to by {@link WebSocketRequestDispatcher}
   * using Spring's {@link org.springframework.util.AntPathMatcher}
   */
  String value() default "";

}

@ChannelHandler("/board/*")
public class BoardChannelHandler {

  private static final Logger log = LoggerFactory.getLogger(BoardChannelHandler.class);

  @Action("subscribe")
  public void subscribe(RealTimeSession session, @ChannelValue String channel) {
    log.debug("RealTimeSession[{}] Subscribe to channel `{}`", session.id(), channel);
    SubscriptionHub.subscribe(session, channel);
  }

  @Action("unsubscribe")
  public void unsubscribe(RealTimeSession session, @ChannelValue String channel) {
    log.debug("RealTimeSession[{}] Unsubscribe from channel `{}`", session.id(), channel);
    SubscriptionHub.unsubscribe(session, channel);
  }
}

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Action {

  /**
   * The action pattern. It needs to be an exact match.
   * <p>For example, "subscribe"
   */
  String value() default "";
}

你能看出这里有什么问题吗?新版本是否缺少其他注释 春天?

更新 - 添加其他必要的代码。

public class ChannelHandlerInvoker {

  private static final Logger log = LoggerFactory.getLogger(ChannelHandlerInvoker.class);

  private static final AntPathMatcher antPathMatcher = new AntPathMatcher();

  private String channelPattern;
  private Object handler;
  // Key is the action, value is the method to handle that action
  private final Map<String, Method> actionMethods = new HashMap<>();

  public ChannelHandlerInvoker(Object handler) {
    Assert.notNull(handler, "Parameter `handler` must not be null");

    Class<?> handlerClass = handler.getClass();
    ChannelHandler handlerAnnotation = handlerClass.getAnnotation(ChannelHandler.class);
    Assert.notNull(handlerAnnotation, "Parameter `handler` must have annotation @ChannelHandler");

    Method[] methods = handlerClass.getMethods();
    for (Method method : methods) {
      Action actionAnnotation = method.getAnnotation(Action.class);
      if (actionAnnotation == null) {
        continue;
      }

      String action = actionAnnotation.value();
      actionMethods.put(action, method);
      log.debug("Mapped action `{}` in channel handler `{}#{}`", action, handlerClass.getName(), method);
    }

    this.channelPattern = ChannelHandlers.getPattern(handlerAnnotation);
    this.handler = handler;
  }

  public boolean supports(String action) {
    return actionMethods.containsKey(action);
  }

  public void handle(IncomingMessage incomingMessage, RealTimeSession session) {
    Assert.isTrue(antPathMatcher.match(channelPattern, incomingMessage.getChannel()), "Channel of the handler must match");
    Method actionMethod = actionMethods.get(incomingMessage.getAction());
    Assert.notNull(actionMethod, "Action method for `" + incomingMessage.getAction() + "` must exist");

    // Find all required parameters
    Class<?>[] parameterTypes = actionMethod.getParameterTypes();
    // All the annotations for each parameter
    Annotation[][] allParameterAnnotations = actionMethod.getParameterAnnotations();
    // The arguments that will be passed to the action method
    Object[] args = new Object[parameterTypes.length];

    try {
      // Populate arguments
      for (int i = 0; i < parameterTypes.length; i++) {
        Class<?> parameterType = parameterTypes[i];
        Annotation[] parameterAnnotations = allParameterAnnotations[i];

        // No annotation applied on this parameter
        if (parameterAnnotations.length == 0) {
          if (parameterType.isInstance(session)) {
            args[i] = session;
          } else {
            args[i] = null;
          }
          continue;
        }

        // Only use the first annotation applied on the parameter
        Annotation parameterAnnotation = parameterAnnotations[0];
        if (parameterAnnotation instanceof Payload) {
          Object arg = JsonUtils.toObject(incomingMessage.getPayload(), parameterType);
          if (arg == null) {
            throw new IllegalArgumentException("Unable to instantiate parameter of type `" +
              parameterType.getName() + "`.");
          }
          args[i] = arg;
        } else if (parameterAnnotation instanceof ChannelValue) {
          args[i] = incomingMessage.getChannel();
        }
      }

      actionMethod.invoke(handler, args);
    } catch (Exception e) {
      String error = "Failed to invoker action method `" + incomingMessage.getAction() +
        "` at channel `" + incomingMessage.getChannel() + "` ";
      log.error(error, e);
      session.error(error);
    }
  }
}

@Component
public class ChannelHandlerResolver {

  private static final Logger log = LoggerFactory.getLogger(ChannelHandlerResolver.class);

  private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
  // The key is the channel ant-like path pattern, value is the corresponding invoker
  private final Map<String, ChannelHandlerInvoker> invokers = new HashMap<>();

  private ApplicationContext applicationContext;

  public ChannelHandlerResolver(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
    this.bootstrap();
  }

  public ChannelHandlerInvoker findInvoker(IncomingMessage incomingMessage) {
    ChannelHandlerInvoker invoker = null;
    Set<String> pathPatterns = invokers.keySet();
    for (String pathPattern : pathPatterns) {
      if (antPathMatcher.match(pathPattern, incomingMessage.getChannel())) {
        invoker = invokers.get(pathPattern);
      }
    }
    if (invoker == null) {
      return null;
    }
    return invoker.supports(incomingMessage.getAction()) ? invoker : null;
  }

  private void bootstrap() {
    log.info("Bootstrapping channel handler resolver");

    Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(ChannelHandler.class);
    for (String handlerName : handlers.keySet()) {
      Object handler = handlers.get(handlerName);
      Class<?> handlerClass = handler.getClass();

      ChannelHandler handlerAnnotation = handlerClass.getAnnotation(ChannelHandler.class);
      String channelPattern = ChannelHandlers.getPattern(handlerAnnotation);
      if (invokers.containsKey(channelPattern)) {
        throw new IllegalStateException("Duplicated handlers found for chanel pattern `" + channelPattern + "`.");
      }
      invokers.put(channelPattern, new ChannelHandlerInvoker(handler));
      log.debug("Mapped channel `{}` to channel handler `{}`", channelPattern, handlerClass.getName());
    }
  }
}


<!-- begin snippet: js hide: false console: true babel: false -->

更新 2

我已经设法通过添加@Inherited 注释和使用 AnnotationUtils.findAnnotation() 来使 ChannelHandler 和 Action 注释工作,如果注释不直接存在于给定方法本身上,它会遍历其超级方法。

但是,我还没有设法访问类型参数(ChannelValue)的自定义注释值

这里,Annotation[][] allParameterAnnotations = actionMethod.getParameterAnnotations();

返回空值。

更新 3 -> 已解决

只需将 @Aspect 注释添加到您的 ChannelHandler 实现(例如 “BoardChannelHandler”)。

【问题讨论】:

  • 注释在什么方面不起作用?就 Spring 而言,任何带有 @ChannelHandler 注释的内容都只是 @Component。它会被组件扫描拾取并变成一个bean,但仅此而已。 @Action 对 Spring 没有任何意义。
  • ChannelHandler handlerAnnotation = handlerClass.getAnnotation(ChannelHandler.class); github.com/PacktPublishing/… 返回 NullpointerException "无法实例化 ChannelHandlerResolver 构造函数抛出异常;嵌套异常是 java.lang.NullPointerException"
  • 你说“@Action 对 Spring 没有任何意义”是什么意思?
  • 这是一个Spring一无所知的自定义注解。此外,您的问题中没有任何代码可以对 @Action 注释执行任何操作。您可能已链接到 GitHub 存储库,但如果问题本身包含重现问题的所有必要部分,您更有可能获得所需的答案。
  • 哦,我明白你的意思了。我已经更新了我的问题,包括使用 websocket 接收的 Action 方法的解析器和调用器类。

标签: spring-boot spring-annotations


【解决方案1】:

看起来像 bootstrap() 方法,它通过所有 @ChannelHandler 注释的 bean 执行得太早 - 尝试调试它以检查它是否在此阶段检测到任何 bean。

如果不尝试在 Spring 上下文准备好后调用 bootstrap()(例如监听 ContextRefreshedEvent

【讨论】:

  • 没错,你成功了。我试过了,但它遇到了同样的错误。但我不确定我是否做得对:我已经设置 ChannelHandlerResolver 来实现 ApplicationListener 这是我覆盖的方法`@Override public void onApplicationEvent(ContextRefreshedEvent event) { bootstrap(); }`
  • 我结束了进一步的调试,触发了 ContexRefreshedEvent,调用了 bootstrap() 方法,但是 handlerAnnotation 中的 AnnotationData 和 Annotation 为空,所以同样抛出了 nullpointerException。由于某种原因,调用 applicationContext.getBeansWithAnnotation(ChannelHandler.class) 时无法识别注释
  • 已解决。处理程序实现中缺少@Aspect。将更新问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-03
  • 1970-01-01
  • 1970-01-01
  • 2019-09-18
  • 2019-12-18
  • 2017-05-22
  • 1970-01-01
相关资源
最近更新 更多