惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
Y
Y Combinator Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Security Blog
Microsoft Security Blog
aimingoo的专栏
aimingoo的专栏
I
InfoQ
C
Check Point Blog
IT之家
IT之家
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
Vercel News
Vercel News
Last Week in AI
Last Week in AI
GbyAI
GbyAI
P
Proofpoint News Feed
量子位
Stack Overflow Blog
Stack Overflow Blog
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
人人都是产品经理
人人都是产品经理
B
Blog
T
The Blog of Author Tim Ferriss
H
Help Net Security
云风的 BLOG
云风的 BLOG

博客园 - wastonl

maven选择settings.xml策略 RocketMQ消费机制整理 RocketMQ重平衡机制 JSR303和SpringBoot参数校验机制 同一Java进程生产者和消费者与MqClientInstance对应关系 RocketMQ自动创建主题机制 SpringMVC异步请求 tomcat异步请求机制 Spring异步任务和定时任务 Spring ResolvableType说明 Jvm内存以及垃圾回收相关知识 RocketMQ如何保证消息可靠性 RocketMQ整体架构 mybatis-plus易忘点笔记 SpringMVC使用Resource实现二进制传输(下载) Spring事件异步执行设计与实现 Spring Bean销毁机制 Spring Lifecycle组件 Zipkin Brave使用 Spring Boot日志系统简要介绍 spring cloud sleuth基本使用 Spring懒加载与@Lazy注解 tomcat自动刷新响应输出流缓冲区 https碎碎念 ES脚本使用 SpringMVC静态资源处理 Maven插件运行方式 如何使用Maven将项目中的依赖打进jar包 时区以及时区对于Java时间类格式化的影响 SpringMVC处理请求头、响应头、编码行为
@JacksonXmlElementWrapper使用限制
wastonl · 2026-08-21 · via 博客园 - wastonl

作用

@JacksonXmlElementWrapper 是 Jackson XML 模块提供的注解,用于控制CollectionArray字段在 XML 序列化/反序列化时是否使用包装元素,以及包装元素的名称和命名空间。

注:该注解只对CollectionArray字段有效。

行为解释

XmlMapper默认开启包装行为,可通过以下代码手动开启或关闭

 XmlMapper mapper = XmlMapper.builder().defaultUseWrapper(false).build();

上面是一个全局开关,如果开启,即便字段上没有@JacksonXmlElementWrapper注解,也会添加包装元素,包装元素的名称就是属性名。

@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface JacksonXmlElementWrapper
{
    
    public final static String USE_PROPERTY_NAME = "";
    
    String namespace() default USE_PROPERTY_NAME;
    
    /**
     * 包装元素名称
     * 默认为字段名称(@JacksonXmlProperty注解可以指定字段名称,若没有明确指定,则就是java字段名)
     */
    String localName() default USE_PROPERTY_NAME;

    /**
     * 用于控制是否添加包装元素, 可覆盖全局设置
     */
    boolean useWrapping() default true;
}

使用示例

不加任何注解

public class XmlTest {

    @AllArgsConstructor
    @Data
    public static class Item {
        public String id;
        public String name;
    }


    @AllArgsConstructor
    @Data
    public static class SingleContainer {

        public List<Item> itemList;
    }

    @Test
    public void testWrap() {
         SingleContainer obj = new SingleContainer(
            Arrays.asList(
                new Item("1", "a"),
                new Item("2", "b"))
        );

        String xml = XmlUtils.objToXml(obj);
        System.out.println(xml);
    }
}

结果如下:

<?xml version='1.0' encoding='UTF-8'?>
<SingleContainer>
  <itemList>
    <itemList>
      <id>1</id>
      <name>a</name>
    </itemList>
    <itemList>
      <id>2</id>
      <name>b</name>
    </itemList>
  </itemList>
</SingleContainer>

可以看到包装元素名称默认就是字段名。

使用@JacksonXmlProperty注解指定字段名

public class XmlTest {

    @AllArgsConstructor
    @Data
    public static class Item {
        public String id;
        public String name;
    }


    @AllArgsConstructor
    @Data
    public static class SingleContainer {

        @JacksonXmlProperty(localName = "item")
        public List<Item> itemList;
    }

    @Test
    public void testWrap() {
        SingleContainer obj = new SingleContainer(
            Arrays.asList(
                new Item("1", "a"),
                new Item("2", "b"))
        );

        String xml = XmlUtils.objToXml(obj);
        System.out.println(xml);
    }
}

结果

<?xml version='1.0' encoding='UTF-8'?>
<SingleContainer>
  <item>
    <item>
      <id>1</id>
      <name>a</name>
    </item>
    <item>
      <id>2</id>
      <name>b</name>
    </item>
  </item>
</SingleContainer>

可以看到包装元素会跟随字段名一起变化

使用@JacksonXmlElementWrapper

