spring - 如何在 Spring 中注入(inject)基于类型安全限定符的 bean 类型映射?

标签 spring spring-boot

请参阅下面的示例,我正在尝试获取 Map我的TypedService bean,但如果键是 Type,我会更喜欢TypeSafeQualifier 中指定的枚举值而不是不安全的 String “服务名称”。

package org.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Service;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.Map;

import static org.test.Application.Type.ONE;
import static org.test.Application.Type.TWO;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@SpringBootApplication
public class Application {
  @Autowired
  Map<String, TypedService> works;

  @Autowired
  Map<Type, TypedService> fails;

  public static void main(String [] args) {
    SpringApplication.run(Application.class, args);
  }

  public enum Type {
    ONE,
    TWO
  }

  @Target({TYPE, METHOD, FIELD, CONSTRUCTOR})
  @Retention(RUNTIME)
  @Qualifier
  public @interface TypeSafeQualifier {
    Type value();
  }

  public interface TypedService {
    void startSignup();
    void activate();
  }

  @Service
  @TypeSafeQualifier(ONE)
  public class TypeOneService implements TypedService {

    @Override
    public void startSignup() {
    }

    @Override
    public void activate() {
    }
  }

  @Service
  @TypeSafeQualifier(TWO)
  public class TypeTwoService implements TypedService {

    @Override
    public void startSignup() {
    }

    @Override
    public void activate() {
    }
  }
}

SpringBoot版本:springBootVersion=1.5.3.RELEASE

最佳答案

Spring 提供了一种特殊的方法来处理这种类型的注入(inject):AutowireCandidateResolver .

在您的情况下,代码可能是:

package org.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.LinkedHashMap;
import java.util.Map;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@SpringBootApplication
public class Application {

  @Autowired
  Map<String, TypedService> works;

  @Autowired
  Map<Type, TypedService> fails;

  @PostConstruct
  private void init() {
    System.out.println(fails);
  }

  public static void main(String[] args) {
    final SpringApplication application = new SpringApplication(Application.class);
    application.addInitializers(context -> {
      context.addBeanFactoryPostProcessor(beanFactory -> {
        final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;
        dlbf.setAutowireCandidateResolver(new MyAutowireCandidateResolver(dlbf));
      });
    });
    application.run(args);
  }

  @QualifierValue(TypeSafeQualifier.class)
  public enum Type {
    ONE,
    TWO
  }

  @Target({TYPE, METHOD, FIELD, CONSTRUCTOR})
  @Retention(RUNTIME)
  @Qualifier
  public @interface TypeSafeQualifier {
    Type value();
  }

  public interface TypedService {
    void startSignup();
    void activate();
  }

  @Service
  @TypeSafeQualifier(Type.ONE)
  public class TypeOneService implements TypedService {

    @Override
    public void startSignup() {
    }

    @Override
    public void activate() {
    }
  }

  @Target({TYPE})
  @Retention(RUNTIME)
  public @interface QualifierValue {
    Class<? extends Annotation> value();
  }

  @Service
  @TypeSafeQualifier(Type.TWO)
  public class TypeTwoService implements TypedService {

    @Override
    public void startSignup() {
    }

    @Override
    public void activate() {
    }
  }

  private static class MyAutowireCandidateResolver extends ContextAnnotationAutowireCandidateResolver {

    private final DefaultListableBeanFactory beanFactory;

    private MyAutowireCandidateResolver(DefaultListableBeanFactory beanFactory) {
      this.beanFactory = beanFactory;
    }

    @Override
    public Object getSuggestedValue(DependencyDescriptor descriptor) {
      final Object result = super.getSuggestedValue(descriptor);
      if (result != null) {
        return result;
      }

      if (descriptor.getDependencyType() != Map.class) {
        return null;
      }

      final ResolvableType dependencyGenericType = descriptor.getResolvableType().asMap();
      final ResolvableType[] typeParams = dependencyGenericType.getGenerics();

      final QualifierValue qualifierValue = typeParams[0].getRawClass().getAnnotation(QualifierValue.class);
      if (qualifierValue == null) {
        return null;
      }

      final String[] candidateBeanNames = beanFactory.getBeanNamesForType(typeParams[1]);
      final LinkedHashMap<Object, Object> injectedMap = new LinkedHashMap<>(candidateBeanNames.length);

      for (final String candidateBeanName : candidateBeanNames) {
        final Annotation annotation = beanFactory.findAnnotationOnBean(candidateBeanName, qualifierValue.value());

        if (annotation == null) {
          continue;
        }

        final Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation, false);
        final Object value = annotationAttributes.get("value");

        if (value == null || value.getClass() != typeParams[0].getRawClass()) {
          continue;
        }

        injectedMap.put(value, beanFactory.getBean(candidateBeanName));
      }

      return injectedMap;
    }
  }
}

首先,我们添加 TypeQualifierValue 注释以使 Spring 知 Prop 有给定类型值的限定符。

二是在main方法中自定义SpringApplication:我们使用BeanFactoryPostProcessor来设置一个自定义的AutowireCandidateResolver。

最后一步:我们编写 MyAutowireCandidateResolver 扩展 ContextAnnotationAutowireCandidateResolver (委托(delegate)而不是继承适用,它甚至更好一点,因为有一天 Spring 可以默认迁移到 `YetAnotherAutowireCandidateResolver')。

这里的关键部分是被覆盖的 getSuggestedValue方法:在这里我们可以自定义注入(inject)逻辑,考虑依赖的通用类型(字段,方法参数)并应用一些getBean... - 来自 BeanFactory 的类似方法,带有一些 Spring 的魔力 AnnotationUtils类(class)。

关于spring - 如何在 Spring 中注入(inject)基于类型安全限定符的 bean 类型映射?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45865369/

相关文章:

java - Oauth 2 spring RestTemplate 使用刷新 token 登录

java - 如何以简单的方式创建 Spring 配置

Java Spring Boot Actuator Metrics系统负载平均值返回-1

spring-boot-devtools 不会在多模块 maven 项目中重新加载依赖模块

java - Spring AOP代理 session bean在 session 超时后保留,仅在容器重启后死亡

java - Spring Scheduler 意外停止

java - w.a.用户名密码验证过滤器 : An internal error occurred while trying to authenticate the user

java - 在邮件通知中显示环境 Spring Boot Admin

java - Spring Boot - 自定义 JSON 序列化

java - 用于两种不同 URL 模式的 Spring mvc Swagger UI 端点