Guice,从属性获取值时的类型转换
Guice, Type Conversion When Getting Values from Properties
以下 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
}
}
我现在可以将属性直接注入我的 classes。
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
我希望能够为任意 class 获得相同的类型转换,例如:
public class Example {
@Inject
@Named("com.example.background-color")
private Color color;
}
假设 Color
class 包含一个静态方法 decode()
并且我可以通过调用 Color.decode("0x333333")
.[= 获得一个新的 Color
实例20=]
我如何配置 Guice 在后台自动为我执行此操作?
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
我自己通过查看 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
class 的情况下解决我的问题。看,Matchers.subclassesOf(Class<?>)
。这肯定是我的错,因为我不相信 Google 不会想到这个非常常见的用例。如果您找到使它工作的方法,请发表评论。
以下 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
}
}
我现在可以将属性直接注入我的 classes。
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
我希望能够为任意 class 获得相同的类型转换,例如:
public class Example {
@Inject
@Named("com.example.background-color")
private Color color;
}
假设 Color
class 包含一个静态方法 decode()
并且我可以通过调用 Color.decode("0x333333")
.[= 获得一个新的 Color
实例20=]
我如何配置 Guice 在后台自动为我执行此操作?
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
我自己通过查看 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
class 的情况下解决我的问题。看,Matchers.subclassesOf(Class<?>)
。这肯定是我的错,因为我不相信 Google 不会想到这个非常常见的用例。如果您找到使它工作的方法,请发表评论。