public class XmlTest {

    @AllArgsConstructor
    @Data
    public static class Item {
        public String id;
        public String name;
    }


    @AllArgsConstructor
    @Data
    public static class SingleContainer {

        @JacksonXmlElementWrapper(localName = "itemList")
        @JacksonXmlProperty(localName = "item")
        public List<Item> itemList;
    }

    @Test
    public void testWrap() {
        SingleContainer obj = new SingleContainer(
            Arrays.asList(
                new Item("1", "a"),
                new Item("2", "b"))
        );

        String xml = XmlUtils.objToXml(obj);
        System.out.println(xml);
    }
}

结果

<?xml version='1.0' encoding='UTF-8'?>
<SingleContainer>
  <itemList>
    <item>
      <id>1</id>
      <name>a</name>
    </item>
    <item>
      <id>2</id>
      <name>b</name>
    </item>
  </itemList>
</SingleContainer>

可以看到包装元素名称变成了itemList。

也就是说@JacksonXmlElementWrapper指定包装元素名,@JacksonXmlProperty指定集合项元素名,若@JacksonXmlElementWrapper没有指定,包装元素名就是@JacksonXmlProperty的值。而@JacksonXmlProperty的值默认为java字段名,当然也可自己指定。

使用限制

如果一个类中有多个集合类型,想要包装元素名不一样,而集合项元素名一样,使用这两个注解配合是无法做到的。因为集合项元素名是通过@JacksonXmlProperty指定,如果一样则会报名字冲突。就像java类中字段名也不能冲突一样。

public class XmlTest {

    @AllArgsConstructor
    @Data
    public static class Item {
        public String id;
        public String name;
    }


    @AllArgsConstructor
    @Data
    public static class MultiContainer {

        @JacksonXmlElementWrapper(localName = "itemList1")
        @JacksonXmlProperty(localName = "item")
        public List<Item> itemList1;

        @JacksonXmlElementWrapper(localName = "itemList2")
        @JacksonXmlProperty(localName = "item")
        public List<Item> itemList2;
    }

    @Test
    public void testWrap() {
        MultiContainer obj = new MultiContainer(
            Arrays.asList(new Item("1", "a"), new Item("2", "b")),
            Arrays.asList(new Item("3", "a"), new Item("4", "b"))
        );

        String xml = XmlUtils.objToXml(obj);
        System.out.println(xml);
    }
}

报错如下

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Conflicting getter definitions for property "item"

使用自定义序列化解决如上问题

实现

包装元素还是交由@JacksonXmlElementWrapper指定,自定义序列化器用来替换集合元素项名称。

XmlElement.java

/**
 * 自定义注解,用于指定 XML 元素名称。
 * 配合 {@link ObjectXmlSerializer} 使用,可在序列化集合类型时指定集合项元素名称。
 * <p>
 * 设计目的:
 * 当同一个对象中有多个集合类型(如 List),且希望它们的包装名不同,但集合项元素名称相同时,
 * 使用 Jackson 原生的 {@code @JacksonXmlElementWrapper} 和 {@code @JacksonXmlProperty} 注解无法满足需求,
 * 通过本注解 + {@link ObjectXmlSerializer} 可以实现该功能。
 * </p>
 *
 * @see ObjectXmlSerializer
 */
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlElement {

    /**
     * 指定 XML 元素的本地名称(即标签名)。
     *
     * @return 元素名称
     */
    String localName();
}

ObjectXmlSerializer.java

/**
 * 自定义 Jackson 序列化器,用于指定 XML 元素的标签名。
 * 实现 {@link ContextualSerializer} 接口,以便在序列化时动态获取 {@link XmlElement} 注解中指定的名称。
 * <p>
 * 使用方式:
 * 在需要自定义名称的 List 或容器字段上,同时标注:
 * <pre>{@code
 * @XmlElement(localName = "bean")
 * @JsonSerialize(contentUsing = ObjectXmlSerializer.class)
 * private List<Bean> beanList;
 * }</pre>
 * <p>
 * 序列化结果示例:
 * <pre>{@code
 * <ContainerBean>
 *     <BeanList1>
 *         <bean>...</bean>
 *     </BeanList1>
 *     <BeanList2>
 *         <bean>...</bean>
 *     </BeanList2>
 * </ContainerBean>
 * }</pre>
 *
 * @see XmlElement
 * @author wangtao20
 * Created at 2026-08-19
 */
public class ObjectXmlSerializer extends JsonSerializer<Object> implements ContextualSerializer {

    /**
     * XML元素名称
     */
    private String elementName;

    public ObjectXmlSerializer() {

    }

    /**
     * 带元素名称的构造方法,由 {@link #createContextual} 调用。
     *
     * @param elementName XML元素名称
     */
    public ObjectXmlSerializer(String elementName) {
        this.elementName = elementName;
    }

