引言

Ooder A2UI(Annotation to UI)是Ooder框架的核心组件之一,负责将Java类通过注解驱动的方式自动转换为UI组件。这一机制是Ooder实现"配置即代码"理念的关键技术,也是外界最质疑的地方:究竟是什么逻辑让它能够完成拆解?如何保证确定性?组织类方法的原则规则是什么?

本文将从第一性原理出发,深入剖析A2UI的核心设计,还原作者的设计思考过程。

A2UI 拆解机制的核心原理

1. 整体架构概览

A2UI的拆解机制基于注解驱动的反射分析,通过多层次的抽象和映射,将Java类转换为UI组件。整体架构如下:

2. 六阶段拆解流程

A2UI的拆解过程分为六个明确的阶段,每个阶段都有其特定的职责和确定性保障。

阶段1:类加载阶段

ReflectionClassLoaderBridgeClassUserReflectionClassLoaderBridgeClassUser创建BridgeClass加载Java类获取类信息返回类信息返回类对象获取所有字段返回字段列表获取所有方法返回方法列表返回BridgeClass实例

核心代码分析:

void init(Class ctClass) {
    this.ctClass = ctClass;
    this.allCtFields = new ArrayList<>();

    // 获取声明的字段(非静态、非内部类字段)
    for (Field field : ctClass.getDeclaredFields()) {
        if (!Modifier.isStatic(field.getModifiers())
                && !skipClassSet.contains(field.getDeclaringClass())
                && !field.getName().startsWith("this$")) {
            allCtFields.add(field);
        }
    }

    // 获取公共字段
    for (Field field : ctClass.getFields()) {
        if (field.getDeclaringClass().equals(ctClass)
                && !field.getName().startsWith("this$")
                && !skipClassSet.contains(field.getDeclaringClass())
                && !field.getDeclaringClass().equals(Object.class)) {
            allCtFields.add(field);
        }
    }

    // 获取声明的方法(非静态)
    this.allCtMethods = new ArrayList<>();
    List<String> methodNames = new ArrayList<>();
    for (Method method : ctClass.getDeclaredMethods()) {
        if (!Modifier.isStatic(method.getModifiers())
                && !skipClassSet.contains(method.getDeclaringClass())) {
            allCtMethods.add(method);
            methodNames.add(method.getName());
        }
    }

    // 获取公共方法(去重)
    for (Method method : ctClass.getMethods()) {
        if (!Modifier.isStatic(method.getModifiers())
                && !methodNames.contains(method.getName())
                && !skipClassSet.contains(method.getDeclaringClass())
                && !method.getDeclaringClass().equals(ctClass)
                && !method.getDeclaringClass().equals(Enum.class)
                && !method.getDeclaringClass().equals(Object.class)) {
            allCtMethods.add(method);
        }
    }
}

设计思考:

  1. 为什么区分声明字段和公共字段?
    • 声明字段:获取类自己定义的字段,包括私有字段
    • 公共字段:获取继承的公共字段,支持继承场景
  2. 为什么过滤静态成员?
    • 静态成员属于类而非实例,不适合UI绑定
    • 避免静态成员干扰UI渲染
  3. 为什么过滤内部类字段?
    • 内部类字段通常用于编译器生成的辅助逻辑
    • 避免将编译器生成的字段误认为业务字段

阶段2:注解解析阶段

核心代码分析:

MethodChinaName chinaName = AnnotationUtil.getClassAnnotation(ctClass, MethodChinaName.class);
if (chinaName != null) {
    this.methodChinaBean = new MethodChinaMeta(chinaName);
}

RepositoryAnnotation repository = AnnotationUtil.getClassAnnotation(ctClass, RepositoryAnnotation.class);
if (repository != null) {
    repositoryBean = new RepositoryMeta(repository);
}

Aggregation aggregationClass = AnnotationUtil.getClassAnnotation(ctClass, Aggregation.class);
if (aggregationClass != null) {
    aggregationBean = new AggregationBean(aggregationClass);
}

ESDEntity dsmEntityClass = AnnotationUtil.getClassAnnotation(ctClass, ESDEntity.class);
if (dsmEntityClass != null) {
    entityBean = new EntityBean(dsmEntityClass);
}

MenuBarMenu menuBarMenu = AnnotationUtil.getClassAnnotation(ctClass, MenuBarMenu.class);
if (menuBarMenu != null) {
    menuBarBean = new MenuBarMeta(menuBarMenu);
}

DBTable dbTable = AnnotationUtil.getClassAnnotation(ctClass, DBTable.class);

RequestMapping requestMapping = AnnotationUtil.getClassAnnotation(ctClass, RequestMapping.class);
if (requestMapping != null) {
    requestMappingBean = new RequestMappingBean(requestMapping);
}

View view = AnnotationUtil.getClassAnnotation(ctClass, View.class);
if (view != null) {
    viewBean = new ViewBean(view);
}

设计思考:

  1. 为什么需要这么多类级别注解?
    • 不同的注解对应不同的业务场景
    • @Aggregation:聚合视图,用于组合多个实体
    • @Repository:仓储视图,用于数据访问
    • @ESDEntity:实体视图,用于数据实体
    • @MenuBarMenu:菜单视图,用于导航菜单
    • @RequestMapping:REST视图,用于API接口
    • @View:通用视图,用于自定义视图
  2. 为什么注解解析后要转换为Bean?
    • 统一的数据结构,便于后续处理
    • 解耦注解类型和业务逻辑
    • 支持注解的版本演进

