利用JAVA的AOP编译时注解实现领域实体对象的通用封装

背景

在JAVA开发中,针对某一些场景,AOP(面向切面编程)往往可以带来另外一种巧妙的解决方式。对于AOP、还有JAVA注解等基础概念,这里就不啰嗦了。下面是摘取了在DDD(领域驱动设计)实践中的领域对象基础公共实现,来分享一些预编译时注解的开发经验。

设计思路

首先,我们需要确定领域模型中实体对象的基本要求。实际上,DDD作为一套理论,并没有固定的代码规范,大家也是结合自身情况与理解进行开发实现的。而我们在设计编码规范时,主要考虑如下几点:

  • 充血模型
  • 依赖倒置
  • 读写分离,即CQRS (Command and Query Responsibility Segregation)

在制定最终的实现编码规范时,我们秉着尽可能遵从DDD思想的原则上,结合实际项目规模、编码复杂度与团队协作顺畅度等考虑因素,确定了分层架构如下。

有小伙伴们会指出,基础设施层与用户接口层、以及与应用层之间没有遵从依赖倒置原则。我们是这样考虑的,只是仓储类基础设施,依赖倒置是非常适合的(Domain与Infrastructure之间的就是按照这样规定的);但是,另外有些公共的基础设施如果也要依赖倒置,所增加的公共JAR包对整个项目的开发、调试、部署带来的复杂度上升,我们认为并不是非常值得。

项目中将会有大量的Entity对象,那么如何写一套公共的Entity基础实现呢?又有什么要求呢?终于切入正题了。采用一个BasicEntity基类与三个编译时注解(Entity、Keeping、EntityParam),实现Entity的读取与业务操作封装(提供给应用层使用),以及Entity的数据更新封装(提供给基础设施层使用,即仓储接口实现)。

那不用编译时注解能不能实现呢?当然也是可以的,但是会发现在编写各个领域实体对象的代码时,要重复敲很多逻辑相似、字段又不一样的代码——低效!!!所以,我们需要一个类似lombok的@Data自动生成get和set一样,利用注解即搞定实体对象的基础公共逻辑。如此,不仅统一了领域实体对象的编码规范,也让业务开发者可以专心聚焦于业务逻辑上。

代码

1. 基类与注解定义

模块:annotation-utils

1)Entity注解

/**
 * Entity
 * 编译时注解,用于实体类,且该类型必须继承于BasicEntity基类或其子类
 * 编译时在成员属性的setter方法插入addUpdatedField调用
 */
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE})
public @interface Entity {
    /**
     * constructorAccessFlag
     * 构造方法访问标志(public/protected/private)
     */
    AccessFlag constructorAccessFlag() default AccessFlag.PUBLIC;
}

 2)Keeping注解

/**
 * Keeping
 * 编译时注解,用于实体类的成员属性
 * 标示该属性为保留属性
 */
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface Keeping {
}

3)EntityParam注解

/**
 * Command
 * 编译时注解,用于仓储实现类的方法参数
 * 检查所有参数的类型必须使用@Entity。
 * 如果参数类型是泛型,则其类型参数中至少有一个使用@Entity。如List<T>,则T的声明中必须使用@Entity
 */
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.PARAMETER})
public @interface EntityParam {
}

4)Entity基类

/**
 * BaseEntity
 * 实体对象基类
 */
public class BasicEntity {

    /**
     * getUpdatedFields
     * 获取setter方法被调用过的属性集合
     *
     * @return 获取setter方法被调用过的属性集合
     */
    public Set<String> getUpdatedFields() {
        return updatedFields;
    }

    /**
     * isUpdatedField
     * 判断属性的setter是否被调用过
     *
     * @param name 属性名称
     * @return true/false
     */
    public boolean isUpdatedField(String name) {
        return updatedFields.contains(name);
    }

    /**
     * flush
     * 清空属性更新记录,并不修改属性值
     */
    public void flush() {
        updatedFields.clear();
    }

    /**
     * extract
     * 返回一个提取对象,非保留且未调用过setter方法的属性为null
     *
     * @return 被提取保留与更新属性值的新对象
     */
    public BasicEntity extract() {
        Class<? extends BasicEntity> cls = this.getClass();
        BasicEntity target = BeanUtils.instantiateClass(cls);

        // 提取保留属性
        copyKeepingFields(target);

        // 提取已修改属性
        for (String f : updatedFields) {
            copyField(cls, f, target);
        }
        target.updatedFields = this.updatedFields;

        return target;
    }

