You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 12:57:24