0

以下 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通过调用Color.decode("0x333333").

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

4

2 回答 2

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<?>)。这肯定是我的错,因为我不相信谷歌不会想到这个非常常见的用例。如果您找到使其工作的方法,请发表评论。

于 2021-09-25T22:21:13.720 回答
0

Guice 无法为您做到这一点。

我想从String到的转换int发生在注射时,而不是当你打电话时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
于 2021-09-25T21:27:37.877 回答