在微服务项目开发过程中经常碰到这种情况,实体的有些字段来自于别的服务(如机构名称等、被许多服务引用)。对于这种问题最常见的处理方式就是在Service
层调用机构服务
查询到机构名称set
到实体的属性中。这种方式有个很大弊端就是Service
层充斥着大量的冗余代码,导致代码看起来非常臃肿,然后就想到利用AOP
自动填充这些属性,以消除重复代码。
思路一
整体思路是,创建两个注解@EnableFillField
和@FillField
。@EnableFillField
用在方法上,用于标识需要进行填充属性
的方法。@FillField
用在实体属性上,标识需要自动填充值
的属性。而后通过切面
拦截修改方法的返回值,以达到自动填充实体属性
的目的。
实现代码
- 创建
@EnableFillField
注解
/**
* 允许填充属性
*
* @date 2022-12-28
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableFillField {
}
- 创建
@FillField
注解@FillField
注解中包含两个属性fillFieldType
和fromField
。fillFieldType
属性用于标识填充属性的类型,如机构名称、文件信息等。fromField
属性用于标识根据那个字段值来填充,这个可以不填,不填的时候根据默认属性来填充。
/**
* 标识需要填充属性的字段
*
* @date 2022-12-28
**/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FillField {
/**
* 填充字段类型
*/
FillFieldType fillFieldType();
/**
* 根据那个字段值来填充(如机构名称需要根据机构编号属性值来填充)
*/
String fromField() default "";
}
- 创建
FillFieldTypeEnum
枚举类,枚举填充的类型。
/**
* 填充字段类型枚举
*
* @date 2022-12-28
**/
@Getter
@AllArgsConstructor
public enum FillFieldTypeEnum {
/**
* 机构名称
*/
UNIT_NAME("unitNo"),
/**
* 文件
*/
FILE("fileId");
/**
* 默认字段
*/
private final String defaultField;
}
创建EnableFillFieldAspect
切面类
EnableFillFieldAspect
切面使用环绕通知@Around
拦截方法返回值,并通过反射填充实体的属性
。
/**
* 填充属性切面
*
* @date 2022-12-28
**/
@Aspect
@Component
@Slf4j
public class EnableFillFieldAspect {
@Autowired
private UnitService unitService;
@Autowired
private FileService fileService;
@Around(value = "@annotation(cn.example.aop.EnableFillField)")
public Object fillField(ProceedingJoinPoint pjp) throws Throwable {
Object proceed = pjp.proceed();
try {
if (proceed instanceof Page) {
Page page = (Page) proceed;
List content = page.getContent();
for (Object o : content) {
fill(o);
}
}
if (proceed instanceof List) {
List content = (List) proceed;
for (Object o : content) {
fill(o);
}
}
if (proceed instanceof BaseEntity) {
fill(proceed);
}
} catch (Throwable throwable) {
String methodName = pjp.getSignature().getName();
log.error("{} 填充属性失败!", methodName, throwable);
}
return proceed;
}
/**
* 填充实体属性
*
* @param obj 待填充属性的对象
* @date 2022-12-28
*/
private void fill(Object obj) throws IllegalAccessException, NoSuchFieldException {
Class<?> aClass = obj.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
if (declaredField.isAnnotationPresent(FillField.class)) {
FillField fillField = declaredField.getAnnotation(FillField.class);
FillFieldTypeEnum fillFieldTypeEnum = fillField.fillFieldType();
String fromFieldName = fillField.fromField();
if (fromFieldName == null || fromFieldName.isEmpty()) {
fromFieldName = fillFieldTypeEnum.getDefaultField();
}
Field fromField = aClass.getDeclaredField(fromFieldName);
declaredField.setAccessible(true);
fromField.setAccessible(true);
Object fromFieldValue = fromField.get(obj);
Object value = null;
// 根据不同的填充类型进行处理获得待填充的属性值
switch (fillFieldTypeEnum) {
case UNIT_NAME:
value = unitService.getUnitByUnitNo((String) fromFieldValue).getUnitName();
break;
case FILE:
value = fileService.getFileById((String) fromFieldValue);
default:
}
// 填充属性
declaredField.set(obj, value);
}
}
}
}
使用示例
在实体上加上@FillField
注解
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApprovalEntity extends BaseEntity {
/**
* 机构编号
*/
private String unitNo;
/**
* 机构编号名称
*/
@FillField(fillFieldType = FillFieldTypeEnum.UNIT_NAME)
private String unitName;
/**
* 文件编号
*/
private String fileId;
/**
*
*/
@FillField(fillFieldType = FillFieldTypeEnum.FILE)
private FileInfo fileInfo;
}
在方法上加上@EnableFillField
注解
@Slf4j
@Service
public class ApprovalServiceImpl implements ApprovalService {
@Autowired
private ApprovalDao approvalDao;
@Override
@EnableFillField
public ApprovalEntity getApprovalById(String id){
return approvalDao.getApprovalById(id);
}
}
思路二
上面这种实现思路一个弊端就是对于返回值是Map
的方法无法使用。另一个实现思路是将@FillField
注解放在@EnableFillField
里面,使@FillField
注解成为@EnableFillField
的属性。在@EnableFillField
中定义所有需要填充的属性。
由于@FillField
注解不在注在实体属性上了,所以需要增加一个属性,用于标识需要自动填充的字段。
修改后的@FillField
注解
/**
* 标识需要填充属性的字段
*
* @date 2022-12-28
**/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FillField {
/**
* 填充字段类型
*
* @date 2022-12-28
*/
FillFieldTypeEnum fillFieldType();
/**
* 需要自动填充属性的字段
*
* @return java.lang.String
* @date 2022-12-28
*/
String fillField();
/**
* 根据那个字段值来填充(如机构名称需要根据机构编号属性值来填充)
*
* @return java.lang.String
* @date 2022-12-28
*/
String fromField() default "";
}
修改后的@EnableFillField
注解
/**
* 允许填充属性
*
* @date 2022-12-28
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableFillField {
FillField[] value();
}
修改后的EnableFillFieldAspect
切面类
/**
* 填充属性切面
*
* @date 2022-12-28
**/
@Aspect
@Component
@Slf4j
public class EnableFillFieldAspect {
@Autowired
private UnitService unitService;
@Autowired
private FileService fileService;
@Around(value = "@annotation(cn.example.aop.EnableFillField)")
public Object fillField(ProceedingJoinPoint pjp) throws Throwable {
Object proceed = pjp.proceed();
try {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
EnableFillField enableFillField = method.getAnnotation(EnableFillField.class);
FillField[] fillFields = enableFillField.value();
if (proceed instanceof Page) {
Page page = (Page) proceed;
List content = page.getContent();
for (Object o : content) {
fill(o, fillFields);
}
}
if (proceed instanceof List) {
List content = (List) proceed;
for (Object o : content) {
fill(o, fillFields);
}
}
if (proceed instanceof BaseEntity) {
fill(o, fillFields);
}
} catch (Throwable throwable) {
String methodName = pjp.getSignature().getName();
log.error("{} 填充属性失败!", methodName, throwable);
}
return proceed;
}
/**
* 填充实体属性
*
* @param obj 待填充属性的对象
* @param fillFields 带填充的字段集合
* @date 2022-12-28
*/
private void fill(Object obj, FillField[] fillFields) throws IllegalAccessException, NoSuchFieldException {
Class<?> aClass = obj.getClass();
for (FillField fillField : fillFields) {
String fillFieldName = fillField.fillField();
if (Objects.isNull(fillFieldName) || fillFieldName.isEmpty()) {
return;
}
String fromFieldName = fillField.fromField();
if (Objects.isNull(fromFieldName) || fromFieldName.isEmpty()) {
String defaultFieldName = fillField.fillFieldType().getDefaultField();
if (Objects.isNull(defaultFieldName) || defaultFieldName.isEmpty()) {
return;
}
fromFieldName = defaultFieldName;
}
Field declaredField = aClass.getDeclaredField(fillFieldName);
Field fromField = aClass.getDeclaredField(fromFieldName);
declaredField.setAccessible(true);
fromField.setAccessible(true);
String fromFieldValue = (String) fromField.get(obj);
if (Objects.isNull(fromFieldValue) || fromFieldValue.isEmpty()) {
return;
}
Object value = null;
// 根据不同的填充类型进行处理获得待填充的属性值
switch (fillField.fillFieldType()) {
case UNIT_NAME:
value = unitService.getUnitByUnitNo((String) fromFieldValue).getUnitName();
break;
case FILE:
value = fileService.getFileById((String) fromFieldValue);
default:
}
// 填充属性
declaredField.set(obj, value);
}
}
}
使用方式
@Slf4j
@Service
public class ApprovalServiceImpl implements ApprovalService {
@Autowired
private ApprovalDao approvalDao;
@Override
@EnableFillField(value = {
@FillField(fillFieldType= FillFieldTypeEnum.UNIT_NAME, fillField="unitName"),
@FillField(fillFieldType= FillFieldTypeEnum.FILE, , fillField="fileInfo" fromField = "fileId"),
})
public ApprovalEntity getApprovalById(String id){
return approvalDao.getApprovalById(id);
}
}