使用XStream是实现XML与Java对象的转换(4)--转换器
七、转换器(Converter)
我们程序中的POJO是千变万化的,而且需求也是千奇百怪的,所以XStream中的内置的转换器的功能不一定能够满足我们的要求,所以我们就需要自己构建转换器。
1,一个基本的转换器
有如下代码:
- import com.thoughtworks.xstream.XStream;
- import com.thoughtworks.xstream.io.xml.DomDriver;
- public class XStreamTest4 {
- public static void main(String[] args) {
- Person person = new Person();
- person.setName("张三");
- XStream xstream = new XStream(new DomDriver());
- xstream.alias("person", Person.class);
- System.out.println(xstream.toXML(person));
- }
- }
- class Person {
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String toString() {
- return getName();
- }
- }
运行结果是:
- <person>
- <name>张三</name>
- </person>
如果我们需要输出如下信息:
- <person>
- <fullname>张三</fullname>
- </person>
该怎么办?
当然,我们可以使用XStream默认的转换器
- xstream.aliasField("fullname", Person.class,"name");
,甚至我们可以直接使用注解@XStreamAlias。但是这不是我们要介绍的,我们需要创建自己的转换器PersonConverter。
PersonConverter需要有3个功能:
a)告诉XStream对象,它能够转换Person类的对象(canConvert方法)
b)能够将Person对象转换为XML(marshal方法)
c)能够将XML转换成为Person对象(unmarshal方法)
现在,我们实现第一个功能(canConvert方法):
- //告诉XStream对象,它能够转换Person类的对象
- public boolean canConvert(Class clazz) {
- return clazz.equals(Person.class);
- }
就这么简单!
然后,我们实现第二个功能(marshal方法):
- //能够将Person对象转换为XML
- public void marshal(
- Object value, //我们将要转换的对象,这里是Person对象
- HierarchicalStreamWriter writer,//用于输出XML结果的writer
- MarshallingContext context //序列化环境上下文
- ) {
- //写入顺序
- //1,强制转换为我们我们需要的类型
- Person person = (Person) value;
- //2,开始写入fullname节点,相当于写入<fullname>
- writer.startNode("fullname");
- //3,给fullname节点赋值
- writer.setValue(person.getName());
- //4,结束fullname节点,相当于写入</fullname>
- writer.endNode();
- // //如果你愿意,顺便也可以写一点其他的东西
- // writer.startNode("otherContent");
- // writer.setValue("这是一大串其他内容,你可以根据自己的需要写内容!");
- // writer.endNode();
- }
最后,我们实现第三个功能(unmarshal方法):
- //能够将XML转换成为Person对象
- public Object unmarshal(
- HierarchicalStreamReader reader,//用于读取XML的reader
- UnmarshallingContext context //反序列化环境上下文
- ) {
- //1,先创建一个Person对象
- Person person = new Person();
- //2,判断<person>节点下还有没有其他可以读取的节点
- while(reader.hasMoreChildren()){
- //3,开始读取下一个(也可能是第一个)节点,选择当前<person>节点的子节点作为当前节点
- reader.moveDown();
- //4,获取当前节点的值
- String value = reader.getValue();
- if("fullname".equals(reader.getNodeName())){
- person.setName(value);
- }
- //输出当前节点的内容
- System.out.println("node="+reader.getNodeName()+";value="+value);
- //5,返回上一层节点<person>
- reader.moveUp();
- }
- return person;
- }
于是我们的PersonConverter就是:
- package cn.tjpu.zhw.xml.xstream4;
- import com.thoughtworks.xstream.converters.Converter;
- import com.thoughtworks.xstream.converters.MarshallingContext;
- import com.thoughtworks.xstream.converters.UnmarshallingContext;
- import com.thoughtworks.xstream.io.HierarchicalStreamReader;
- import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
- public class PersonConverter implements Converter {
- //告诉XStream对象,它能够转换Person类的对象
- public boolean canConvert(Class clazz) {
- return clazz.equals(Person.class);
- }
- //能够将Person对象转换为XML
- public void marshal(
- Object value, //我们将要转换的对象,这里是Person对象
- HierarchicalStreamWriter writer,//用于输出XML结果的writer
- MarshallingContext context //序列化环境上下文
- ) {
- //写入顺序
- //1,强制转换为我们我们需要的类型
- Person person = (Person) value;
- //2,开始写入fullname节点,相当于写入<fullname>
- writer.startNode("fullname");
- //3,给fullname节点赋值
- writer.setValue(person.getName());
- //4,结束fullname节点,相当于写入</fullname>
- writer.endNode();
- // //如果你愿意,顺便也可以写一点其他的东西
- // writer.startNode("otherContent");
- // writer.setValue("这是一大串其他内容,你可以根据自己的需要写内容!");
- // writer.endNode();
- }
- //能够将XML转换成为Person对象
- public Object unmarshal(
- HierarchicalStreamReader reader,//用于读取XML的reader
- UnmarshallingContext context //反序列化环境上下文
- ) {
- //1,先创建一个Person对象
- Person person = new Person();
- //2,判断<person>节点下还有没有其他可以读取的节点
- while(reader.hasMoreChildren()){
- //3,开始读取下一个(也可能是第一个)节点,选择当前<person>节点的子节点作为当前节点
- reader.moveDown();
- //4,获取当前节点的值
- String value = reader.getValue();
- if("fullname".equals(reader.getNodeName())){
- person.setName(value);
- }
- //输出当前节点的内容
- System.out.println("node="+reader.getNodeName()+";value="+value);
- //5,返回上一层节点<person>
- reader.moveUp();
- }
- return person;
- }
- }
同时我们修改main方法文件:
- package cn.tjpu.zhw.xml.xstream4;
- import com.thoughtworks.xstream.XStream;
- import com.thoughtworks.xstream.io.xml.DomDriver;
- public class XStreamTest4 {
- public static void main(String[] args) {
- //创建Person对象
- Person person = new Person();
- person.setName("张三");
- //创建XStream对象
- XStream xstream = new XStream(new DomDriver());
- xstream.alias("person", Person.class);
- //注册PersonConverter转换器
- xstream.registerConverter(new PersonConverter());
- //使用marshal方法,将Person对象转换成为XML
- System.out.println("****使用marshal方法,将Person对象转换成为XML==>");
- String xml = xstream.toXML(person);
- System.out.println(xml);
- //使用unmarshal方法,将XML转换成为Person对象
- System.out.println();
- System.out.println("****使用unmarshal方法,将XML转换成为Person对象==>");
- Person p = (Person)xstream.fromXML(xml);
- //输出Person对象
- System.out.println();
- System.out.println("****输出Person对象==>");
- System.out.println(p);
- }
- }
- class Person {
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String toString() {
- return "Person对象的name="+getName();
- }
- }
运行结果如下:
- ****使用marshal方法,将Person对象转换成为XML==>
- <person>
- <fullname>张三</fullname>
- </person>
- ****使用unmarshal方法,将XML转换成为Person对象==>
- node=fullname;value=张三
- ****输出Person对象==>
- Person对象的name=张三
我们成功了!!!!!!
2,另一个简单的转换器
现在我们需要程序输出如下:
<person>张三</person>
该咋办?
我们再创建一个转换器:
- package cn.tjpu.zhw.xml.xstream4;
- import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
- public class AnotherPersonConverter extends AbstractSingleValueConverter {
- // 告诉XStream对象,它能够转换Person类的对象
- public boolean canConvert(Class clazz) {
- return clazz.equals(Person.class);
- }
- // 将Person对象转换为XML,参数obj就是要转换的Person对象,返回值就是<person>节点的值
- public String toString(Object obj) {
- Person p = (Person) obj;
- return p.getName();
- }
- // 将XML转换成为Person对象,参数str就是<person>节点的值,返回值就是转换得到的Person对象
- public Object fromString(String str) {
- Person person = new Person();
- person.setName(str);
- System.out.println("参数str="+str);
- return person;
- }
- }
修改Main方法:
- public class XStreamTest4 {
- public static void main(String[] args) {
- //创建Person对象
- Person person = new Person();
- person.setName("张三");
- //创建XStream对象
- XStream xstream = new XStream(new DomDriver());
- xstream.alias("person", Person.class);
- // //注册PersonConverter转换器
- // xstream.registerConverter(new PersonConverter());
- //注册AnotherPersonConverter转换器
- xstream.registerConverter(new AnotherPersonConverter());
- //使用toString方法,将Person对象转换成为XML
- System.out.println("****使用toString方法,将Person对象转换成为XML==>");
- String xml = xstream.toXML(person);
- System.out.println(xml);
- //使用fromString方法,将XML转换成为Person对象
- System.out.println();
- System.out.println("****使用fromString方法,将XML转换成为Person对象==>");
- Person p = (Person)xstream.fromXML(xml);
- //输出Person对象
- System.out.println();
- System.out.println("****输出Person对象==>");
- System.out.println(p);
- }
- }
运行结果:
- ****使用toString方法,将Person对象转换成为XML==>
- <person>张三</person>
- ****使用fromString方法,将XML转换成为Person对象==>
- 参数str=张三
- ****输出Person对象==>
- Person对象的name=张三
这正是我们预期的结果!!!
3,时间转换器
通过上面两个例子我们知道了Converter接口的简单工作方式,现在我们创建一个新的时间转换器DateConverter,这个时间转换器将以Local对象作为构造方法的参数。
这个时间转换器同样有类似PersonConverter转换器的功能:
a)告诉XStream对象,它能够转换Calendar类的对象(canConvert方法)
b)能够将Calendar对象转换为XML(marshal方法)
c)能够将XML转换成为Calendar对象(unmarshal方法)
现在实现第一个功能:
- // 告诉XStream对象,它能够转换Calendar类及其所有子类定义的对象
- public boolean canConvert(Class clazz) {
- return Calendar.class.isAssignableFrom(clazz);
- }
然后,实现第二个功能:
- // 能够将Calendar对象转换为XML
- public void marshal(Object value, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- //value是将要转换的Calendar对象
- Calendar calendar = (Calendar) value;
- Date date = calendar.getTime();
- //设定格式化格式
- DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
- this.locale);
- //格式化并输出Calendar对象
- writer.setValue(formatter.format(date));
- }
最后,实现最后一个功能:
- // 能够将XML转换成为Calendar对象
- public Object unmarshal(HierarchicalStreamReader reader,
- UnmarshallingContext context) {
- //创建一个Calendar对象
- GregorianCalendar calendar = new GregorianCalendar();
- //设定解析格式
- DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
- this.locale);
- try {
- //解析指定格式的Calendar对象
- calendar.setTime(formatter.parse(reader.getValue()));
- } catch (ParseException e) {
- throw new ConversionException(e.getMessage(), e);
- }
- //返回解析成功的Calendar对象
- return calendar;
- }
于是,我们的DateConverter定义如下:
- package cn.tjpu.zhw.xml.xstream4;
- import java.text.DateFormat;
- import java.text.ParseException;
- import java.util.Calendar;
- import java.util.Date;
- import java.util.GregorianCalendar;
- import java.util.Locale;
- import com.thoughtworks.xstream.converters.ConversionException;
- import com.thoughtworks.xstream.converters.Converter;
- import com.thoughtworks.xstream.converters.MarshallingContext;
- import com.thoughtworks.xstream.converters.UnmarshallingContext;
- import com.thoughtworks.xstream.io.HierarchicalStreamReader;
- import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
- public class DateConverter implements Converter {
- private Locale locale;
- public DateConverter(Locale locale) {
- super();
- this.locale = locale;
- }
- // 告诉XStream对象,它能够转换Calendar类及其所有子类定义的对象
- public boolean canConvert(Class clazz) {
- return Calendar.class.isAssignableFrom(clazz);
- }
- // 能够将Calendar对象转换为XML
- public void marshal(Object value, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- //value是将要转换的Calendar对象
- Calendar calendar = (Calendar) value;
- Date date = calendar.getTime();
- //设定格式化格式
- DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
- this.locale);
- //格式化并输出Calendar对象
- writer.setValue(formatter.format(date));
- }
- // 能够将XML转换成为Calendar对象
- public Object unmarshal(HierarchicalStreamReader reader,
- UnmarshallingContext context) {
- //创建一个Calendar对象
- GregorianCalendar calendar = new GregorianCalendar();
- //设定解析格式
- DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
- this.locale);
- try {
- //解析指定格式的Calendar对象
- calendar.setTime(formatter.parse(reader.getValue()));
- } catch (ParseException e) {
- throw new ConversionException(e.getMessage(), e);
- }
- //返回解析成功的Calendar对象
- return calendar;
- }
- }
再写我们的main方法:
- package cn.tjpu.zhw.xml.xstream4;
- import java.text.DateFormat;
- import java.util.Calendar;
- import java.util.GregorianCalendar;
- import java.util.Locale;
- import com.thoughtworks.xstream.XStream;
- import com.thoughtworks.xstream.io.xml.DomDriver;
- public class DateMain {
- public static void main(String[] args) {
- // 获取当前时间
- Calendar calendar = new GregorianCalendar();
- // 创建XStream对象
- XStream xstream = new XStream(new DomDriver());
- xstream.alias("date", Calendar.class);
- // 注册我们定义的时间转换器
- xstream.registerConverter(new DateConverter(new Locale("zh", "cn")));
- // 转换并输出XML
- String xml = xstream.toXML(calendar);
- System.out.println(xstream.toXML(calendar));
- // 加载XML中的时间数据
- Calendar loaded = (Calendar) xstream.fromXML(xml);
- //格式化并输出时间
- System.out.println(DateFormat.getDateInstance(DateFormat.MEDIUM).format(
- loaded.getTime()));
- }
- }
运行,结果如下:
- <date>2013年12月24日 星期二</date>
- 2013-12-24
4,更加复杂的转换器
复杂的转换器都是由一系列基本的转换器组成的,我们只需要将这些基本的转换器按照适当的顺序排列组合就行了。
下面有一个复杂的Birthday类:
- package cn.tjpu.zhw.xml.xstream4;
- import java.util.Calendar;
- public class Birthday {
- private Person person;
- private Calendar date;
- private char gender;
- public Person getPerson() {
- return person;
- }
- public void setPerson(Person person) {
- this.person = person;
- }
- public Calendar getDate() {
- return date;
- }
- public void setDate(Calendar date) {
- this.date = date;
- }
- public char getGender() {
- return gender;
- }
- public void setGenderMale() {
- this.gender = 'm';
- }
- public void setGenderFemale() {
- this.gender = 'f';
- }
- public String toString(){
- return "{person="+person+"};{date="+date.getTime()+"};{gender="+gender+"}";
- }
- }
我们将怎样转换这个类呢?
答案是,我们只需要将现有的转换器组合成一个BirthdayConverter转换器就成了!
- package cn.tjpu.zhw.xml.xstream4;
- import java.util.Calendar;
- import com.thoughtworks.xstream.converters.ConversionException;
- import com.thoughtworks.xstream.converters.Converter;
- import com.thoughtworks.xstream.converters.MarshallingContext;
- import com.thoughtworks.xstream.converters.UnmarshallingContext;
- import com.thoughtworks.xstream.io.HierarchicalStreamReader;
- import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
- public class BirthdayConverter implements Converter {
- // 告诉XStream对象,它能够转换Birthday类的对象
- public boolean canConvert(Class clazz) {
- return Birthday.class == clazz;
- }
- // 能够将Birthday对象转换为XML
- public void marshal(Object value, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- //value是将要被转换的Birthday对象
- Birthday birthday = (Birthday) value;
- //给<birthday>节点添加gender属性
- if (birthday.getGender() != '\0') {
- writer.addAttribute("gender",
- Character.toString(birthday.getGender()));
- }
- //给<birthday>节点添加子节点<person>
- if (birthday.getPerson() != null) {
- writer.startNode("person");
- context.convertAnother(birthday.getPerson());
- writer.endNode();
- }
- //给<birthday>节点添加子节点<birth>
- if (birthday.getDate() != null) {
- writer.startNode("birth");
- context.convertAnother(birthday.getDate());
- writer.endNode();
- }
- }
- // 能够将XML转换成为Birthday对象
- public Object unmarshal(HierarchicalStreamReader reader,
- UnmarshallingContext context) {
- //创建Birthday对象
- Birthday birthday = new Birthday();
- //当前节点是<birthday>
- //解析<birthday>节点的gender属性
- String gender = reader.getAttribute("gender");
- if (gender != null) {
- if (gender.length() > 0) {
- if (gender.charAt(0) == 'f') {
- birthday.setGenderFemale();
- } else if (gender.charAt(0) == 'm') {
- birthday.setGenderMale();
- } else {
- throw new ConversionException("Invalid gender value: "
- + gender);
- }
- } else {
- throw new ConversionException(
- "Empty string is invalid gender value");
- }
- }
- //遍历解析<birthday>节点的所有子节点
- while (reader.hasMoreChildren()) {
- //将下一个(也可能是第一个)子节点作为当前节点
- reader.moveDown();
- //解析<person>节点
- if ("person".equals(reader.getNodeName())) {
- Person person = (Person) context.convertAnother(birthday,
- Person.class);
- birthday.setPerson(person);
- }
- //解析<birth>节点
- else if ("birth".equals(reader.getNodeName())) {
- Calendar date = (Calendar) context.convertAnother(birthday,
- Calendar.class);
- birthday.setDate(date);
- }
- //返回到<birthday>节点作为当前节点
- reader.moveUp();
- }
- //返回解析得到的Birthday对象
- return birthday;
- }
- }
下面写main方法:
- package cn.tjpu.zhw.xml.xstream4;
- import java.util.GregorianCalendar;
- import com.thoughtworks.xstream.XStream;
- public class BirthdayMain {
- public static void main(String[] args) {
- //创建Birthday对象
- Birthday birthday = new Birthday();
- Person p = new Person();
- p.setName("张三");
- birthday.setPerson(p);
- birthday.setDate(new GregorianCalendar());
- birthday.setGenderMale();
- //创建XStream对象
- XStream xstream = new XStream();
- xstream.alias("birthday", Birthday.class);
- //注册BirthdayConverter转换器
- xstream.registerConverter(new BirthdayConverter());
- //将Birthday对象转换成为XML并输出
- String xml = xstream.toXML(birthday);
- System.out.println("**************将Birthday对象转换成为XML并输出**************");
- System.out.println(xml);
- //将XML转换成为Birthday对象
- Birthday b = (Birthday)xstream.fromXML(xml);
- //输出Birthday对象
- System.out.println();
- System.out.println("**************将XML转换成为Birthday对象**************");
- System.out.println(b);
- }
- }
运行结果:
- **************将Birthday对象转换成为XML并输出**************
- <birthday gender="m">
- <person>
- <name>张三</name>
- </person>
- <birth>
- <time>1387897906531</time>
- <timezone>Asia/Shanghai</timezone>
- </birth>
- </birthday>
- **************将XML转换成为Birthday对象**************
- {person=Person对象的name=张三};{date=Tue Dec 24 23:11:46 CST 2013};{gender=m}
使用XStream是实现XML与Java对象的转换(4)--转换器的更多相关文章
- 使用XStream是实现XML与Java对象的转换(6)--持久化
九.持久化 在第八节的示例中,当我们操作一组对象时,我们可以指定Writer.OutputStream来写出序列化后的XML数据,我们还可以指定Reader.InputStream来读取序列化后的XM ...
- 使用XStream是实现XML与Java对象的转换(3)--注解
六.使用注解(Annotation) 总是使用XStream对象的别名方法和注册转换器,会让人感到非常的乏味,又会产生很多重复性代码,于是我们可以使用注解的方式来配置要序列化的POJO对象. 1,最基 ...
- 使用XStream是实现XML与Java对象的转换(2)--别名
五.使用别名(Alias) 首先,有这样一段Java代码: import java.util.ArrayList; import java.util.List; import com.thoughtw ...
- 使用XStream是实现XML与Java对象的转换(1)--简介及入门示例
一.简单介绍 XStream是thoughtworks开发的开源框架,用于实现XML数据于Java对象.Json数据的转换.它不需要schema或其他的mapping文件就可以进行java对象和xml ...
- 使用XStream是实现XML与Java对象的转换(5)--Object Stream
八,Object Stream 之前的例子我们都是直接输出Xml成为String类型或者从String中获得并解析Xml,现在我们要处理输入流和输出流! 1,输出流(ObjectOutputStrea ...
- 不规矩的xml与JAVA对象互相转换的小技巧-使用Marshaller
摘要:将XML文档与JAVA对象互转是很常见的需求,如果XML定义很规整这很好实现.然而在现实中“不规矩”的XML可能更常见,Marshaller便无能为力了吗?下面是一个小技巧,调整一下思维便能重用 ...
- XStream轻松转换xml和java对象
首先引入所需的jar: xstream-1.4.9.xpp3_min-1.1.4c.dom4j-1.6.1, 或用maven管理jar包时在pom.xml中添加: <!-- https://mv ...
- xml-mapping xml 与 java 对象转换映射框架,像 XStream 一样优雅地读写xml
xml xml 是 java 实现的 xml 框架. 希望以最优雅的方式进行 xml 和 java 之间的转换处理,一行代码搞定一切. 特点 对象的和 xml 的互相映射 支持注解 @Alias 指定 ...
- XML 和 java对象相互转换
XML 和 java对象相互转换 博客分类: XML 和 JSON 下面使用的是JDK自带的类,没有引用任何第三方jar包. Unmarshaller 类使客户端应用程序能够将 XML 数据转换为 ...
随机推荐
- Android开发学习之路--React-Native之初体验
近段时间业余在学node.js,租了个阿里云准备搭建后端,想用node.js,偶尔得知react-native可以在不同平台跑,js在iOS和android上都可以运行ok,今天就简单学习下rea ...
- 【ShaderToy】基础篇之谈谈点、线的绘制
写在前面 写前面一篇的时候,发现还是不够基础.因此打算增加几篇基础篇,从点线面开始,希望可以更好理解. 其实用Pixel Shader的过程很像在纸上绘画的过程.屏幕上的每一个像素对应了纸上的一个方格 ...
- CentOs查看文件的几种方式
有许多命令都可以查看文件,不同的命令有不同的优点,可以针对不同的需要分别选择命令以提高效率: cat 由第一行开始显示内容,并将所有内容输出 tac 从最后一行倒序显示内容,并 ...
- AsyncTask(异步任务)讲解-android的学习之旅(四十六)
AsyncTask简介 Android的UI线程主要处理用户的按键,触屏和View的绘制等,不能在里面处理耗时的操作,否则会出现ANR,因此耗时的操作要单独开一个线程处理,但是新线程不能直接处理UI线 ...
- java设计模式---状态模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述状态(State)模式的: 状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为 ...
- 尚学堂马士兵struts2 课堂笔记(一)
06_尚学堂马士兵_Struts2_Struts2_HelloWorld_5 <constant name="struts.devMode" value="true ...
- Java 8新特性探究(三)泛型的目标类型推断
简单理解泛型 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.通俗点将就是"类型的变量".这种类型变量可以用在类.接口和方法 ...
- Java创建二叉搜索树,实现搜索,插入,删除操作
Java实现的二叉搜索树,并实现对该树的搜索,插入,删除操作(合并删除,复制删除) 首先我们要有一个编码的思路,大致如下: 1.查找:根据二叉搜索树的数据特点,我们可以根据节点的值得比较来实现查找,查 ...
- /sbin/insserv: No such file or directory
/sbin/insserv: No such file or directory在Ubuntu下安装service服务,可能会报如下错误:/sbin/insserv: No such file or ...
- 用CSS指定外部链接的样式
大部分的信息类网站,比如维基百科,都会对外部链接(<a>标签)指定特定的样式.作为用户,一眼就知道该链接是指向另一个站点的资源是很好的体验.许多网站在服务器端做外部链接检查,添加一个`re ...