阶段3:字段拆解阶段

核心代码分析:

void initField() {
    Map<String, BridgeField> fieldMap = new LinkedHashMap<>();
    Map<String, BridgeField> disableFieldMap = new LinkedHashMap<>();
    Map<String, CustomAnnotation> customAnnotationMap = new LinkedHashMap<>();
    int index = 0;

    // 并行处理字段初始化
    List<Callable<BridgeFieldConfig>> fieldTasks = new ArrayList<>();
    for (Field field : allCtFields) {
        fieldTasks.add(new InitFieldTask<>(field, index, this));
        index++;
    }
    List<BridgeFieldConfig> fields = this.invokFieldTasks(className, fieldTasks);

    for (BridgeFieldConfig fieldInfo : fields) {
        Field field = fieldInfo.field;
        if (fieldInfo.isSerialize()) {
            fieldMap.put(field.getName().toLowerCase(), fieldInfo);
            esdFieldMap.put(fieldInfo.getFieldName(), fieldInfo);
            fieldNameList.add(fieldInfo.getFieldName());
        } else {
            disableFieldMap.put(field.getName().toLowerCase(), fieldInfo);
            disableFieldList.add(fieldInfo);
        }

        if (fieldInfo.isUid()) {
            this.uid = field.getName();
        }

        if (fieldInfo.isCaptionField()) {
            this.captionField = fieldInfo;
        }
    }
}

设计思考:

  1. 为什么使用LinkedHashMap而不是HashMap?
    • LinkedHashMap保持插入顺序
    • 保证字段的显示顺序与声明顺序一致
    • 提供确定性的字段顺序
  2. 为什么区分fieldMap和disableFieldMap?
    • fieldMap:需要序列化和显示的字段
    • disableFieldMap:不需要显示的字段(如隐藏字段、系统字段)
    • 分离关注点,提高处理效率
  3. 为什么使用并行处理?
    • 字段初始化是CPU密集型操作
    • 并行处理可以显著提高性能
    • 特别是在字段数量较多时

阶段4:方法拆解阶段

核心代码分析:

