使用AOP+反射自动填充实体属性

221 阅读4分钟

在微服务项目开发过程中经常碰到这种情况,实体的有些字段来自于别的服务(如机构名称等、被许多服务引用)。对于这种问题最常见的处理方式就是在Service层调用机构服务查询到机构名称set到实体的属性中。这种方式有个很大弊端就是Service层充斥着大量的冗余代码,导致代码看起来非常臃肿,然后就想到利用AOP自动填充这些属性,以消除重复代码。

思路一

整体思路是,创建两个注解@EnableFillField@FillField@EnableFillField用在方法上,用于标识需要进行填充属性的方法。@FillField用在实体属性上,标识需要自动填充值的属性。而后通过切面拦截修改方法的返回值,以达到自动填充实体属性的目的。

实现代码

  1. 创建@EnableFillField注解
/**
 * 允许填充属性
 *
 * @date 2022-12-28
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableFillField {
    
}
  1. 创建@FillField注解 @FillField注解中包含两个属性fillFieldTypefromFieldfillFieldType属性用于标识填充属性的类型,如机构名称、文件信息等。fromField属性用于标识根据那个字段值来填充,这个可以不填,不填的时候根据默认属性来填充。
/**
 * 标识需要填充属性的字段
 *
 * @date 2022-12-28
 **/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FillField {

    /**
     * 填充字段类型
     */
    FillFieldType fillFieldType();

    /**
     * 根据那个字段值来填充(如机构名称需要根据机构编号属性值来填充)
     */
    String fromField() default "";
    
}
  1. 创建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);
    }

}