项目介绍
ns-cn/delines:基于正则匹配和反射实现解析各种文本(单行或多行)文件转Java实体
- 特性1、多种基础数据类型支持
- 特性2、可自定义类型,支持自定义decoder,支持@EntityCreator方式生成实例,可使用Spring Bean方式
- 特性3、支持单行文本与多种映射实体匹配执行
- 特性4、支持模型应用范围指定(指定行行或则指定正则形式)
- 特性5、支持单行和文本的流和字符串文本的映射解析
- 特性6、支持匹配事件拦截(匹配成功回调执行)
- 特性7、原生级嵌套支持(since V1.0.2)
- 特性8、支持编译过程的校验(正则格式校验,必填项校验,@EntityCreator校验(since V1.0.2))
�关键词:$反射$、$正则$、$设计模式$、$编译过程$
功能演示
1
2
3
4
5
6
7
8
9
10
11
|
public class TestDelines {
public static void main(String[] args) {
String[] lines = new String[]{"P01 小明 14 M 19990909 1999年9月9日", "P02 小霞 15 F 19990919 1999年09月19日"};
Person person = null;
for (String line : lines) {
person = Delines.with(line, Person.class);
System.out.println(person);
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class TestDelinesDocument {
public static void main(String[] args) {
String text = "成绩单\n" +
"P01 小明 14 M 19990903\n" +
"语文 73\n数学 92\n" +
"P02 小霞 15 F 19980706\n" +
"语文 64\n数学 94\n" +
"P03 小文 15 M 19981212\n" +
"语文 90\n数学 73\n";
DelinesDocument document = DelinesDocument.of(text);
Consumer<DelinesDocument> print = (doc) -> {
List<Person> people = document.getFoundEntities(Person.class);
if (people != null) {
Person person = people.get(people.size() - 1);
List<Score> scores = document.getFoundEntities(Score.class);
System.out.printf("%s%s\n", person.toString(), scores);
document.getBusEntity(Score.class).cleanEntities();
}
};
document.registerDelinesEntity(Person.class, ((bus, entity) -> {
print.accept(document);
return true;
}))
.registerDelinesEntity(Score.class)
.consume();
print.accept(document);
}
}
|
设计模式
介绍应用到delines中的创建型设计模式
单例模式
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点;
饿汉模式
在类加载的时候就对实例进行初始化,没有线程安全问题;获取实例的静态方法没有使用同步,调用效率高;但是没有使用懒加载,如果该实例从始至终都没被使用过,则会造成内存浪费。
1
2
3
4
5
6
7
|
public class Singleton {
private static Singleton instance = new Singleton();
private Snigleton() {}
public static Singleton getInstance() {
return instance;
}
}
|
懒汉模式
在第一次使用的时候才进行初始化,达到了懒加载的效果;由于获取实例的静态方法用synchronized修饰,所以也没有线程安全的问题;但是,这种写法每次获取实例都要进行同步(加锁),因此效率较低。
1
2
3
4
5
6
7
8
9
10
|
public class Singleton {
private static Singleton instance;
private Snigleton() {}
public static synchronized Singleton getInstance() {
if (instance==null){
instance = new Singleton();
}
return instance;
}
}
|
双重检测机制(DCL)
在第一次使用的时候才进行初始化,达到了懒加载的效果;在进行初始化的时候会进行同步(加锁),因此没有线程安全问题;并且只有第一次进行初始化才进行同步,因此不会有效率方面的问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class Singleton {
private static volatile Singleton instance;
private Snigleton() {}
public static Singleton getInstance() {
if (instance==null){
synchronized (Singleton.class){
if (instance==null) {
instance = new Singleton();
}
}
}
return instance;
}
}
|
静态内部类(延迟初始化占位类)
JVM将推迟SingletonHolder的初始化操作,直到开始使用这个类时才初始化,并且由于通过一个静态初始化来初始化Singleton,因此不需要额外的同步。当任何一个线程第一次调用getInstance时,都会使SingletonHolder被加载和被初始化,此时静态初始化器将执行Singleton的初始化操作。
1
2
3
4
5
6
7
8
9
|
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Snigleton() {}
public static synchronized Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
|
枚举
很简洁的一种实现方式,提供了序列化机制,保证线程安全,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。
1
2
3
|
public enum Singleton {
INSTANCE;
}
|
总结
方式 |
优点 |
缺点 |
饿汉模式 |
线程安全、效率高 |
非懒加载 |
懒汉模式 |
线程安全、懒加载 |
效率低 |
双重检测 |
线程安全、懒加载、效率高 |
无 |
静态内部类 |
线程安全、懒加载、效率高 |
无 |
枚举 |
线程安全、效率高 |
非懒加载 |
工厂模式
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public final class DefaultDecoderFactory implements IDecoderFactory {
private static final Map<Class<?>, IDelinesDecoder> decoders = new ConcurrentHashMap<>();
public DefaultDecoderFactory() {
}
/**
* 获取目标类型的解析器实例
*
* @param clazz 解析器的类型
* @return 解析器实例
*/
@Override
public IDelinesDecoder get(Class<? extends IDelinesDecoder> clazz) {
IDelinesDecoder decoder = decoders.get(clazz);
if (decoder != null) {
return decoder;
}
try {
decoder = IEntityFactory.build(clazz);
decoders.put(clazz, decoder);
return decoder;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
|
一种通过反射创建实例的思想
常用生成实体的方法:通过构造器new、通过反射获取构造器newInstance。特殊的,如果使用Spring,可以使用BeanFactory获取指定类型的实体。
delines也提供了基于spring方式获取实体的功能,delines-spring
通过给定注解用于标识静态方法生成实例,从而实现对指定实体进行定制
1
2
3
4
5
6
7
8
|
/**
* 实体生成器
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EntityCreator {
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
public interface IEntityFactory<T> {
static <T> T build(Class<T> clazz) {
if (clazz == null) {
return null;
}
Method[] methods = clazz.getDeclaredMethods();
Method creator = null;
for (Method method : methods) {
EntityCreator entityCreator = method.getAnnotation(EntityCreator.class);
if (entityCreator != null) {
if (creator != null) {
throw new RuntimeException("@EntityCreator can be only used once in each class");
}
if (!Modifier.isStatic(method.getModifiers())
|| !Modifier.isPublic(method.getModifiers())
|| method.getParameterCount() != 0
|| method.getReturnType() != clazz) {
throw new RuntimeException("@EntityCreator can be used on only zero-args" +
" public static method with returning class itself");
}
creator = method;
}
}
if (creator != null) {
try {
T t = (T) creator.invoke(null, new Object[]{});
if (t == null) {
throw new RuntimeException("annotated @EntityCreator but returning null");
}
return t;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Constructor<?>[] constructors = clazz.getConstructors();
try {
return clazz.newInstance();
} catch (Exception exception) {
for (Constructor<?> constructor : constructors) {
if (constructor.getParameterCount() == 0) {
constructor.setAccessible(true);
try {
Object o = constructor.newInstance(new Object[]{});
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
throw new RuntimeException("now constructor or @EntityCreator zero-args public static method declared");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public final class DefaultDecoderFactory implements IDecoderFactory {
private final Map<Class<?>, IDelinesDecoder> decoders = new ConcurrentHashMap<>();
public static DefaultDecoderFactory customized(){
decoders.put(SimpleDecoder.class, SimpleDecoder.getInstance());
return new DefaultDecoderFactory();
}
public DefaultDecoderFactory() {
}
/**
* 获取目标类型的解析器实例
*
* @param clazz 解析器的类型
* @return 解析器实例
*/
@Override
public IDelinesDecoder get(Class<? extends IDelinesDecoder> clazz) {
IDelinesDecoder decoder = decoders.get(clazz);
if (decoder != null) {
return decoder;
}
try {
decoder = IEntityFactory.build(clazz);
decoders.put(clazz, decoder);
return decoder;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
|
注解处理器
注解处理器(Annotation Processor)是javac的一个工具,不管是运行时注解还是编译时注解,都会通过处理器在编译时进行扫描和处理注解。
常用的场景:生成代码(lombok)、编译过程检查(例如@Override检查)
自定义注解处理器
自定义注解处理器详细步骤:声明处理器、注册处理器、编译过程运行1、 声明处理器:继承自AbstractProcessor
- @SupportedAnnotationTypes({“com.tangyujun.delines.annotation.DelinesField”}):声明注解处理器处理的目标注解类型
- @SupportedSourceVersion(SourceVersion.RELEASE_8):指定使用的Java版本,也可以通过AbstractProcessor#getSupportedSourceVersion()指定
自定义注解处理器需要作为当前待编译项目以外的class文件(例如maven作为依赖引入),即编译当前项目以外已经编译好注解处理器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
@SupportedAnnotationTypes({"com.tangyujun.delines.annotation.DelinesField"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class DelinesFieldPatternProcessor extends AbstractProcessor {
public DelinesFieldPatternProcessor() {
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.WARNING, "delines field pattern checking");
Set<? extends Element> elements = env.getElementsAnnotatedWith(DelinesField.class);
messager.printMessage(Diagnostic.Kind.NOTE, "total found: " + elements.size());
boolean success = true;
try {
for (Element element : elements) {
if (element.getKind().equals(ElementKind.FIELD)) {
VariableElement variableElement = (VariableElement) element;
DelinesField field = variableElement.getAnnotation(DelinesField.class);
if (field != null) {
if (CharSequenceUtil.isEmpty(field.regExp())) {
messager.printMessage(Diagnostic.Kind.ERROR, "@DelinesField without regExp assigned!"
, element);
} else {
try {
if (CharSequenceUtil.isNotBlank(field.regExp())) {
Pattern.compile(field.regExp());
}
} catch (Exception e) {
messager.printMessage(Diagnostic.Kind.ERROR,
"@DelinesField with wrong pattern: " + field.regExp(),
element);
success = false;
}
}
}
}
}
} catch (Exception e) {
messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
}
return success;
}
}
|
2、注册处理器:可选手动注册、自动注册(使用Google的AutoService)
1
2
3
4
|
// 在resources/META-INF/services目录下新建javax.annotation.processing.Processor文件
// 在javax.annotation.processing.Processor文件中逐行注册处理器完整类名
com.tangyujun.delines.processor.DelinesFieldPatternProcessor
com.tangyujun.delines.processor.DelinesEntityPatternProcessor
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 1、引入Google的AutoService
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
<version>1.0.1</version>
<scope>compile</scope>
</dependency>
// 2、在自定义注解处理器上手动增加@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.tangyujun.delines.annotation.DelinesField"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class DelinesFieldPatternProcessor extends AbstractProcessor {
// ... ...
}
|
3、编译编程自动运行
delines编译过程自定义注解处理器运行结果示例
注解处理器应用场景
注解生成代码 |
@Getter、@Setter等Lombok的应用 |
注解验证代码结构 |
@Override、@Deprecated等结构的验证 |
注解内容验证 |
delines中@DelinesField、@DelinesEntity中正则格式验证 |
delines现有注解处理器实现情况
- 所有有正则的位置的校验
- 应用范围的声明校验(正则类型和数字类型校验)
- 自定义类型的解析器实现情况校验
- 嵌套类型的模型检查,嵌套内无需要映射的字段则编译异常
开放讨论
讨论1:字符串转各种类型的方式
- delines方式:通过类型判断并用类型的parse方法
- 其他(待讨论)
讨论2:delines功能建议
- 原生级内联模型嵌套支持(现有可通过自定义类型解析器实现)
- 原生级解析结果校验
- 逐行文本的匹配前处理和匹配后处理
讨论3:delines在FIMS中的应用场景
- 日志或报文的解析
- 拓展阅读:Excel的解析工具类:alibaba/easyexcel
- 其他可能场景