    /**
     * 序列化方法,覆盖当前节点的元素名称。
     *
     * @param object     要序列化的对象
     * @param gen        JSON 生成器(实际为 {@link ToXmlGenerator})
     * @param serializers 序列化提供者
     * @throws IOException 序列化异常
     */
    @Override
    public void serialize(Object object, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (this.elementName == null) {
            throw new IllegalArgumentException("elementName is null, please use @XmlElement to set the element name");
        }

        /*
         * 此时的nextName为@JacksonXmlProperty指定的名称
         * 将当前节点元素名替换为指定的elementName
         */
        ToXmlGenerator xmlGen = (ToXmlGenerator) gen;
        xmlGen.setNextName(new QName(this.elementName));

        // 委托给默认序列化器处理值内容
        serializers.defaultSerializeValue(object, gen);
    }

    /**
     * 上下文序列化工厂方法,用于从字段/方法上读取 {@link XmlElement} 注解,
     * 并构造带有指定元素名称的序列化器实例。
     *
     * @param prov     序列化提供者
     * @param property 当前序列化的属性(字段或方法)
     * @return 如果注解存在则返回新的 {@link ObjectXmlSerializer} 实例,否则返回 null(表示不干预)
     */
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
        XmlElement annotation = property.getAnnotation(XmlElement.class);
        // 若用户指定了容器元素名称,则使用用户指定的名称
        if (annotation != null) {
            return new ObjectXmlSerializer(annotation.localName());
        }
        return this;
    }
}

提供一个默认的集合项元素名为sdo的序列化器,与JAXB类似。

SdoObjectXmlSerializer.java

/**
 * 默认集合项元素名称为sdo, 与JAXB风格保持一致
 */
public class SdoObjectXmlSerializer extends ObjectXmlSerializer {

    public SdoObjectXmlSerializer() {
        super("sdo");
    }
}

使用1

public class XmlTest {

    @AllArgsConstructor
    @Data
    public static class Item {
        public String id;
        public String name;
    }


    @AllArgsConstructor
    @Data
    public static class MultiContainer {

        /**
         * @JacksonXmlElementWrapper可以省略,默认值为java字段名
         * @JsonSerialize使用contentUsing而不是using
         * contentUsing针对的是集合中的元素
         */
        @JsonSerialize(contentUsing = ObjectXmlSerializer.class)
        @XmlElement(localName = "item")
        @JacksonXmlElementWrapper(localName = "itemList1")
        public List<Item> itemList1;

        @JsonSerialize(contentUsing = ObjectXmlSerializer.class)
        @XmlElement(localName = "item")
        @JacksonXmlElementWrapper(localName = "itemList2")
        public List<Item> itemList2;
    }

    @Test
    public void testWrap() {
        MultiContainer obj = new MultiContainer(
            Arrays.asList(new Item("1", "a"), new Item("2", "b")),
            Arrays.asList(new Item("3", "a"), new Item("4", "b"))
        );

        String xml = XmlUtils.objToXml(obj);
        System.out.println(xml);
    }
}

结果

<?xml version='1.0' encoding='UTF-8'?>
<MultiContainer>
  <itemList1>
    <item>
      <id>1</id>
      <name>a</name>
    </item>
    <item>
      <id>2</id>
      <name>b</name>
    </item>
  </itemList1>
  <itemList2>
    <item>
      <id>3</id>
      <name>a</name>
    </item>
    <item>
      <id>4</id>
      <name>b</name>
    </item>
  </itemList2>
</MultiContainer>

使用2

public class XmlTest {

    @AllArgsConstructor
    @Data
    public static class Item {
        public String id;
        public String name;
    }


    @AllArgsConstructor
    @Data
    public static class MultiContainer {

        @JsonSerialize(contentUsing = SdoObjectXmlSerializer.class)
        public List<Item> itemList1;

        @JsonSerialize(contentUsing = SdoObjectXmlSerializer.class)
        public List<Item> itemList2;
    }

    @Test
    public void testWrap() {
        MultiContainer obj = new MultiContainer(
            Arrays.asList(new Item("1", "a"), new Item("2", "b")),
            Arrays.asList(new Item("3", "a"), new Item("4", "b"))
        );

        String xml = XmlUtils.objToXml(obj);
        System.out.println(xml);
    }
}

结果

<?xml version='1.0' encoding='UTF-8'?>
<MultiContainer>
  <itemList1>
    <sdo>
      <id>1</id>
      <name>a</name>
    </sdo>
    <sdo>
      <id>2</id>
      <name>b</name>
    </sdo>
  </itemList1>
  <itemList2>
    <sdo>
      <id>3</id>
      <name>a</name>
    </sdo>
    <sdo>
      <id>4</id>
      <name>b</name>
    </sdo>
  </itemList2>
</MultiContainer>