    protected void copyKeepingFields(BasicEntity target) {}

    private Set<String> updatedFields = new HashSet<String>();

    protected void addUpdatedField(String name) {
        updatedFields.add(name);
    }

    private void copyField(Class<? extends BasicEntity> cls, String name, BasicEntity target) {
        try {
            Field field = getField(cls, name);
            assert field != null : "No field " + name;
            String methodName = name.substring(0, 1).toUpperCase() + name.substring(1);
            Method setter = getMethod(cls, "set" + methodName, field.getType());
            assert setter != null : "No set" + methodName;
            Method getter = getMethod(cls, "get" + methodName);
            assert getter != null : "No get" + methodName;
            setter.invoke(target, getter.invoke(this));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static Method getMethod(Class<?> cls, String name, Class<?>... parameterTypes) {
        try {
            return cls.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> sc = cls.getSuperclass();
            if (sc != null) {
                return getMethod(sc, name, parameterTypes);
            }
            return null;
        }
    }

    private static Field getField(Class<?> cls, String name) {
        try {
            return cls.getDeclaredField(name);
        } catch (NoSuchFieldException e) {
            Class<?> sc = cls.getSuperclass();
            if (sc != null) {
                return getField(sc, name);
            }
            return null;
        }
    }
}

5)AccessFlag枚举

/**
 * AccessFlag
 * 类成员访问标志枚举
 */
public enum AccessFlag {
    PUBLIC(Flags.PUBLIC),
    PRIVATE(Flags.PRIVATE),
    PROTECTED(Flags.PROTECTED);

    AccessFlag(long flag) {
        this.value = flag;
        this.lowercaseName = StringUtils.toLowerCase(name());
    }

    @Override
    public String toString() {
        return lowercaseName;
    }

    public Long value() { return this.value; }

    final long value;
    final String lowercaseName;
}

2. 注解编译时的处理实现

模块:annotation-processor

1)JCTree基础工具类

public class JCTreeUtils {
    private JavacTrees trees;
    public TreeMaker maker;
    public Names names;

    public JCTreeUtils(ProcessingEnvironment processingEnv) {
        trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        maker = TreeMaker.instance(context);
        names = Names.instance(context);
    }

    public JCTree getTree(Element e) {
        return trees.getTree(e);
    }

    public Name nameString(String str) {
        return names.fromString(str);
    }

    public JCTree.JCLiteral literal(Object o) {
        return maker.Literal(o);
    }

    public JCTree.JCLiteral literalNull() {
        return maker.Literal(TypeTag.BOT, null);
    }

    public JCTree.JCExpression chainDotsString(String str) {
        assert str != null : "chainDots(null)";
        String[] s = str.split("\\.");
        if (s.length == 1) {
            return maker.Ident(names.fromString(s[0]));
        }
        JCTree.JCFieldAccess jcFieldAccess = maker.Select(maker.Ident(names.fromString(s[0])), names.fromString(s[1]));
        for (int i = 2; i < s.length; i++) {
            jcFieldAccess = maker.Select(jcFieldAccess, names.fromString(s[i]));
        }
        return jcFieldAccess;
    }
}

2)Entity注解的编译时处理实现

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.example.annotation.utils.domain.Entity"})
@AutoService(Processor.class)
public class EntityProcessor extends AbstractProcessor {

