是否可以使用地图地图作为Maven插件参数?,例如
@Parameter
private Map<String, Map<String, String>> converters;
然后像
一样使用它<converters>
<json>
<indent>true</indent>
<strict>true</strict>
</json>
<yaml>
<stripComments>false</stripComments>
</yaml>
<converters>
如果我这样使用它,converters
只包含键json
和yaml
,其值为null。
我知道可以将复杂对象作为值,但是在某种程度上可以使用映射作为变量元素值,如本示例所示吗?
答案 0 :(得分:3)
这显然是Mojo API内部使用的sisu.plexus项目的限制。如果您查看MapConverter
源内部,您会发现它首先尝试通过尝试将配置解释为String(调用fromExpression
)来尝试获取映射的值,并且当此失败时,looks up the expected type of the value。但是,此方法不检查parameterized types,这是我们的情况(因为地图值的类型是Map<String, String>
)。我在此项目的Bugzilla上提交了the bug 498757来跟踪此事。
一种解决方法是不使用Map<String, String>
作为值,而是使用自定义对象:
@Parameter
private Map<String, Converter> converters;
,类Converter
,位于与Mojo相同的包中,为:
public class Converter {
@Parameter
private Map<String, String> properties;
@Override
public String toString() { return properties.toString(); } // to test
}
然后,您可以使用以下命令配置Mojo:
<converters>
<json>
<properties>
<indent>true</indent>
<strict>true</strict>
</properties>
</json>
<yaml>
<properties>
<stripComments>false</stripComments>
</properties>
</yaml>
</converters>
此配置将正确地在内部映射中注入值。它还保留变量方面:对象仅作为内部映射的包装引入。我用一个简单的测试魔法测试了这个
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info(converters.toString());
}
,输出是预期的{json={indent=true, strict=true}, yaml={stripComments=false}}
。
我还找到了一种使用自定义Map<String, Map<String, String>>
保留ComponentConfigurator
的方法。
所以我们希望通过继承它来修复MapConverter
,问题是如何注册这个新的FixedMapConverter
。默认情况下,Maven使用BasicComponentConfigurator
来配置Mojo,它依赖于DefaultConverterLookup
来查找要用于特定类的转换器。在这种情况下,我们希望为Map
提供一个自定义转换,它将返回我们的固定版本。因此,我们需要扩展这个基本的配置器并注册我们的新转换器。
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.configurator.BasicComponentConfigurator;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
public class CustomBasicComponentConfigurator extends BasicComponentConfigurator {
@Override
public void configureComponent(final Object component, final PlexusConfiguration configuration,
final ExpressionEvaluator evaluator, final ClassRealm realm, final ConfigurationListener listener)
throws ComponentConfigurationException {
converterLookup.registerConverter(new FixedMapConverter());
super.configureComponent(component, configuration, evaluator, realm, listener);
}
}
然后我们需要告诉Maven使用这个新的配置器而不是基本配置器。这是一个分为两步的过程:
在Maven插件中,创建一个注册新组件的文件src/main/resources/META-INF/plexus/components.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<component-set>
<components>
<component>
<role>org.codehaus.plexus.component.configurator.ComponentConfigurator</role>
<role-hint>custom-basic</role-hint>
<implementation>package.to.CustomBasicComponentConfigurator</implementation>
</component>
</components>
</component-set>
注意以下几点:我们声明一个具有提示"custom-basic"
的新组件,这将作为引用它的id,<implementation>
引用我们的配置器的完全限定类名。
告诉我们的Mojo将此配置程序与@Mojo
注释的configurator
属性一起使用:
@Mojo(name = "test", configurator = "custom-basic")
此处传递的配置程序对应于上面components.xml
中指定的角色提示。
通过这样的设置,您最终可以声明
@Parameter
private Map<String, Map<String, String>> converters;
并且所有内容都将正确注入:Maven将使用我们的自定义配置器,它将注册我们的固定版本的地图转换器并正确转换内部地图。
FixedMapConverter
的完整代码(几乎复制粘贴MapConverter
,因为我们无法覆盖错误的方法):
public class FixedMapConverter extends MapConverter {
public Object fromConfiguration(final ConverterLookup lookup, final PlexusConfiguration configuration,
final Class<?> type, final Type[] typeArguments, final Class<?> enclosingType, final ClassLoader loader,
final ExpressionEvaluator evaluator, final ConfigurationListener listener)
throws ComponentConfigurationException {
final Object value = fromExpression(configuration, evaluator, type);
if (null != value) {
return value;
}
try {
final Map<Object, Object> map = instantiateMap(configuration, type, loader);
final Class<?> elementType = findElementType(typeArguments);
if (Object.class == elementType || String.class == elementType) {
for (int i = 0, size = configuration.getChildCount(); i < size; i++) {
final PlexusConfiguration element = configuration.getChild(i);
map.put(element.getName(), fromExpression(element, evaluator));
}
return map;
}
// handle maps with complex element types...
final ConfigurationConverter converter = lookup.lookupConverterForType(elementType);
for (int i = 0, size = configuration.getChildCount(); i < size; i++) {
Object elementValue;
final PlexusConfiguration element = configuration.getChild(i);
try {
elementValue = converter.fromConfiguration(lookup, element, elementType, enclosingType, //
loader, evaluator, listener);
}
// TEMP: remove when http://jira.codehaus.org/browse/MSHADE-168
// is fixed
catch (final ComponentConfigurationException e) {
elementValue = fromExpression(element, evaluator);
Logs.warn("Map in " + enclosingType + " declares value type as: {} but saw: {} at runtime",
elementType, null != elementValue ? elementValue.getClass() : null);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
map.put(element.getName(), elementValue);
}
return map;
} catch (final ComponentConfigurationException e) {
if (null == e.getFailedConfiguration()) {
e.setFailedConfiguration(configuration);
}
throw e;
}
}
@SuppressWarnings("unchecked")
private Map<Object, Object> instantiateMap(final PlexusConfiguration configuration, final Class<?> type,
final ClassLoader loader) throws ComponentConfigurationException {
final Class<?> implType = getClassForImplementationHint(type, configuration, loader);
if (null == implType || Modifier.isAbstract(implType.getModifiers())) {
return new TreeMap<Object, Object>();
}
final Object impl = instantiateObject(implType);
failIfNotTypeCompatible(impl, type, configuration);
return (Map<Object, Object>) impl;
}
private static Class<?> findElementType( final Type[] typeArguments )
{
if ( null != typeArguments && typeArguments.length > 1 )
{
if ( typeArguments[1] instanceof Class<?> )
{
return (Class<?>) typeArguments[1];
}
// begin fix here
if ( typeArguments[1] instanceof ParameterizedType )
{
return (Class<?>) ((ParameterizedType) typeArguments[1]).getRawType();
}
// end fix here
}
return Object.class;
}
}
答案 1 :(得分:2)
一种解决方案非常简单,适用于1级嵌套。在替代答案中可以找到更复杂的方法,这可能也允许更深的地图嵌套。
不使用接口作为类型参数,只需使用类似TreeMap
@Parameter
private Map<String, TreeMap> converters.
原因是MapConverter中的这个检查失败了一个接口但是超过了一个具体的类:
private static Class<?> findElementType( final Type[] typeArguments )
{
if ( null != typeArguments && typeArguments.length > 1
&& typeArguments[1] instanceof Class<?> )
{
return (Class<?>) typeArguments[1];
}
return Object.class;
}
作为旁注,因为它也与Maven的answer相关&gt; 3.3.x它还可以通过继承BasicComponentConfigurator
并将其用作Plexus组件来安装自定义转换器。 BasicComponentConfigurator
将DefaultConverterLookup
作为受保护的成员变量,因此可以轻松访问自定义转换器。