SpringBoot中Jolt自定义转换器无法注入Bean的问题咨询
问题:Jolt自定义转换器无法注入Spring Boot Bean的解决方案
我需要将XML文件中的部分属性转换,但在CountryCodeTransformer中注入CountryListBean时,该Bean无法实例化。推测Jolt自定义转换执行时缺少Spring Boot上下文,请问如何传递Spring Boot上下文,让Jolt自定义转换器能够正确注入Bean?
相关代码与资源
示例XML
<?xml version="1.0" encoding="utf-8"?> <CountryList xmlns="http://www.org.com/tc/CountryList"> <countries> <countryName>Afghanistan</countryName> <eCountryName>Afghanistan</eCountryName> <twoCharCode>AF</twoCharCode> <threeCharCode>AFG</threeCharCode> </countries> <countries> <countryName>Albania</countryName> <eCountryName>Albania</eCountryName> <twoCharCode>AL</twoCharCode> <threeCharCode>ALB</threeCharCode> </countries> </CountryList>
XML对应的XSD
<?xml version="1.0" encoding="utf-8"?> <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://www.org.com/tc/CountryList" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="CountryList"> <xs:complexType> <xs:sequence> <xs:element maxOccurs="unbounded" name="countries"> <xs:complexType> <xs:sequence> <xs:element name="countryName" type="xs:string" /> <xs:element name="eCountryName" type="xs:string" /> <xs:element name="twoCharCode" type="xs:string" /> <xs:element name="threeCharCode" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
Bean模型类
import java.util.Map; import com.org.xsd.CountryList.Countries; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @Setter @Getter @EqualsAndHashCode public class CountryListBean { private Map<String, Countries> countryToCode; }
Spring Boot配置类
import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.org.CountryListBean; import com.org.xsd.CountryList.Countries; import com.fasterxml.jackson.core.exc.StreamReadException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DatabindException; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @Getter @Setter @Configuration @Slf4j public class LookUpListConfigurtion { @Bean public CountryListBean countryConfiguration() { InputStream is = LookUpListConfigurtion.class.getResourceAsStream("/mapping/CountryList.xml"); XmlMapper xmlMapper = new XmlMapper(); List<Countries> countries = null; try { countries = xmlMapper.readValue(is, new TypeReference<List<Countries>>() {}); } catch (StreamReadException | DatabindException | IOException e) { e.printStackTrace(); } Map<String, Countries> nameToCountry = countries.stream() .collect(Collectors.toMap(Countries::getECountryName, Function.identity())); CountryListBean bean = new CountryListBean(); bean.setCountryToCode(nameToCountry); return bean; } }
原Jolt转换器
public class CountryCodeTransformer { @Component public static final class countryCode extends ListFunction { @Autowired CountryListBean bean; @Override protected Optional<Object> applyList(List<Object> arg0) { return Optional.of(String.valueOf(bean.getCountryToCode().get(arg0.get(0)).getTwoCharCode())); } } }
原转换器规格类
import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.bazaarvoice.jolt.ContextualTransform; import com.bazaarvoice.jolt.SpecDriven; import com.bazaarvoice.jolt.common.Optional; import com.bazaarvoice.jolt.common.tree.MatchedElement; import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.exception.SpecException; import com.bazaarvoice.jolt.modifier.OpMode; import com.bazaarvoice.jolt.modifier.TemplatrSpecBuilder; import com.bazaarvoice.jolt.modifier.function.Function; import com.bazaarvoice.jolt.modifier.spec.ModifierCompositeSpec; public class CountryTransformerSpec implements SpecDriven, ContextualTransform { private static final Map<String, Function> STOCK_FUNCTIONS = Map.of( "countryCode", new CountryCodeTransformer.countryCode()); private final ModifierCompositeSpec rootSpec; @SuppressWarnings("unchecked") private CountryTransformerSpec(Object spec, OpMode opMode, Map<String, Function> functionsMap) { if (spec == null) { throw new SpecException(opMode.name() + " expected a spec of Map type, got 'null'."); } if (!(spec instanceof Map)) { throw new SpecException( opMode.name() + " expected a spec of Map type, got " + spec.getClass().getSimpleName()); } if (functionsMap == null || functionsMap.isEmpty()) { throw new SpecException(opMode.name() + " expected a populated functions' map type, got " + (functionsMap == null ? "null" : "empty")); } functionsMap = Collections.unmodifiableMap(functionsMap); TemplatrSpecBuilder templatrSpecBuilder = new TemplatrSpecBuilder(opMode, functionsMap); rootSpec = new ModifierCompositeSpec(ROOT_KEY, (Map<String, Object>) spec, opMode, templatrSpecBuilder); } @Override public Object transform(final Object input, final Map<String, Object> context) { Map<String, Object> contextWrapper = new HashMap<>(); contextWrapper.put(ROOT_KEY, context); MatchedElement rootLpe = new MatchedElement(ROOT_KEY); WalkedPath walkedPath = new WalkedPath(); walkedPath.add(input, rootLpe); rootSpec.apply(ROOT_KEY, Optional.of(input), walkedPath, null, contextWrapper); return input; } public static final class Overwritr extends CountryTransformerSpec { public Overwritr(Object spec) { this(spec, STOCK_FUNCTIONS); } public Overwritr(Object spec, Map<String, Function> functionsMap) { super(spec, OpMode.OVERWRITR, functionsMap); } } public static final class Definr extends CountryTransformerSpec { public Definr(final Object spec) { this(spec, STOCK_FUNCTIONS); } public Definr(Object spec, Map<String, Function> functionsMap) { super(spec, OpMode.DEFINER, functionsMap); } } public static class Defaultr extends CountryTransformerSpec { public Defaultr(final Object spec) { this(spec, STOCK_FUNCTIONS); } public Defaultr(Object spec, Map<String, Function> functionsMap) { super(spec, OpMode.DEFAULTR, functionsMap); } } }
示例输入JSON
{ "countryName": "Afghanistan" }
期望输出JSON
{ "countryName": "AF" }
Jolt转换规格
[ { "operation": "com.org.jolt.transformer.CountryTransformerSpec$Overwritr", "spec": { "countryName": "=countryCode(@(1,countryName))" } } ]
问题原因
Jolt默认通过反射实例化自定义转换器和函数,完全脱离Spring容器的生命周期管理,因此@Autowired注解无法生效——Spring的上下文根本没传递到Jolt的执行环境中。
解决方案
1. 改造自定义函数为Spring管理的Bean
把原静态内部类改成独立的Spring组件,用构造注入替代字段注入(避免空指针风险):
import org.springframework.stereotype.Component; import com.bazaarvoice.jolt.modifier.function.ListFunction; import com.bazaarvoice.jolt.common.Optional; import com.org.CountryListBean; import com.org.xsd.CountryList.Countries; import java.util.List; @Component public class CountryCodeFunction extends ListFunction { private final CountryListBean countryListBean; // 构造注入Spring管理的Bean public CountryCodeFunction(CountryListBean countryListBean) { this.countryListBean = countryListBean; } @Override protected Optional<Object> applyList(List<Object> args) { if (args == null || args.isEmpty()) { return Optional.empty(); } String countryName = String.valueOf(args.get(0)); Countries country = countryListBean.getCountryToCode().get(countryName); return country != null ? Optional.of(country.getTwoCharCode()) : Optional.empty(); } }
2. 修改转换器规格类,接入Spring上下文
移除硬编码的函数实例,改为从Spring容器注入自定义函数:
import org.springframework.stereotype.Component; import java.util.Collections; import java.util.Map; import com.bazaarvoice.jolt.ContextualTransform; import com.bazaarvoice.jolt.SpecDriven; import com.bazaarvoice.jolt.common.Optional; import com.bazaarvoice.jolt.common.tree.MatchedElement; import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.exception.SpecException; import com.bazaarvoice.jolt.modifier.OpMode; import com.bazaarvoice.jolt.modifier.TemplatrSpecBuilder; import com.bazaarvoice.jolt.modifier.function.Function; import com.bazaarvoice.jolt.modifier.spec.ModifierCompositeSpec; @Component public class CountryTransformerSpec implements SpecDriven, ContextualTransform { private final CountryCodeFunction countryCodeFunction; private final Map<String, Function> stockFunctions; // 注入自定义函数,构建函数映射 public CountryTransformerSpec(CountryCodeFunction countryCodeFunction) { this.countryCodeFunction = countryCodeFunction; this.stockFunctions = Collections.unmodifiableMap( Map.of("countryCode", countryCodeFunction) ); } @SuppressWarnings("unchecked") private CountryTransformerSpec(Object spec, OpMode opMode, Map<String, Function> functionsMap) { if (spec == null) { throw new SpecException(opMode.name() + " expected a spec of Map type, got 'null'."); } if (!(spec instanceof Map)) { throw new SpecException( opMode.name() + " expected a spec of Map type, got " + spec.getClass().getSimpleName()); } if (functionsMap == null || functionsMap.isEmpty()) { throw new SpecException(opMode.name() + " expected a populated functions' map type, got " + (functionsMap == null ? "null" : "empty")); } functionsMap = Collections.unmodifiableMap(functionsMap); TemplatrSpecBuilder templatrSpecBuilder = new TemplatrSpecBuilder(opMode, functionsMap); rootSpec = new ModifierCompositeSpec(ROOT_KEY, (Map<String, Object>) spec, opMode, templatrSpecBuilder); } @Override public Object transform(final Object input, final Map<String, Object> context) { // 原transform逻辑不变 Map<String, Object> contextWrapper = new HashMap<>(); contextWrapper.put(ROOT_KEY, context); MatchedElement rootLpe = new MatchedElement(ROOT_KEY); WalkedPath walkedPath = new WalkedPath(); walkedPath.add(input, rootLpe); rootSpec.apply(ROOT_KEY, Optional.of(input), walkedPath, null, contextWrapper); return input; } // 改造内部类,支持传入Spring管理的函数映射 public static final class Overwritr extends CountryTransformerSpec { public Overwritr(Object spec, Map<String, Function> functionsMap) { super(spec, OpMode.OVERWRITR, functionsMap); } } }
3. 在业务代码中手动关联Spring与Jolt
不要让Jolt自动反射实例化转换器,而是在Spring管理的Service中,注入自定义函数后手动构建转换器:
import org.springframework.stereotype.Service; import com.bazaarvoice.jolt.Chainr; import com.bazaarvoice.jolt.JsonUtils; import com.org.jolt.transformer.CountryCodeFunction; import com.org.jolt.transformer.CountryTransformerSpec; import java.util.List; import java.util.Map; @Service public class CountryTransformService { private final CountryCodeFunction countryCodeFunction; public CountryTransformService(CountryCodeFunction countryCodeFunction) { this.countryCodeFunction = countryCodeFunction; } public Object transformCountry(Object input) { // 读取Jolt规格 String specJson = "[{\"operation\": \"com.org.jolt.transformer.CountryTransformerSpec$Overwritr\", \"spec\": {\"countryName\": \"=countryCode(@(1,countryName))\"}}]"; List<Object> spec = JsonUtils.jsonToList(specJson); // 构建自定义函数映射 Map<String, com.bazaarvoice.jolt.modifier.function.Function> functionsMap = Map.of("countryCode", countryCodeFunction); // 自定义Chainr的实例化逻辑,传入Spring管理的函数 Chainr chainr = Chainr.fromSpec(spec, specElement -> { if (specElement instanceof Map && "com.org.jolt.transformer.CountryTransformerSpec$Overwritr".equals(((Map) specElement).get("operation"))) { Object specObj = ((Map) specElement).get("spec"); return new CountryTransformerSpec.Overwritr(specObj, functionsMap); } return null; // 其他操作使用默认处理 }); return chainr.transform(input); } }
关键说明
- Jolt和Spring是两个独立的生态,必须手动把Spring管理的Bean传递给Jolt组件,不能依赖Jolt的自动实例化机制。
- 优先使用构造注入,Spring会保证注入的Bean已经完成初始化,避免字段注入可能导致的空指针问题。
- 如果需要复用转换器,可以把
CountryTransformerSpec.Overwritr也配置为Spring Bean,直接注入后使用。
内容的提问来源于stack exchange,提问作者Prasad S Deshpande
相关产品推荐
相关产品推荐

