大家好, 我是徐徐!
今天给大家分享一个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源文件
现在我们已经实现了根据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源文件
可以看到, 这次我们生成的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源文件
可以看到, 我们生成的类名虽然满足了需求, 但是字段名还是不符合预期. 这里我们还需要配置一个全局的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
可以看到, 这次book属性变成了books, 虽然还是不正确, 但是离我们的期望越来越近了.
通过翻阅XJC的源码, 我们可以发现, 控制复数形式字段名称的生成是由com.sun.codemodel.JJavaName#getPluralForm这个方法决定的, 但是这个方法并没有提供扩展, 因此我们并不能对其进行修改, 所以我们只有在我们的工程中, 新建一个包名和类名都与com.sun.codemodel.JJavaName一至的类, 并且修改它的getPluralForm方法. 具体代码如下
修改完成后, 我们再运行代码, 就会得了满足我们需求的Java Bean了
好了, 今天的分享就到此结束了~
如这个篇文章对你有帮助, 记得点赞关注哟~