public BridgeMethodConfig(Method method, BridgeClass bridgeClass) {
    this.innerMethod = method;
    this.returnType = method.getReturnType();
    this.domainId = bridgeClass.getDomainId();
    this.bridgeClass = bridgeClass;
    this.methodName = method.getName();
    this.fieldName = MethodUtil.getFieldName(method);
    this.innerMethod = method;
    this.name = fieldName;
    this.id = fieldName;

    String fieldName = MethodUtil.getFieldName(method);

    // 查找对应的字段
    for (Field fieldInfo : this.bridgeClass.getAllCtFields()) {
        if (fieldInfo.getName().equals(fieldName)) {
            this.field = fieldInfo;
            continue;
        }
    }

    try {
        if (returnType.isArray() || Collection.class.isAssignableFrom(returnType)) {
            Class innerClass = JSONGenUtil.getInnerReturnType(method);
            if (innerClass != null) {
                returnType = innerClass;
            }
        }
        init(index, bridgeClass);
        if (this.getRefBean() != null) {
            CustomRefMeta ref = this.getRefBean();
            if (ref.getView() != null && ref.getView().equals(ViewType.DIC)) {
                componentType = ComponentType.COMBOINPUT;
            }
            this.refType = ref.getRef();
            this.viewType = ref.getView();
        }

        if (componentType == null) {
            componentType = ComponentType.COMBOINPUT;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    // 检查是否为模块方法
    ModuleAnnotation annotation = AnnotationUtil.getMethodAnnotation(method, ModuleAnnotation.class);
    if (annotation != null) {
        isModule = true;
    }

    ModuleRefFieldAnnotation comboModuleAnnotation = AnnotationUtil.getMethodAnnotation(method, ModuleRefFieldAnnotation.class);
    if (comboModuleAnnotation != null) {
        isModule = true;
    }

    APIEventAnnotation apiEventAnnotation = AnnotationUtil.getMethodAnnotation(method, APIEventAnnotation.class);
    if (apiEventAnnotation != null) {
        for (CustomMenuItem menuItem : apiEventAnnotation.bindMenu()) {
            if (!menuItem.getReturnView().equals(ModuleViewType.NONE) && menuItem.getDefaultView()) {
                isModule = true;
            }
        }
    }
}

设计思考:

  1. 为什么需要查找对应的字段?
    • 方法可能对应某个字段的Getter/Setter
    • 字段的注解可以补充方法的元数据
    • 支持字段和方法的协同工作
  2. 为什么需要处理数组和集合类型?
    • 数组和集合需要获取内部类型
    • 内部类型决定UI组件类型
    • 支持泛型类型的正确处理
  3. 为什么需要检查是否为模块方法?
    • 模块方法返回的是UI组件
    • 需要特殊处理模块方法的渲染
    • 支持动态UI组件的生成

阶段5:类型映射阶段

核心代码分析:

public static ComponentType getComponentType(Class typeClass, Type type) {
    Class clazz = JSONGenUtil.getInnerType(type);

    if (Enumstype.class.isAssignableFrom(clazz) || clazz.isEnum()) {
        return ComponentType.COMBOINPUT;
    }

    if (typeClass != null && (typeClass.isArray() || Collection.class.isAssignableFrom(typeClass))) {
        if (Arrays.asList(customClass).contains(clazz) || typeClass.equals(clazz)) {
            return ComponentType.LIST;
        }
    }

    if (java.sql.Date.class.isAssignableFrom(clazz)) {
        return ComponentType.COMBOINPUT;
    }

    if (clazz.equals(short.class) || clazz.equals(Short.class)) {
        return ComponentType.COMBOINPUT;
    }

    if (clazz.equals(int.class) || clazz.equals(Integer.class)) {
        return ComponentType.COMBOINPUT;
    }

    if (clazz.equals(boolean.class) || clazz.equals(Boolean.class)) {
        return ComponentType.CHECKBOX;
    }

    if (clazz.equals(long.class) || clazz.equals(Long.class)) {
        return ComponentType.COMBOINPUT;
    }

    if (clazz.equals(float.class) || clazz.equals(Float.class)) {
        return ComponentType.COMBOINPUT;
    }

    if (clazz.equals(BigDecimal.class)) {
        return ComponentType.COMBOINPUT;
    }

    if (clazz.equals(BigInteger.class)) {
        return ComponentType.COMBOINPUT;
    }

    if (clazz.equals(Date.class)) {
        return ComponentType.COMBOINPUT;
    }

    if (clazz.equals(Reader.class)) {
        return ComponentType.RICHEDITOR;
    }

    if (clazz.equals(StringBuffer.class)) {
        return ComponentType.RICHEDITOR;
    }

    return ComponentType.INPUT;
}

设计思考:

  1. 为什么需要处理泛型类型?
    • 泛型类型在编译时会被擦除
    • 需要通过反射获取实际类型
    • 支持泛型集合的正确映射
  2. 为什么Boolean映射到CHECKBOX而不是INPUT?
    • CHECKBOX更符合布尔值的语义
    • 提供更好的用户体验
    • 减少用户输入错误
  3. 为什么StringBuffer映射到RICHEDITOR?
    • StringBuffer通常用于存储长文本
    • RICHEDITOR支持富文本编辑
    • 提供更好的文本编辑体验

阶段6:组件生成阶段

核心代码分析:

public C2UConfigFactory() {
    Map<String, Class?>> allClassMap = new HashMap<>();
    allClassMap.putAll(EsbBeanFactory.getInstance().getAllClass());
    Set<Map.Entry<String, Class?>>> allClass = allClassMap.entrySet();
    for (Map.Entry<String, Class?>> clazzEntry : allClass) {
        Class clazz = clazzEntry.getValue();
        try {
            initClass(clazz);
        } catch (Throwable e) {
            // 忽略初始化失败的类
        }
    }
}

void initClass(Class beanClazz) {
    CustomClass customClass = (CustomClass) beanClazz.getAnnotation(CustomClass.class);
    if (CustomBean.class.isAssignableFrom(beanClazz) && customClass != null) {
        AnnotationType ann = (AnnotationType) beanClazz.getAnnotation(AnnotationType.class);
        viewTypeAnnMap.put(ann.clazz(), customClass);
        if (UIComponent.class.isAssignableFrom(customClass.clazz())) {
            switch (customClass.viewType()) {
                case COMPONENT:
                    WidgetMetaMap.put(customClass.componentType(), beanClazz);
                    widgetComponentMap.put(customClass.componentType(), customClass.clazz());
                    if (ann != null) {
                        widgetClass.add(ann.clazz());
                        widgetAnnMap.put(customClass.componentType(), ann.clazz());
                    }
                    break;
                case COMBOBOX:
                    if (ann != null) {
                        for (ComboInputType inputType : customClass.inputType()) {
                            comboBoxAnnMap.put(inputType, ann.clazz());
                            comboBoxComponentMap.put(inputType, customClass.clazz());
                        }
                        widgetClass.add(ann.clazz());
                        widgetAnnMap.put(customClass.componentType(), ann.clazz());
                    }

                    for (ComboInputType inputType : customClass.inputType()) {
                        comboBoxBeanMap.put(inputType, beanClazz);
                    }
                    break;
            }
        }
    }
}

设计思考:

  1. 为什么需要扫描所有类?
    • 自动发现所有UI组件
    • 避免手动注册组件
    • 支持组件的热插拔
  2. 为什么使用Map存储组件映射?
    • 快速查找组件类
    • O(1)时间复杂度
    • 提高组件创建效率
  3. 为什么需要区分COMPONENT和COMBOBOX?
    • COMPONENT:独立组件(如Input、Checkbox)
    • COMBOBOX:下拉组件(如Date、Number)
    • 不同的组件类型有不同的渲染逻辑

确定性来源分析

A2UI的确定性来自以下五个层次,每个层次都有其特定的确定性保障机制。

1. 注解层确定性

注解是A2UI确定性的第一层保障,通过注解提供明确的元数据。

注解确定性示例:

@CustomAnnotation(
    caption = "用户名",
    uid = true,
    readonly = true,
    required = true
)
private String username;

// 确定性分析:
// 1. caption明确字段的显示名称
// 2. uid明确字段为主键
// 3. readonly明确字段为只读
// 4. required明确字段为必填

设计思考:

  1. 为什么注解能提供确定性?
    • 注解是编译时元数据,不会在运行时改变
    • 注解的值是明确的字符串或枚举
    • 注解的解析规则是固定的
  2. 为什么需要这么多注解?
    • 不同的注解对应不同的字段属性
    • 支持灵活的字段配置
    • 满足不同的业务场景

2. 类型层确定性

Java类型系统是A2UI确定性的第二层保障,通过类型映射提供组件选择的确定性。

类型确定性示例:

private Boolean isActive;  // -> CHECKBOX
private Integer age;      // -> COMBOINPUT
private Date birthday;    // -> COMBOINPUT
private UserStatus status; // -> COMBOINPUT (Enum)
private List<String> tags; // -> LIST

// 确定性分析:
// 1. Boolean类型必然映射到CHECKBOX
// 2. Integer类型必然映射到COMBOINPUT
// 3. Date类型必然映射到COMBOINPUT
// 4. Enum类型必然映射到COMBOINPUT
// 5. Collection类型必然映射到LIST

设计思考:

  1. 为什么类型能提供确定性?
    • Java类型系统是静态的,编译时确定
    • 类型的继承关系是明确的
    • 类型映射规则是固定的
  2. 为什么需要区分基本类型和包装类型?
    • 基本类型和包装类型在反射中处理不同
    • 包装类型支持null值
    • 基本类型有默认值

3. 命名约定层确定性

Java Bean命名约定是A2UI确定性的第三层保障,通过命名约定提供方法识别的确定性。

命名约定确定性示例:

public String getName() {
    return name;
}  // -> fieldName = "name"

public String getUserName() {
    return userName;
}  // -> fieldName = "userName"

public boolean isActive() {
    return active;
}  // -> fieldName = "active"

public void setAge(Integer age) {
    this.age = age;
}  // -> fieldName = "age"

// 确定性分析:
// 1. getName()必然对应name字段
// 2. getUserName()必然对应userName字段
// 3. isActive()必然对应active字段
// 4. setAge()必然对应age字段

设计思考:

  1. 为什么命名约定能提供确定性?
    • Java Bean规范是业界标准
    • 命名规则是明确的
    • 方法名到字段名的映射是固定的
  2. 为什么需要支持多种命名模式?
    • 支持标准的Getter/Setter
    • 支持Boolean的isXxx模式
    • 支持驼峰命名转换

4. 索引层确定性

索引属性是A2UI确定性的第四层保障,通过索引保证字段顺序的确定性。

索引确定性示例:

public class UserForm {
    private String name;        // index = 0
    private Integer age;       // index = 1
    private Date birthday;     // index = 2
    private Boolean active;    // index = 3

    @CustomAnnotation(index = 10)
    private String email;      // index = 10 (自定义索引)
}

// 确定性分析:
// 1. name字段必然在第一个位置
// 2. age字段必然在第二个位置
// 3. birthday字段必然在第三个位置
// 4. active字段必然在第四个位置
// 5. email字段必然在第十个位置(自定义索引)

设计思考:

  1. 为什么索引能提供确定性?
    • 索引是整数,可以排序
    • 索引的分配规则是固定的
    • 索引的优先级是明确的
  2. 为什么需要支持自定义索引?
    • 允许开发者调整字段顺序
    • 支持字段的灵活排列
    • 满足不同的UI需求

5. 映射规则层确定性

映射规则是A2UI确定性的第五层保障,通过映射规则提供组件选择的确定性。

映射规则确定性示例:

// ComponentType -> UIComponent
INPUT -> InputUIComponent
CHECKBOX -> CheckboxUIComponent
COMBOINPUT -> ComboInputUIComponent
RICHEDITOR -> RichEditorUIComponent

// ComboInputType -> ComboBoxMeta
number -> NumberComboBoxMeta
date -> DateComboBoxMeta
checkbox -> CheckboxComboBoxMeta
listbox -> ListboxComboBoxMeta

// ColType -> ComponentType
BOOLEAN -> CHECKBOX
TEXT -> MULTILINES
DATETIME -> COMBOINPUT

// 确定性分析:
// 1. INPUT必然映射到InputUIComponent
// 2. CHECKBOX必然映射到CheckboxUIComponent
// 3. number必然映射到NumberComboBoxMeta
// 4. BOOLEAN必然映射到CHECKBOX

设计思考:

  1. 为什么映射规则能提供确定性?
    • 映射规则是预定义的
    • 映射关系是一对一的
    • 映射规则不会在运行时改变
  2. 为什么需要多层次的映射?
    • 支持不同的抽象层次
    • 支持不同的使用场景
    • 提供灵活的映射机制

组织类方法的原则规则

A2UI组织类方法遵循六大原则,每个原则都有其特定的设计目的和实现方式。

原则1:字段优先原则

字段优先原则是指在字段和方法都存在时,优先使用字段的定义。

核心代码分析:

for (BridgeMethodConfig methodInfo : methodInfos) {
    if (methodInfo.isSerialize()) {
        if (MethodUtil.isGetMethod(methodInfo.getInnerMethod()) || methodInfo.isModule()) {
            String fieldName = methodInfo.getFieldName();
            CustomAnnotation allmapping = customAnnotationMap.get(fieldName);

            // 字段必须可见
            if (!disableFieldMap.containsKey(methodInfo.getFieldName())) {
                // 如果字段未定义或为默认字段,才使用方法
                BridgeField field = fieldMap.get(fieldName.toLowerCase());
                if (field == null
                        || !(field instanceof BridgeFieldConfig)
                        || ((BridgeFieldConfig) fieldMap.get(fieldName.toLowerCase())).isDefault()) {
                    fieldMap.put(fieldName.toLowerCase(), methodInfo);
                    esdFieldMap.put(methodInfo.getFieldName(), methodInfo);
                    if (!fieldNameList.contains(methodInfo.getFieldName())) {
                        fieldNameList.add(methodInfo.getFieldName());
                    }
                } else {
                    if (allmapping == null && (methodInfo.isCustomFiled() || methodInfo.isDynLoad())) {
                        // 优先使用字段注解
                        fieldMap.put(fieldName.toLowerCase(), methodInfo);
                    }
                }
            }
        }
    }
}

设计思考:

  1. 为什么字段优先?
    • 字段是数据的直接表示,更直观
    • 字段的类型和注解更明确
    • 减少方法解析的复杂性
  2. 什么时候使用方法?
    • 字段未定义时
    • 字段为默认字段时
    • 方法有自定义注解时

原则2:Getter/Setter识别原则

Getter/Setter识别原则是指通过方法名识别Getter和Setter方法。

核心代码分析:

this.fieldName = MethodUtil.getFieldName(method);

// MethodUtil.getFieldName() 实现逻辑
public static String getFieldName(Method method) {
    String methodName = method.getName();
    if (methodName.startsWith("get")) {
        return OODUtil.formatJavaName(methodName.substring(3), false);
    } else if (methodName.startsWith("set")) {
        return OODUtil.formatJavaName(methodName.substring(3), false);
    } else if (methodName.startsWith("is")) {
        return OODUtil.formatJavaName(methodName.substring(2), false);
    }
    return methodName;
}

设计思考:

  1. 为什么识别Getter/Setter?
    • 符合Java Bean规范
    • 提供对私有字段的访问
    • 支持动态计算字段
  2. 为什么需要三种模式?
    • getXxx:标准Getter
    • setXxx:标准Setter
    • isXxx:Boolean Getter

原则3:注解优先级原则

注解优先级原则是指不同注解之间有明确的优先级顺序。

核心代码分析:

void init(Integer index, BridgeClass bridgeClass) {
    // ...

    // CustomAnnotation - 最高优先级
    if (customAnnotation != null) {
        this.customBean = new CustomFieldMeta(customAnnotation);
        this.index = customBean.getIndex();
        this.target = customBean.getTarget();
        if (customAnnotation.hidden()) {
            componentType = ComponentType.HIDDENINPUT;
            this.hidden = true;
        }
        this.isCaption = customAnnotation.captionField();
        this.uid = customAnnotation.uid();
        // ...
    }

    // JSONField - 覆盖基本属性
    if (jsonField != null) {
        this.serialize = jsonField.serialize();
        if (!jsonField.name().equals("")) {
            this.fieldName = jsonField.name();
            this.name = jsonField.name();
        }
        if (!jsonField.name().equals("")) {
            id = jsonField.name();
        }
    }

    // 中文注解 - 最后覆盖
    if (caption == null || caption.equals("")) {
        if (methodChinaName != null) {
            caption = methodChinaName.cname();
        } else {
            if (caption.equals("")) {
                this.caption = name;
            }
        }
    }
}

设计思考:

  1. 为什么需要优先级?
    • 支持灵活的配置
    • 允许覆盖默认行为
    • 保持向后兼容性
  2. 为什么CustomAnnotation优先级最高?
    • CustomAnnotation是业务注解
    • 业务需求优先于框架需求
    • 支持自定义业务逻辑

原则4:索引排序原则

索引排序原则是指按照索引值对字段进行排序。

Index Sorting

Get All Fields

Sort by Index

Render in Order

Field1: index=0

Field2: index=1

Field3: index=2

Field4: index=10

Field1, Field2, Field3, Field4

核心代码分析:

int index = 0;
for (Field field : allCtFields) {
    fieldTasks.add(new InitFieldTask<>(field, index, this));
    index++;
}

// BridgeBaseField.java
this.index = index;

@Override
public int compareTo(BridgeField o) {
    if (index == null) {
        return -1;
    }

    if (index != null && o.getIndex() != null) {
        return this.index - o.getIndex();
    }

    return 0;
}

设计思考:

  1. 为什么需要索引排序?
    • 保证字段顺序的确定性
    • 支持自定义排序
    • 便于UI渲染
  2. 为什么使用compareTo?
    • 支持Collections.sort()
    • 提供灵活的排序机制
    • 符合Java规范

原则5:类型推断原则

类型推断原则是指根据Java类型推断UI组件类型。

核心代码分析:

public static ComponentType getComponentType(Class typeClass, Type type) {
    Class clazz = JSONGenUtil.getInnerType(type);

    if (Enumstype.class.isAssignableFrom(clazz) || clazz.isEnum()) {
        return ComponentType.COMBOINPUT;
    }

    if (typeClass != null && (typeClass.isArray() || Collection.class.isAssignableFrom(typeClass))) {
        if (Arrays.asList(customClass).contains(clazz) || typeClass.equals(clazz)) {
            return ComponentType.LIST;
        }
    }

    if (clazz.equals(boolean.class) || clazz.equals(Boolean.class)) {
        return ComponentType.CHECKBOX;
    }

    if (clazz.equals(int.class) || clazz.equals(Integer.class)) {
        return ComponentType.COMBOINPUT;
    }

    if (clazz.equals(Date.class)) {
        return ComponentType.COMBOINPUT;
    }

    if (clazz.equals(Reader.class)) {
        return ComponentType.RICHEDITOR;
    }

    if (clazz.equals(StringBuffer.class)) {
        return ComponentType.RICHEDITOR;
    }

    return ComponentType.INPUT;
}

设计思考:

  1. 为什么需要类型推断?
    • 减少注解的使用
    • 提供合理的默认值
    • 提高开发效率
  2. 为什么需要检查多种类型?
    • 支持不同的Java类型
    • 支持泛型类型
    • 支持自定义类型

原则6:组件映射原则

组件映射原则是指将组件类型映射到具体的UI组件类。

核心代码分析:

public Class<UIComponent> getComponent(ComponentType componentType) {
    Class<UIComponent> componentClass = widgetComponentMap.get(componentType);
    return componentClass;
}

void initClass(Class beanClazz) {
    CustomClass customClass = (CustomClass) beanClazz.getAnnotation(CustomClass.class);
    if (CustomBean.class.isAssignableFrom(beanClazz) && customClass != null) {
        AnnotationType ann = (AnnotationType) beanClazz.getAnnotation(AnnotationType.class);
        viewTypeAnnMap.put(ann.clazz(), customClass);
        if (UIComponent.class.isAssignableFrom(customClass.clazz())) {
            switch (customClass.viewType()) {
                case COMPONENT:
                    WidgetMetaMap.put(customClass.componentType(), beanClazz);
                    widgetComponentMap.put(customClass.componentType(), customClass.clazz());
                    if (ann != null) {
                        widgetClass.add(ann.clazz());
                        widgetAnnMap.put(customClass.componentType(), ann.clazz());
                    }
                    break;
                case COMBOBOX:
                    if (ann != null) {
                        for (ComboInputType inputType : customClass.inputType()) {
                            comboBoxAnnMap.put(inputType, ann.clazz());
                            comboBoxComponentMap.put(inputType, customClass.clazz());
                        }
                        widgetClass.add(ann.clazz());
                        widgetAnnMap.put(customClass.componentType(), ann.clazz());
                    }

                    for (ComboInputType inputType : customClass.inputType()) {
                        comboBoxBeanMap.put(inputType, beanClazz);
                    }
                    break;
            }
        }
    }
}

设计思考:

  1. 为什么需要组件映射?
    • 支持组件扩展
    • 提供组件查找机制
    • 实现组件工厂模式
  2. 为什么需要区分COMPONENT和COMBOBOX?
    • COMPONENT:独立组件
    • COMBOBOX:下拉组件
    • 不同的组件类型有不同的渲染逻辑

构建编译与列结构抽象建模

1. 构建阶段

构建阶段是指将Java类加载到内存中,并创建BridgeClass实例。

C2UConfigFactoryReflectionBridgeClassBridgeClassLoaderUserC2UConfigFactoryReflectionBridgeClassBridgeClassLoaderUser加载类获取类信息返回类信息创建BridgeClass获取字段和方法返回字段和方法初始化字段返回字段配置初始化方法返回方法配置返回BridgeClass实例

核心代码分析:

void init(Class ctClass) {
    this.ctClass = ctClass;
    this.allCtFields = new ArrayList<>();

    // 获取所有字段
    for (Field field : ctClass.getDeclaredFields()) {
        if (!Modifier.isStatic(field.getModifiers())
                && !skipClassSet.contains(field.getDeclaringClass())
                && !field.getName().startsWith("this$")) {
            allCtFields.add(field);
        }
    }

    for (Field field : ctClass.getFields()) {
        if (field.getDeclaringClass().equals(ctClass)
                && !field.getName().startsWith("this$")
                && !skipClassSet.contains(field.getDeclaringClass())
                && !field.getDeclaringClass().equals(Object.class)) {
            allCtFields.add(field);
        }
    }

    // 获取所有方法
    this.allCtMethods = new ArrayList<>();
    List<String> methodNames = new ArrayList<>();
    for (Method method : ctClass.getDeclaredMethods()) {
        if (!Modifier.isStatic(method.getModifiers())
                && !skipClassSet.contains(method.getDeclaringClass())) {
            allCtMethods.add(method);
            methodNames.add(method.getName());
        }
    }

    for (Method method : ctClass.getMethods()) {
        if (!Modifier.isStatic(method.getModifiers())
                && !methodNames.contains(method.getName())
                && !skipClassSet.contains(method.getDeclaringClass())
                && !method.getDeclaringClass().equals(ctClass)
                && !method.getDeclaringClass().equals(Enum.class)
                && !method.getDeclaringClass().equals(Object.class)) {
            allCtMethods.add(method);
        }
    }

    // 解析类级别注解
    MethodChinaName chinaName = AnnotationUtil.getClassAnnotation(ctClass, MethodChinaName.class);
    if (chinaName != null) {
        this.methodChinaBean = new MethodChinaMeta(chinaName);
    }

    RepositoryAnnotation repository = AnnotationUtil.getClassAnnotation(ctClass, RepositoryAnnotation.class);
    if (repository != null) {
        repositoryBean = new RepositoryMeta(repository);
    }

    Aggregation aggregationClass = AnnotationUtil.getClassAnnotation(ctClass, Aggregation.class);
    if (aggregationClass != null) {
        aggregationBean = new AggregationBean(aggregationClass);
    }

    // ...
}

设计思考:

  1. 为什么需要构建阶段?
    • 将类信息加载到内存
    • 创建统一的数据结构
    • 为后续处理做准备
  2. 为什么需要缓存字段和方法?
    • 避免重复反射
    • 提高性能
    • 支持懒加载

2. 编译阶段

编译阶段是指动态编译Java类,并注册到Spring容器。

SpringContainerClassLoaderJavaCompilerBridgeClassUserSpringContainerClassLoaderJavaCompilerBridgeClassUser动态编译编译Java源码加载编译后的类注册Bean注册成功返回Class对象

核心代码分析:

public void compileJavaSrc(List<JavaSrcMeta> srcBeanList, String projectName, ChromeProxy chrome) {
    D2CGenerator javaGen = D2CGenerator.getInstance(projectName);
    try {
        javaGen.compileJavaSrc(srcBeanList, chrome);
    } catch (JDSException e) {
        e.printStackTrace();
    }

    Set<Class?>> classSet = new HashSet<>();
    // 更新服务注册
    for (JavaSrcMeta srcBean : srcBeanList) {
        Class clazz = null;
        try {
            clazz = ClassUtility.loadClass(srcBean.getClassName());
            classSet.add(clazz);
            registerClass(clazz);
        } catch (ClassNotFoundException e) {
            log.warn(e.getMessage());
        }
    }
    APIConfigFactory.getInstance().dyReload(classSet);
}

public void registerClass(Class clazz) {
    if (clazz != null) {
        if (AnnotationUtil.getClassAnnotation(clazz, Aggregation.class) != null
                || AnnotationUtil.getClassAnnotation(clazz, EsbBeanAnnotation.class) != null) {
            EsbBeanFactory.getInstance().registerService(clazz);
        }
        String className = clazz.getName();
        try {
            classManager.clear(className);
            APIConfigFactory.getInstance().reload(className);
            APIFactory.getInstance().loadApiConfig(className);
            ClassUtility.loadClass(className);

        } catch (Exception e) {
        }
    }
}

设计思考:

  1. 为什么需要动态编译?
    • 支持运行时生成代码
    • 支持热部署
    • 提高开发效率
  2. 为什么需要注册到Spring容器?
    • 支持依赖注入
    • 支持AOP
    • 支持事务管理

3. 列结构抽象阶段

列结构抽象阶段是指提取字段列表,确定字段类型,建立字段映射关系。

核心代码分析:

void initField() {
    Map<String, BridgeField> fieldMap = new LinkedHashMap<>();
    Map<String, BridgeField> disableFieldMap = new LinkedHashMap<>();
    Map<String, CustomAnnotation> customAnnotationMap = new LinkedHashMap<>();
    int index = 0;

    // 并行处理字段初始化
    List<Callable<BridgeFieldConfig>> fieldTasks = new ArrayList<>();
    for (Field field : allCtFields) {
        fieldTasks.add(new InitFieldTask<>(field, index, this));
        index++;
    }
    List<BridgeFieldConfig> fields = this.invokFieldTasks(className, fieldTasks);

    for (BridgeFieldConfig fieldInfo : fields) {
        Field field = fieldInfo.field;
        if (fieldInfo.isSerialize()) {
            fieldMap.put(field.getName().toLowerCase(), fieldInfo);
            esdFieldMap.put(fieldInfo.getFieldName(), fieldInfo);
            fieldNameList.add(fieldInfo.getFieldName());
        } else {
            disableFieldMap.put(field.getName().toLowerCase(), fieldInfo);
            disableFieldList.add(fieldInfo);
        }

        if (fieldInfo.isUid()) {
            this.uid = field.getName();
        }

        if (fieldInfo.isCaptionField()) {
            this.captionField = fieldInfo;
        }
    }
}

设计思考:

  1. 为什么需要列结构抽象?
    • 统一字段的数据结构
    • 支持字段的批量处理
    • 提高字段处理的效率
  2. 为什么需要区分fieldMap和disableFieldMap?
    • fieldMap:需要显示的字段
    • disableFieldMap:不需要显示的字段
    • 分离关注点,提高处理效率

4. 建模阶段

建模阶段是指创建UI组件,建立组件层次结构,配置组件属性,绑定数据源。

核心代码分析:

public void initWidget() {
    Class? clazz = null;
    ComponentType[] skipComs = new ComponentType[]{ComponentType.TOOLBAR, ComponentType.APICALLER, ComponentType.COMBOINPUT, ComponentType.INPUT, ComponentType.LIST};
    Class<? extends Annotation>[] skipAnnotations = new Class[]{ComboInputAnnotation.class, ToolBarMenu.class, ListAnnotation.class, InputAnnotation.class, APIEventAnnotation.class};

    if (componentType == null || Arrays.asList(skipComs).contains(componentType)) {
        CustomClass customClass = C2UConfigFactory.getInstance().getWidgetCustomAnnotation(this.getAllAnnotation().toArray(new Annotation[]{}));
        if (customClass != null) {
            componentType = customClass.componentType();
        }

        if (componentType == null || componentType.equals(ComponentType.INPUT)) {
            if (this.getAnnotation(InputAnnotation.class) != null) {
                componentType = ComponentType.INPUT;
            } else if (this.getAnnotation(ListAnnotation.class) != null) {
                componentType = ComponentType.LIST;
            } else if (this.getAnnotation(ToolBarMenu.class) != null) {
                componentType = ComponentType.TOOLBAR;
            } else if (this.getAnnotation(APIEventAnnotation.class) != null) {
                componentType = ComponentType.APICALLER;
            }
        }
    }

    try {
        clazz = C2UConfigFactory.getInstance().getDefaultWidgetClass(componentType);
        Constructor constructor = null;
        try {
            constructor = clazz.getConstructor(new Class[]{BridgeField.class, Set.class});
            widgetConfig = (M) constructor.newInstance(new Object[]{this, this.getAllAnnotation()});
        } catch (NoSuchMethodException e) {
            constructor = clazz.getConstructor(new Class[]{Set.class});
            widgetConfig = (M) constructor.newInstance(new Object[]{this.getAllAnnotation()});
        }
    } catch (Throwable e) {
        e.printStackTrace();
    }
}

设计思考:

  1. 为什么需要建模阶段?
    • 创建UI组件实例
    • 建立组件层次结构
    • 配置组件属性
  2. 为什么需要多种构造函数?
    • 支持不同的初始化方式
    • 支持不同的参数组合
    • 提高灵活性

架构优势与设计哲学

1. 架构优势

A2UI架构具有以下优势:

1.1 可扩展性

设计思考:

  1. 注解扩展
    • 通过添加新的注解支持新功能
    • 注解的解析规则是统一的
    • 不需要修改核心代码
  2. 组件扩展
    • 通过添加新的UI组件支持新UI元素
    • 组件的注册机制是统一的
    • 不需要修改核心代码
  3. 类型扩展
    • 通过添加新的类型映射支持新数据类型
    • 类型映射的规则是统一的
    • 不需要修改核心代码

1.2 可维护性

设计思考:

  1. 分层架构
    • 每层有明确的职责
    • 层与层之间通过接口通信
    • 便于理解和维护
  2. 单一职责
    • 每个类只负责一个功能
    • 类的职责清晰
    • 便于测试和维护
  3. 集中规则
    • 规则集中在配置类中
    • 便于修改和扩展
    • 避免规则分散

1.3 确定性

设计思考:

  1. 五层确定性
    • 每层都有明确的确定性保障
    • 层与层之间相互补充
    • 保证结果的可预测性
  2. 有序处理
    • 字段按照声明顺序处理
    • 方法按照声明顺序处理
    • 保证处理顺序的稳定性
  3. 固定规则
    • 类型映射规则是固定的
    • 组件映射规则是固定的
    • 保证映射结果的一致性

1.4 灵活性

设计思考:

  1. 字段和方法两种定义方式
    • 支持字段定义
    • 支持方法定义
    • 满足不同的开发习惯
  2. 注解覆盖默认行为
    • 支持通过注解覆盖默认行为
    • 支持灵活的配置
    • 满足不同的业务需求
  3. 自定义组件映射
    • 支持自定义组件映射
    • 支持自定义组件
    • 满足不同的UI需求

2. 设计哲学

A2UI的设计哲学体现了以下原则:

2.1 约定优于配置

设计思考:

  1. 默认行为
    • 提供合理的默认值
    • 减少配置的工作量
    • 提高开发效率
  2. 最小配置
    • 只在必要时配置
    • 减少配置文件
    • 简化项目结构
  3. 显式覆盖
    • 需要时显式覆盖
    • 配置意图明确
    • 避免隐式行为

2.2 开闭原则

设计思考:

  1. 对扩展开放
    • 支持添加新的注解
    • 支持添加新的组件
    • 支持添加新的类型映射
  2. 对修改关闭
    • 不需要修改核心代码
    • 核心代码保持稳定
    • 保证向后兼容

2.3 依赖倒置原则

设计思考:

  1. 依赖抽象
    • 依赖BridgeField接口
    • 依赖BridgeClass接口
    • 依赖BridgeMethodConfig接口
  2. 不依赖实现
    • 不依赖具体的字段类
    • 不依赖具体的组件类
    • 不依赖具体的方法类

2.4 单一职责原则

设计思考:

  1. 一个类一个职责
    • BridgeClass:类分析
    • BridgeFieldConfig:字段配置
    • BridgeMethodConfig:方法配置
    • C2UConfigFactory:组件映射
    • OODTypeMapping:类型映射
  2. 专注的功能
    • 每个类只负责一个功能
    • 功能边界清晰
    • 便于理解和维护

总结

A2UI(Annotation to UI)是Ooder框架的核心组件,通过注解驱动的反射分析,将Java类自动转换为UI组件。其核心逻辑包括:

核心机制

  1. 六阶段拆解流程
    • 类加载阶段
    • 注解解析阶段
    • 字段拆解阶段
    • 方法拆解阶段
    • 类型映射阶段
    • 组件生成阶段
  2. 五层确定性保障
    • 注解层确定性
    • 类型层确定性
    • 命名约定层确定性
    • 索引层确定性
    • 映射规则层确定性
  3. 六大组织原则
    • 字段优先原则
    • Getter/Setter识别原则
    • 注解优先级原则
    • 索引排序原则
    • 类型推断原则
    • 组件映射原则

设计优势

  1. 可扩展性
    • 支持注解扩展
    • 支持组件扩展
    • 支持类型扩展
  2. 可维护性
    • 分层架构
    • 单一职责
    • 集中规则
  3. 确定性
    • 五层确定性保障
    • 有序处理
    • 固定规则
  4. 灵活性
    • 字段和方法两种定义方式
    • 注解覆盖默认行为
    • 自定义组件映射

设计哲学

  1. 约定优于配置
    • 提供合理的默认值
    • 最小化配置
    • 显式覆盖
  2. 开闭原则
    • 对扩展开放
    • 对修改关闭
  3. 依赖倒置原则
    • 依赖抽象
    • 不依赖实现
  4. 单一职责原则
    • 一个类一个职责
    • 专注的功能
    • 易于测试

A2UI通过这些机制和原则,实现了从Java类到UI组件的自动化转换,为Ooder框架提供了强大的"配置即代码"能力。这一设计不仅提高了开发效率,也保证了系统的可维护性和可扩展性。

© 2024 Ooder Framework. All rights reserved.

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com