首页 > 解决方案 > 在运行时绑定动态bean的最佳实践是什么

问题描述

我有一些配置属性定义为

@Data
@ConfigurationProperties(prefix = RedisClientConfigProperties.CONFIG_PROP_NAME)
public class RedisClientConfigProperties {
  public static final String CONFIG_PROP_NAME = "dao.redis";
  private int database = 0;
  private String host = "127.0.0.1";
  private int port = 6379;
  private boolean enabled = false;
}

和以下java类:

public interface IDao {
  Object get(UUID id);
  void put(UUID id, Object item);
}

public class NoOpDao implements IDao {
  @Override
  public Object get(UUID id) {
    return new Object();
  }

  @Override
  public void put(UUID id, Object item) {
    // no-op
  }
}

public class RedisDao implements IDao {
  @Autowired
  private RedisClient client;


  @Override
  public Object get(UUID id) {
    return client.get(id);
  }

  @Override
  public void put(UUID id, Object item) {
    client.put(id, item);
  }
}

我想要的是能够将一个 IDao 对象自动装配到另一个 bean 中,并且在运行时根据该RedisClientConfigProperties#enabled字段决定实现。在 Guice 中,我可以在模块中执行以下操作:

class DaoModule extends PrivateModule {
    private final RedisClientConfigProperties configProps;


    @Inject
    public DaoModule(RedisClientConfigProperties configProps) {
        this.configProps = configProps;
    }

    @Override
    protected void configure() {
        if (configProps.isEnabled()) {
            bind(IDao.class).to(RedisDao.class);
        } else {
            bind(IDao.class).to(NoOpDao.class);
        }
    }
}

在 Spring 中,还不清楚如何执行此操作。我找到了几种不同的方法来做到这一点,但我不确定“春天的方式”是什么:

  1. 使用带有@Bean 方法的@Configuration 类,该方法返回IDao,方法内带有条件逻辑
@Bean
public IDao getDao(RedisClientConfigProperties config) {
  if (config.isEnabled()) return new RedisDao();
  else return new NoOpDao;
}
  1. 使用服务定位器模式创建一个工厂,该工厂将返回您想要的 bean
  2. 使用 @ConditionalOnProperty 注释对实现进行注释

我很好奇推荐使用这些方法中的哪一种,因为我对它们中的每一种都有一些保留意见:

  1. 我宁愿不必手动实例化 bean,而是让 Springs IOC 根据需要处理实例化 bean
  2. 这仍然会创建所有接口实现 bean,并要求 bean 的任何消费者知道他们要求什么 bean
  3. 由于它不使用 Config 属性对象,而是直接在您的配置属性文件中查找,因此可能容易出错。

在这三个解决方案中,我认为我更喜欢#1,但我会喜欢反馈/建议。

标签: javaspringspring-boot

解决方案


您的代码似乎是每个应用程序的代码,因此我建议您将所有属性都放在配置路径中,而不是放在RedisClientConfigProperties.

保留你的类,但像这样修改它:RedisClientConfig.java

@Data
@ConfigurationProperties(prefix = "dao.redis")
public class RedisClientConfigProperties {
  private int database;
  private String host;
  private int port;
  private boolean enabled; 
  // public getters and setters
  .
  .
}

在你的配置文件中:application.yml

dao:
    redis:
        enabled: true
        database: 0
        host: 127.0.0.1
        port: 6379

并让您的 dao 仅在基于属性的上下文中注入

如果您只使用前缀来避免传递布尔值,您可以让它检查是否定义了属性

RedisDao.java

@Component
@Primary
@ConditionalOnProperty(prefix = "dao.redis", name = "enabled", havingValue = "true")
public class RedisDao implements IDao {
    .
    .
    .
}

这样,您将NoOpDao始终被注入,除非dao.redis.enabled为真,否则 Redis 将覆盖NoOpDao实现,因为它带有注释@Primary


推荐阅读