XSD逆向生成Java Bean详解

大家好, 我是徐徐!
今天给大家分享一个xsd逆向生成Java Bean的实用工具, 它就是JAXB XJC, 下面我通过一个需求案例来详细地介绍一下这个工具的使用方式.

需求描述

根据下面的book-store.xsd文件逆向生成Java Bean
生成的Java Bean类名要求以大驼峰命名, 如BookStore, Book
生成的Java Bean的属性, 如果其类型是List, 其名称要以List结尾, 如bookList

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:complexType abstract="false" name="BOOK-STORE">
        <xsd:sequence>
            <xsd:element minOccurs="1" maxOccurs="1" type="xsd:ID" name="ID"/>
            <xsd:element minOccurs="1" maxOccurs="1" type="xsd:string" name="NAME"/>
            <xsd:element minOccurs="1" maxOccurs="unbounded" type="BOOK" name="BOOK"/>
        </xsd:sequence>
    </xsd:complexType>
    <xsd:complexType abstract="false" name="BOOK">
        <xsd:sequence>
            <xsd:element minOccurs="1" maxOccurs="1" type="xsd:ID" name="ID"/>
            <xsd:element minOccurs="1" maxOccurs="1" type="xsd:string" name="NAME"/>
            <xsd:element minOccurs="1" maxOccurs="1" type="xsd:string" name="AUTHOR"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:schema>

确认需求之后, 我们就一步一步地演示一下如何通过XJC工具来实现上述的需求.

开发环境

JDK8

通过命令使用XJC

首先需要确认一下我们是否拥有XJC的环境(JDK8已经内置了该工具), 在终端输入以下命令确认

xjc -version

如果控制台有正常的输出XJC的版本信息, 则表示我们拥有XJC的环境
在确保拥有XJC的环境后, 我们通过输入以下命令执行xsd文件逆向生成Java Bean的功能

xjc -d . -p com.xuxu.book.store book-store.xsd

命令参数解析:
-d: 生成的文件将放入此目录中(默认当前目录)
-p: 指定目标程序包
执行以上完命令后, 我们可以得到以下Java源文件
image.png
image.png
现在我们已经实现了根据xsd逆向生成Java Bean的功能, 但是目前生成的类却不满足需求对类名和属性名的约束条件(类名必须是大驼峰格式, 属性类型如果是List, 其名称需以List结尾). 此时, 我们就需要用到XJC的binding文件了(.xjb文件), 通过这个文件, 我们可以修改xsd中元素的名称.
编写book-store.xjb文件, 其代码如下

<?xml version="1.0" encoding="UTF-8"?>
<jaxb:bindings version="1.0"
               xmlns:xs="http://www.w3.org/2001/XMLSchema"
               xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
               xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc">
    <jaxb:bindings schemaLocation="./book-store.xsd">
        <jaxb:bindings node="//xs:complexType[@name='BOOK-STORE']">
            <jaxb:class name="BookStore"/>
        </jaxb:bindings>
        <jaxb:bindings node="//xs:complexType[@name='BOOK']">
            <jaxb:class name="Book"/>
        </jaxb:bindings>
        <jaxb:bindings node="//xs:element[@name='BOOK']">
            <jaxb:property name="bookList"/>
        </jaxb:bindings>
    </jaxb:bindings>
</jaxb:bindings>

接下来我们再执行XJC命令, 加上binding文件的参数, 命令如下

xjc -d . -p com.xuxu.book.store -b book-store.xjb book-store.xsd

执行完上述命令后, 我们会得到以下Java源文件
image.png
image.png
可以看到, 这次我们生成的Java Bean已经完全满足了需求. 下面我们简单的总结一下这种方式的优缺点
优点: 简单、便捷, 容易上手;
缺点: 如果xsd文件很大, 那么我们手动去编写binding文件也是一项非常麻烦的工作
介于通过命令行使用XJC的缺点, 我们还有没有其它的方式能够实现上述需求呢?当然是有的, 接下来, 徐徐就再介绍一种以编程的方式来使用XJC.

通过编程使用XJC

首先需要引入xjc的依赖

<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-xjc</artifactId>
    <version>2.3.3</version>
</dependency>

然后通过XJC库编写生成Java Bean的代码, 具体如下

public static void main(String[] args) throws Exception {
    // 创建SchemaCompiler对象
    SchemaCompiler sc = XJC.createSchemaCompiler();
    // 设置错误监听器, 当有错误发生时, 可以定制处理逻辑
    sc.setErrorListener(new CustomErrorListener());

    // 获取Options对象, 用于设置参数
    Options options = ((SchemaCompilerImpl) sc).getOptions();

    // 设置定制的名称转化器
    options.setNameConverter(new CustomNameConverter(), null);

    // 设置生成的Java Bean的输出目录(注意: 这个目录必须存在)
    String outputDir = "/path/to/your/output/dir";
    String[] arguments = new String[]{"-d", outputDir};
    options.parseArgument(arguments, 0);

    URL xsd = ModelGenerator.class.getResource("/book-store.xsd");
    Objects.requireNonNull(xsd);
    sc.parseSchema(new InputSource(xsd.toExternalForm()));

    // 设置生成的Java Bean的包
    sc.forcePackageName("com.xuxu.book.store");
    JCodeModel jCodeModel = sc.bind().generateCode(null, null);
    jCodeModel.build(options.targetDir);
    System.out.println("Generate models success.");
}

