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