参考资料
- 自定义AnnotationFormatterFactory实现注解方式类型转换
- Spring MVC 基于AnnotationFormatterFactory接口实现自定义的规则
目录
一. 前期准备
1.1. 自定义转换标记注解
⏹code补全注解,标记code补全到指定的位数
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface CompletedCodeAnnotation {
// 补全的位数
int completedLength();
}
⏹身份证处理注解,可将身份证号处理为一个实体类
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface IdentityCardHandleAnnotation {
}
1.2 入参form
import lombok.Data;
@Data
public class Test27Form {
private String id = "110120119";
// 若code的长度不为5,则左补全至5位
@CompletedCodeAnnotation(completedLength = 5)
private String applyNo;
}
import lombok.Builder;
import lombok.ToString;
import java.time.LocalDate;
// 通过lombok的@Builder注解来实现构建者模式创建对象
@Builder
@ToString
public class PersonInfo {
private String id;
private String sex;
private LocalDate birthday;
}
二. 实现AnnotationFormatterFactory接口,构建格式化Factory
2.1 code补全Factory
import org.apache.commons.lang3.StringUtils;
import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.Formatter;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.text.ParseException;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
@Component
public class CompletedCodeFormatterFactory implements AnnotationFormatterFactory<CompletedCodeAnnotation> {
private final Set<Class<?>> types = new HashSet<>();
// 指定格式化的Field的类型为String类型
@Override
public Set<Class<?>> getFieldTypes() {
types.add(String.class);
return types;
}
@Override
public Printer<?> getPrinter(CompletedCodeAnnotation annotation, Class<?> fieldType) {
return null;
}
@Override
public Parser<?> getParser(CompletedCodeAnnotation annotation, Class<?> fieldType) {
// 获取注解上标记的补全长度
int completedLength = annotation.completedLength();
return new CompletedCodeFormatter(completedLength);
}
// 定义一个内部类,更好的聚合
private class CompletedCodeFormatter implements Formatter<String> {
private int completedLength;
private CompletedCodeFormatter(int completedLength) {
this.completedLength = completedLength;
}
@Override
public String parse(String fieldValue, Locale locale) throws ParseException {
if (ObjectUtils.isEmpty(fieldValue)) {
return "";
}
// 使用apache.commons.lang3包补全code
return StringUtils.leftPad(fieldValue, completedLength, '0');
}
@Override
public String print(String object, Locale locale) {
return null;
}
}
}
2.2 身份证号处理Factory
import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.Formatter;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
@Component
public class IdentityCardFormatterFactory implements AnnotationFormatterFactory<IdentityCardHandleAnnotation> {
private final Set<Class<?>> types = new HashSet<>();
// 指定格式化的Field的类型为自定义的PersonInfo类型
@Override
public Set<Class<?>> getFieldTypes() {
types.add(PersonInfo.class);
return types;
}
@Override
public Printer<?> getPrinter(IdentityCardHandleAnnotation annotation, Class<?> fieldType) {
return null;
}
@Override
public Parser<?> getParser(IdentityCardHandleAnnotation annotation, Class<?> fieldType) {
return new IdentityCardHandle();
}
private class IdentityCardHandle implements Formatter<PersonInfo> {
@Override
public PersonInfo parse(String identityId, Locale locale) throws ParseException {
if (ObjectUtils.isEmpty(identityId)) {
return null;
}
// 通过构建者模式创建一个PersonInfo类
return PersonInfo.builder()
.id(identityId)
.birthday(this.getBirthday(identityId))
.sex(this.getSex(identityId))
.build();
}
@Override
public String print(PersonInfo object, Locale locale) {
return null;
}
// 从身份证号中获取出出生日期,转换为LocalDate格式
private LocalDate getBirthday(String identityId) throws ParseException {
String birthdayStr = identityId.substring(6, 14);
return LocalDate.parse(birthdayStr, DateTimeFormatter.ofPattern("yyyyMMdd"));
}
private String getSex(String identityId) {
// 偶数:女,奇数:男
return identityId.charAt(16) % 2 == 0 ? "女" : "男";
}
}
}
三. 将自定义的格式化Factory添加到Spring中
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Map;
@Configuration
public class MyWebConfigurer implements WebMvcConfigurer {
@Autowired
private ApplicationContext applicationContext;
@Override
public void addFormatters(FormatterRegistry registry) {
// 获取Ioc容器中实现了AnnotationFormatterFactory接口的所有的Bean
Map<String, AnnotationFormatterFactory> beansOfType = applicationContext.getBeansOfType(AnnotationFormatterFactory.class);
beansOfType.forEach((key, value) -> registry.addFormatterForFieldAnnotation(value));
}
}
四. 效果
⏹前台
- 需要使用表单提交数据,不能提交json数据
const formData = {
applyNo: '10',
personInfo: '370281199412164456'
};
$.ajax({
url: "/test27/test",
type: 'POST',
data: formData,
success: function (data, status, xhr) {
console.log(data);
}
});
⏹后台
@PostMapping("/test")
@ResponseBody
public Test27Form test(Test27Form form, @IdentityCardHandleAnnotation PersonInfo personInfo) {
System.out.println(form);
System.out.println(personInfo);
return form;
}
可以看到
💪前台提交的applyNo不满5位,已经被补全到5位数
💪前台提交的personInfo(身份证号),已经被处理为一个entity对象了
五. 注意点
- 上述转换方式只适用于表单提交数据或者url传参提交数据这两种方式,
- 如果是前台使用
contentType: "application/json"
,后台使用@RequestBody
来接收数据的话,不适用于此处理。