CustomErrorListener的代码如下

public class CustomErrorListener implements ErrorListener {

    @Override
    public void error(SAXParseException exception) {
        System.out.println("error: " + exception);
    }

    @Override
    public void fatalError(SAXParseException exception) {
        System.out.println("fatalError: " + exception);
    }

    @Override
    public void warning(SAXParseException exception) {
        System.out.println("warning: " + exception);
    }

    @Override
    public void info(SAXParseException exception) {
        System.out.println("info: " + exception);
    }
}

CustomNameConverter的代码如下, 我们生成驼峰样式的类名和属性名的逻辑就是在这里实现的

public class CustomNameConverter implements NameConverter {

    private static String toCamelCase(String str) {
        String camelCase = CharSequenceUtil.toCamelCase(str.toLowerCase(), '-');
        return CharSequenceUtil.upperFirst(camelCase);
    }

    @Override
    public String toClassName(String token) {
        return toCamelCase(token);
    }

    @Override
    public String toInterfaceName(String token) {
        return toCamelCase(token);
    }

    @Override
    public String toPropertyName(String token) {
        return toCamelCase(token);
    }

    @Override
    public String toConstantName(String token) {
        return standard.toConstantName(token);
    }

    @Override
    public String toVariableName(String token) {
        return standard.toVariableName(token);
    }

    @Override
    public String toPackageName(String namespaceUri) {
        return standard.toPackageName(namespaceUri);
    }
}

运行程序后, 我们可以得到以下Java源文件
image.png
image.png
可以看到, 我们生成的类名虽然满足了需求, 但是字段名还是不符合预期. 这里我们还需要配置一个全局的binding文件(配置这个文件后, XJC会对复数形式的字段名称做特殊处理)
编写global.xjb文件, 其代码如下

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
           xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
           jaxb:version="1.0"
           jaxb:extensionBindingPrefixes="xjc">
    <xs:annotation>
        <xs:appinfo>
            <jaxb:globalBindings>
                <xjc:simple/>
            </jaxb:globalBindings>
        </xs:appinfo>
    </xs:annotation>
</xs:schema>

修改启动类代码, 加入binding文件

public static void main(String[] args) throws Exception {
    // 创建SchemaCompiler对象
    SchemaCompiler sc = XJC.createSchemaCompiler();
    // 设置错误监听器, 当有错误发生时, 可以定制处理逻辑
    sc.setErrorListener(new CustomErrorListener());

    // 获取Options对象, 用于设置参数
    Options options = ((SchemaCompilerImpl) sc).getOptions();

    URL globalXjb = ModelGenerator.class.getResource("/global.xjb");
    Objects.requireNonNull(globalXjb);
    options.addBindFile(new File(globalXjb.toURI()));

    // 设置定制的名称转化器
    options.setNameConverter(new CustomNameConverter(), null);

    // 设置生成的Java Bean的输出目录(注意: 这个目录必须存在)
    String outputDir = "/path/to/your/output/dir";
    String[] arguments = new String[]{"-d", outputDir};
    options.parseArgument(arguments, 0);

    URL xsd = ModelGenerator.class.getResource("/book-store.xsd");
    Objects.requireNonNull(xsd);
    sc.parseSchema(new InputSource(xsd.toExternalForm()));

    // 设置生成的Java Bean的包
    sc.forcePackageName("com.xuxu.book.store");
    JCodeModel jCodeModel = sc.bind().generateCode(null, null);
    jCodeModel.build(options.targetDir);
    System.out.println("Generate models success.");
}

编写完成后, 我们再运行代码, 得到了以下的Java Bean
image.png
可以看到, 这次book属性变成了books, 虽然还是不正确, 但是离我们的期望越来越近了.
通过翻阅XJC的源码, 我们可以发现, 控制复数形式字段名称的生成是由com.sun.codemodel.JJavaName#getPluralForm这个方法决定的, 但是这个方法并没有提供扩展, 因此我们并不能对其进行修改, 所以我们只有在我们的工程中, 新建一个包名和类名都与com.sun.codemodel.JJavaName一至的类, 并且修改它的getPluralForm方法. 具体代码如下
image.png
修改完成后, 我们再运行代码, 就会得了满足我们需求的Java Bean了


好了, 今天的分享就到此结束了~
如这个篇文章对你有帮助, 记得点赞关注哟~

  • 50
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值