Spring 引导自定义注释设计不起作用

Spring Boot custom annotation design not working

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

Spring创建bean时不识别Annotation

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

一些重要的代码片段是:

@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 "";
}

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

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

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();

returns 空值。

更新 3 -> 已解决

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

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

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