    private JCTreeUtils treeUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        treeUtils = new JCTreeUtils(processingEnv);
    }

    @Override
    public synchronized boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Entity.class);
        set.forEach(element -> {
            JCTree jcTree = treeUtils.getTree(element);
            if (jcTree.getKind().equals(Tree.Kind.CLASS)) {
                long constructorAccessFlag = element.getAnnotation(Entity.class).constructorAccessFlag().value();
                jcTree.accept(new TreeTranslator() {
                    @Override
                    public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                        if (jcClassDecl.getTree().equals(jcTree)) {
                            JCTree.JCMethodDecl jcMethodDecl;
                            for (JCTree d : jcClassDecl.defs) {
                                if (d.getKind().equals(Tree.Kind.METHOD)) {
                                    jcMethodDecl = (JCTree.JCMethodDecl) d;
                                    if (jcMethodDecl.name == treeUtils.names.init) {
                                        // 构造方法设置访问标志
                                        jcMethodDecl.mods.flags = jcMethodDecl.mods.flags & ~(Flags.AccessFlags) | constructorAccessFlag;
                                    } else {
                                        // 在setter方法插入addKeepingField()调用
                                        String s = jcMethodDecl.name.toString();
                                        if (s.startsWith("set")) {
                                            Name varName = treeUtils.nameString(s.substring(3, 4).toLowerCase() + s.substring(4));
                                            JCTree.JCExpressionStatement jcStatement = treeUtils.maker.Exec(
                                                    treeUtils.maker.Apply(List.nil(),
                                                            treeUtils.chainDotsString("this.addUpdatedField"),
                                                            List.of(treeUtils.literal(varName.toString()))));
                                            jcMethodDecl.body = treeUtils.maker.Block(0, jcMethodDecl.body.getStatements().append(jcStatement));
                                        }
                                    }
                                }
                            }
                            // copyKeepingFields
                            JCTree.JCStatement jcStatement = treeUtils.maker.Exec(treeUtils.maker.Apply(List.nil(),
                                    treeUtils.chainDotsString("super.copyKeepingFields"),
                                    List.of(treeUtils.maker.Ident(treeUtils.nameString("target")))));
                            JCTree.JCBlock block = treeUtils.maker.Block(0, List.of(jcStatement));
                            JCTree.JCVariableDecl param = treeUtils.maker.VarDef(treeUtils.maker.Modifiers(Flags.PARAMETER),
                                    treeUtils.nameString("target"), treeUtils.chainDotsString("com.example.annotation.utils.domain.BasicEntity"),
                                    null);
                            param.pos = jcClassDecl.pos;    // 如果没有此行,编译会报错(Value of x -1)
                            jcMethodDecl = treeUtils.maker.MethodDef(treeUtils.maker.Modifiers(Flags.PROTECTED),
                                    treeUtils.nameString("copyKeepingFields"), treeUtils.maker.Type(new Type.JCVoidType()),
                                    List.nil(), List.of(param), List.nil(), block, null);
                            jcClassDecl.defs = jcClassDecl.defs.append(jcMethodDecl);
                        }
                        super.visitClassDef(jcClassDecl);
                    }
                });
            }
        });
        return true;
    }

}

3)Keeping注解的编译时处理实现

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.example.annotation.utils.domain.Keeping")
@AutoService(Processor.class)
public class KeepingProcessor extends AbstractProcessor {

    private JCTreeUtils treeUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        treeUtils = new JCTreeUtils(processingEnv);
    }

    @Override
    public synchronized boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Keeping.class);
        set.forEach(element -> {
            Name varName = treeUtils.nameString(element.getSimpleName().toString());
            JCTree jcTreeEncl = treeUtils.getTree(element.getEnclosingElement());
            Type clsType = ((JCTree.JCClassDecl)jcTreeEncl).sym.type;
            jcTreeEncl.accept(new TreeTranslator() {
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                    if (jcClassDecl.getTree().equals(jcTreeEncl)) {
                        for (JCTree d : jcClassDecl.defs) {
                            if (d.getKind().equals(Tree.Kind.METHOD)) {
                                JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) d;
                                if (jcMethodDecl.name.toString().equals("copyKeepingFields")) {
                                    // 插入保留属性的赋值
                                    JCTree.JCStatement jcStatement = treeUtils.maker.Exec(treeUtils.maker.Assign(
                                            treeUtils.maker.Select(treeUtils.maker.TypeCast(clsType, treeUtils.chainDotsString("target")),
                                                    varName),
                                            treeUtils.maker.Ident(varName)));
                                    jcMethodDecl.body = treeUtils.maker.Block(0, jcMethodDecl.body.getStatements().append(jcStatement));
                                    break;
                                }
                            }
                        }
                    }
                    super.visitClassDef(jcClassDecl);
                }
            });
        });
        return true;
    }

}

4)EntityParam注解的编译时处理实现

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.example.annotation.utils.domain.EntityParam")
@AutoService(Processor.class)
public class EntityParamProcessor extends AbstractProcessor {

