【问题标题】:Guice, Type Conversion When Getting Values from PropertiesGuice,从属性中获取值时的类型转换
【发布时间】:2023-04-11 08:32:02
【问题描述】:

以下 Guice 模块将属性文件绑定到 @Named 注释。

import com.google.inject.AbstractModule;
import com.google.inject.name.Names;

// Omitted: other imports

public class ExampleModule extends AbstractModule {
    @Override
    protected void configure() {
        Names.bindProperties(binder(), getProperties());
    }

    private Properties getProperties() {
        // Omitted: return the application.properties file
    }
}

我现在可以将属性直接注入到我的类中。

public class Example {
    @Inject
    @Named("com.example.title")
    private String title;

    @Inject
    @Named("com.example.panel-height")
    private int panelHeight;
}

从属性文件中读取的值是字符串,但正如您在上面的示例中所见,Guice 能够对 int 字段进行类型转换。

现在,给定com.example.background-color=0x333333 属性,我希望能够为任意类获得相同的类型转换,例如:

public class Example {
    @Inject
    @Named("com.example.background-color")
    private Color color;
}

假设Color 类包含一个静态方法decode(),我可以通过调用Color.decode("0x333333") 获得一个新的Color 实例。

如何将 Guice 配置为自动在幕后为我执行此操作?

【问题讨论】:

    标签: java guice


    【解决方案1】:

    我自己通过查看 Guice 资源找到了一个解决方案,尽管我不得不说它不是最漂亮的(稍后会详细介绍)。

    首先,我们需要创建一个TypeConverter

    import com.google.inject.TypeLiteral;
    import com.google.inject.spi.TypeConverter;
    
    // Omitted: other imports
    
    public class ColorTypeConverter implements TypeConverter {
        @Override
        public Object convert(String value, TypeLiteral<?> toType) {
            if (!toType.getRawType().isAssignableFrom(Color.class)) {
                throw new IllegalArgumentException("Cannot convert type " + toType.getType().getTypeName());
            }
    
            if (value == null || value.isBlank()) {
                return null;
            }
    
            return Color.decode(value);
        }
    }
    
    

    然后,Matcher。我概括了。

    import com.google.inject.TypeLiteral;
    import com.google.inject.matcher.AbstractMatcher;
    
    // Omitted: other imports
    
    public class SubclassMatcher extends AbstractMatcher<TypeLiteral<?>> {
        private final Class<?> type;
    
        public SubclassMatcher(Class<?> type) {
            this.type = type;
        }
    
        @Override
        public boolean matches(TypeLiteral<?> toType) {
            return toType.getRawType().isAssignableFrom(type);
        }
    }
    

    最后,将以下行添加到 Guice 模块中。

    import com.google.inject.AbstractModule;
    
    // Omitted: other imports
    
    public class ExampleModule extends AbstractModule {
        @Override
        protected void configure() {
            binder().convertToTypes(new SubclassMatcher(Color.class), new ColorTypeConverter());
            // Omitted: other configurations
        }
    }
    

    现在,以下注入工作。

    public class Example {
        @Inject
        @Named("com.example.background-color")
        private Color backgroundColor;
    }
    

    它可能会更漂亮。存在我无法使用的com.google.inject.matcher.Matchers API,并且可以在不构建我的个人SubclassMatcher 类的情况下解决我的问题。见,Matchers.subclassesOf(Class&lt;?&gt;)。这肯定是我的错,因为我不相信谷歌不会想到这个非常常见的用例。如果您找到了使其工作的方法,请发表评论。

    【讨论】:

      【解决方案2】:

      Guice 无法为您做到这一点。

      我想从Stringint 的转换是在注入时发生的,而不是在您调用Names.bindProperties(...) 时发生的

      查看bindProperties 方法:

        /** Creates a constant binding to {@code @Named(key)} for each entry in {@code properties}. */
        public static void bindProperties(Binder binder, Map<String, String> properties) {
          binder = binder.skipSources(Names.class);
          for (Map.Entry<String, String> entry : properties.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            binder.bind(Key.get(String.class, new NamedImpl(key))).toInstance(value);
          }
        }
      
        /**
         * Creates a constant binding to {@code @Named(key)} for each property. This method binds all
         * properties including those inherited from {@link Properties#defaults defaults}.
         */
        public static void bindProperties(Binder binder, Properties properties) {
          binder = binder.skipSources(Names.class);
      
          // use enumeration to include the default properties
          for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements(); ) {
            String propertyName = (String) e.nextElement();
            String value = properties.getProperty(propertyName);
            binder.bind(Key.get(String.class, new NamedImpl(propertyName))).toInstance(value);
          }
        }
      

      它们只是绑定字符串。

      您可以复制其中一个并创建自己的绑定。如果属性值是颜色格式,额外绑定为Color

      举个例子:

      public class GuiceColors {
      
          public static class GameModule extends AbstractModule {
              @Override
              protected void configure() {
                  Properties props = new Properties();
                  try {
                      props.load(getClass().getResourceAsStream("application.properties"));
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
      
                  bindPropertiesWithColors(props);
              }
      
              private void bindPropertiesWithColors(Properties properties) {
                  Binder binder2 = binder().skipSources(Names.class);
      
                  // use enumeration to include the default properties
                  for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
                      String propertyName = (String) e.nextElement();
                      String value = properties.getProperty(propertyName);
                      try {
                          Color decodedColor = Color.decode(value);
                          binder2.bind(Key.get(Color.class, Names.named(propertyName)))
                                  .toInstance(decodedColor);
                      } catch (NumberFormatException ex) {
                          // property value cannot be decoded as color, ignore the exception
                      }
                      binder2.bind(Key.get(String.class, Names.named(propertyName))).toInstance(value);
                  }
              }
      
          }
      
          public static class Example {
              @Inject
              @Named("com.example.background-color")
              private Color color;
      
              @Inject
              @Named("com.example.background-color")
              private String colorString;
      
          }
      
          public static void main(String[] args) {
              Injector injector = Guice.createInjector(new GameModule());
              System.out.println(injector.getInstance(Example.class).color);
              System.out.println(injector.getInstance(Example.class).colorString);
          }
      }
      

      application.properties 是:

      com.example.background-color = 0x333333
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-01-04
        • 2020-01-29
        • 2021-12-04
        • 2017-03-07
        • 1970-01-01
        • 2010-10-26
        • 1970-01-01
        • 2017-03-22
        相关资源
        最近更新 更多