    private Types typeUtils;
    private Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        typeUtils = processingEnv.getTypeUtils();
        messager = processingEnv.getMessager();
    }

    @Override
    public synchronized boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> paramSet = roundEnv.getElementsAnnotatedWith(EntityParam.class);
        Set<? extends Element> entitySet = roundEnv.getElementsAnnotatedWith(Entity.class);
        Set<String> entityStringSet = new HashSet();
        for (Element e : entitySet) {
            entityStringSet.add(e.asType().toString());
        }
        paramSet.forEach(element -> {
            TypeMirror paramType = element.asType();
            // 元素类型声明有@Entity
            if (typeUtils.asElement(paramType).getAnnotation(Entity.class) != null) {
                return;
            }
            String s = paramType.toString();
            int left = s.indexOf("<");
            // 元素类型是泛型
            if (left > 0) {
                String[] typeArgs = s.substring(left + 1, s.length() - 1).split("\\,(\\s)*");
                boolean ok = false;
                for (String t : typeArgs) {
                    // 泛型的参数类型声明有@Entity
                    if (entityStringSet.contains(t)) {
                        ok = true;
                        break;
                    }
                }
                if (ok) {
                    return;
                }
            }
            // 元素类型(包括泛型的参数类型)声明没有@Entity
            messager.printMessage(Diagnostic.Kind.ERROR, "Not found @Entity for @EntityParam " + s);
        });
        return true;
    }

}

3. 领域服务

模块:svc-user

1)领域实体对象(以User Account Domain Object为例)

@Data
@Entity(constructorAccessFlag = AccessFlag.PRIVATE)
public class UserAccountDo extends BasicEntity {
    @Keeping
    private Long id;

    private String phone;
    private String name;
    private Boolean removed;
    private Date removeTime;

    public static UserAccountDo get(Long id) {
        UserAccountDo userDo = new UserAccountDo();
        userDo.setId(id);
        return UserAccountDoRepositoryAware.userAccountDoRepository.get(userDo);
    }

    public static UserAccountDo register(String phone) {
        UserAccountDo userDo = new UserAccountDo();
        userDo.setPhone(phone);
        return UserAccountDoRepositoryAware.userAccountDoRepository.add(userDo);
    }

    public void save() {
        UserAccountDoRepositoryAware.userAccountDoRepository.save(this);
    }
}

2)仓储接口实现(以User Account Repository为例)

省略了用户账户的PO定义、POUpdater定义以及查询(应用层发起的Query)的仓储接口实现。

@Repository
public class UserAccountRepositoryImpl implements UserAccountDoRepository, UserQueryRepository {

    @Resource
    private UserAccountMapper userAccountMapper;

    @Override
    public UserAccountDo get(@EntityParam UserAccountDo userAccountDo) {
        PoUpdater.INSTANCE.update(userDo, userAccountMapper.selectByPrimaryKey(userAccountDo.getId()));
        userAccountDo.flush();
        return userAccountDo;
    }

    @Override
    public UserAccountDo add(@EntityParam UserAccountDo userAccountDo) {
        UserAccountPo userAccountPo = PoConverter.INSTANCE.toUserAccountPo(userAccountDo);
        int id = userAccountMapper.insertSelective(userAccountPo);
        userAccountPo.setId((long)id);
        PoUpdater.INSTANCE.update(userAccountDo, userAccountPo);
        userAccountDo.flush();
        return userAccountDo;
    }

    @Override
    public void save(@EntityParam UserAccountDo userAccountDo) {
        UserAccountDo updatedDo = (UserAccountDo)userAccountDo.extract();
        UserAccountPo userAccountPo = PoConverter.INSTANCE.toUserAccountPo(updatedDo);
        userAccountMapper.updateByPrimaryKeySelective(userAccountPo);
        userAccountDo.flush();
    }

}

3)应用层访问领域实体对象

public UserDto register(String phone) {
    UserAccountDo userDo = UserAccountDo.register(phone);
    return DtoConverter.INSTANCE.toUserDto(userDo);
}

public boolean setName(Long id, String name) {
    UserAccountDo userDo = UserAccountDo.get(id);
    userDo.setName(name);
    userDo.save();
    return true;
}

结束语

其中,注解的编译处理实现部分的开发过程是最耗时的,很多隐藏的坑都是一点一点踩出来的;调试起来也没有常规代码方便,需要先调试领域服务模块的编译过程与输出;然后才是常规的模块逻辑测试。总之,希望这些经验对有类似需要的小伙伴们起到帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值