找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1938

积分

0

好友

272

主题
发表于 7 天前 | 查看: 20| 回复: 0

在使用MyBatis进行开发时,你是否好奇过:

  • MyBatis如何将ResultSet数据自动映射到Java对象?
  • 它是如何在运行时动态操作对象属性的?
  • 为什么MyBatis能够处理各种复杂的嵌套属性?

这一切的背后,都离不开其强大而精巧的反射模块。作为MyBatis框架的“核心基础设施”,反射模块承担了对象操作的重任,是理解MyBatis工作原理的关键。

一、MyBatis整体架构与反射模块

1.1 反射模块在MyBatis中的位置

MyBatis整体架构图与反射模块位置

从上图所示的MyBatis整体架构中可以看到,整个框架采用了清晰的分层设计。反射模块(Reflection)位于基础支撑层,与类型处理、日志、缓存等模块并列,为上层的数据映射、SQL执行等核心功能提供底层的对象操作支持。

1.2 反射模块的五大核心职责

  • 对象属性访问 - 通过反射高效读写对象属性。
  • 对象实例化 - 动态创建各种类型的对象实例。
  • 类型解析 - 分析并缓存类的类型信息,包括泛型。
  • 元数据获取 - 获取方法、字段、构造器等元数据。
  • 性能优化 - 通过缓存反射信息,避免重复解析,大幅提升性能。

1.3 为什么需要反射模块?

试想一下,如果没有它,MyBatis将如何工作?

  • 场景1:结果映射
    ResultSet → 提取数据 → 通过反射设置到Java对象
  • 场景2:参数传递
    Java对象通过反射获取属性值 → 设置到PreparedStatement
  • 场景3:对象创建
    Mapper返回类型通过反射创建实例 → 填充数据
  • 场景4:元数据解析
    XML配置 → 解析类名 → 通过反射获取类信息

没有反射模块,上述每一个操作都需要开发者手工编写大量重复且易错的样板代码。反射模块的价值就在于封装复杂度,提供统一、高效的API

1.4 Java反射基础回顾

在深入MyBatis的实现之前,先快速回顾一下标准Java反射API的基本用法:

// 获取Class对象
Class<?> clazz = User.class;

// 创建实例(需要无参构造)
Object obj = clazz.newInstance();

// 获取方法
Method method = clazz.getMethod("setName", String.class);

// 调用方法
method.invoke(obj, "张三");

// 获取字段(即使是私有的)
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(obj, "张三");

原生反射API虽然强大,但直接使用存在性能开销大、代码冗长、异常处理繁琐等问题。MyBatis的反射模块正是在此基础上进行了深度优化和封装。

二、反射模块架构

MyBatis反射模块核心组件架构图

2.1 反射模块组成

反射模块是一个精心设计的组件集合,职责分明:

  • Reflection(反射模块)
    • ├── ObjectFactory(对象工厂) - 负责对象创建
    • ├── PropertyTokenizer(属性分词器) - 解析复杂属性表达式
    • ├── PropertyCopier(属性复制器) - 对象间属性复制
    • ├── Reflector(反射器) - 核心,缓存类反射信息
    • ├── MetaClass(元数据类) - 封装Reflector,提供友好API
    • ├── MetaObject(元对象) - 统一入口,支持多种对象类型
    • └── SystemMetaObject(系统元对象) - 工具类

2.2 ObjectFactory - 对象工厂

ObjectFactory 定义了创建对象实例的接口,其设计简洁而强大:

public interface ObjectFactory {
    // 创建对象实例(无参)
    <T> T create(Class<T> type);

    // 创建对象实例(带构造参数)
    <T> T create(Class<T> type, 
                 Class<?>[] classes, 
                 Object[] values);

    // 判断是否为集合类型
    <T> boolean isCollection(Class<T> type);
}

默认实现 DefaultObjectFactory 的核心逻辑清晰:

public class DefaultObjectFactory implements ObjectFactory {
    @Override
    public <T> T create(Class<T> type) {
        try {
            // 尝试调用无参构造器
            return type.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(
                "Error creating instance of " + type, e);
        }
    }

    @Override
    public <T> T create(Class<T> type, 
                        Class<?>[] classes, 
                        Object[] values) {
        try {
            // 查找匹配参数类型的构造器
            Constructor<?> constructor = 
                type.getDeclaredConstructor(classes);
            constructor.setAccessible(true);
            return (T) constructor.newInstance(values);
        } catch (Exception e) {
            // 如果带参构造失败,则回退到无参构造
        }
        return create(type);
    }
}

2.3 PropertyTokenizer - 属性分词器

这个精巧的类用于解析像 user.address.cityorders[0].itemName 这样的复杂属性表达式。

public class PropertyTokenizer 
    implements Iterator<PropertyTokenizer> {

    private String fullname;     // 完整属性表达式,如 “user.address.city”
    private String name;         // 当前层名称,如第一次迭代是 “user”
    private String indexedName;  // 索引名称,如 “list”
    private String index;        // 索引值,如 “0”
    private String children;     // 剩余子属性,如第一次迭代后是 “address.city”

    public PropertyTokenizer(String fullname) {
        this.fullname = fullname;

        // 解析属性表达式,以 '.' 分割
        int delim = fullname.indexOf('.');
        if (delim > -1) {
            name = fullname.substring(0, delim);
            children = fullname.substring(delim + 1);
        } else {
            name = fullname;
            children = null;
        }

        indexedName = name;
        // 解析索引 例如:list[0]
        delim = name.indexOf('[');
        if (delim > -1) {
            index = name.substring(delim + 1, 
                                   name.length() - 1);
            name = name.substring(0, delim);
        }
    }

    @Override
    public boolean hasNext() {
        return children != null; // 还有子属性需要继续解析
    }

    @Override
    public PropertyTokenizer next() {
        return new PropertyTokenizer(children); // 递归解析下一层
    }
}

使用示例:

String expression = "user.address.city";
PropertyTokenizer tokenizer = 
    new PropertyTokenizer(expression);

while (tokenizer.hasNext()) {
    System.out.println(
        "Current: " + tokenizer.getName());
    tokenizer = tokenizer.next();
}
// 输出:
// Current: user
// Current: address
// Current: city

2.4 PropertyCopier - 属性复制器

PropertyCopier 用于在两个对象之间复制属性值,其实现依赖于元数据类:

public class PropertyCopier {
    public void copyProperties(Object source, 
                               Object target) {
        // 获取源和目标对象的元数据
        MetaClass sourceMetaClass = 
            MetaClass.forClass(source.getClass());
        MetaClass targetMetaClass = 
            MetaClass.forClass(target.getClass());

        // 获取源对象的所有可读属性名
        String[] getterNames = 
            sourceMetaClass.getGetterNames();

        for (String name : getterNames) {
            // 检查目标对象是否有对应的setter方法
            if (targetMetaClass.hasSetter(name)) {
                try {
                    // 获取源对象属性值
                    Object value = sourceMetaClass
                        .getGetter(name)
                        .invoke(source);
                    // 设置到目标对象
                    targetMetaClass
                        .getSetter(name)
                        .invoke(target, value);
                } catch (Exception e) {
                    // 忽略无法复制的属性(如类型不匹配)
                }
            }
        }
    }
}

三、对象属性访问

MyBatis对象属性访问与元对象工作流程

3.1 Reflector - 反射器

Reflector 是整个反射模块的心脏,它会在类首次被访问时,解析并缓存其所有的反射信息,避免后续重复解析带来的性能损耗。

public class Reflector {
    // 类类型
    private final Class<?> type;

    // 可读属性映射:属性名 -> getter方法调用器
    private final Map<String, Invoker> getMethods 
        = new HashMap<>();

    // 可写属性映射:属性名 -> setter方法调用器
    private final Map<String, Invoker> setMethods 
        = new HashMap<>();

    // 属性类型映射
    private final Map<String, Class<?>> getTypes 
        = new HashMap<>();
    private final Map<String, Class<?>> setTypes 
        = new HashMap<>();

    // 所有已识别属性名的集合
    private final Set<String> properties 
        = new HashSet<>();

    public Reflector(Class<?> clazz) {
        this.type = clazz;
        addDefaultConstructor(clazz); // 查找默认构造器
        addGetMethods(clazz);         // 扫描并缓存所有getter
        addSetMethods(clazz);         // 扫描并缓存所有setter
        addFields(clazz);             // 补充通过字段直接访问的属性
    }
}

添加getter方法的核心逻辑展示了其如何遵循JavaBean规范:

private void addGetMethods(Class<?> clazz) {
    Method[] methods = clazz.getDeclaredMethods();

    for (Method method : methods) {
        // 处理 getXxx() 方法
        if (method.getName().startsWith("get") 
            && method.getParameterTypes().length == 0) {

            String name = method.getName().substring(3);
            name = Character.toLowerCase(name.charAt(0)) 
                   + name.substring(1);
            addGetMethod(name, method);
        } 
        // 处理 isXxx() 布尔方法
        else if (method.getName().startsWith("is") 
            && method.getParameterTypes().length == 0
            && method.getReturnType() == boolean.class) {

            String name = method.getName().substring(2);
            name = Character.toLowerCase(name.charAt(0)) 
                   + name.substring(1);
            addGetMethod(name, method);
        }
    }
}

3.2 MetaClass - 元数据类

MetaClassReflector 之上提供了一个更友好、更安全的API层,隐藏了底层缓存的细节。

public class MetaClass {
    private final Reflector reflector;

    // 获取属性值
    public Object getValue(String name, Object obj) {
        try {
            Invoker method = 
                reflector.getGetInvoker(name);
            return method.invoke(obj);
        } catch (Exception e) {
            throw new RuntimeException(
                "Error getting property '" + name + "'", e);
        }
    }

    // 设置属性值
    public void setValue(String name, 
                         Object obj, 
                         Object value) {
        try {
            Invoker method = 
                reflector.getSetInvoker(name);
            method.invoke(obj, value);
        } catch (Exception e) {
            throw new RuntimeException(
                "Error setting property '" + name + "'", e);
        }
    }

    // 获取属性的getter返回类型
    public Class<?> getGetterType(String name) {
        return reflector.getGetterType(name);
    }

    // 检查属性是否有getter/setter
    public boolean hasGetter(String name) {
        return reflector.hasGetter(name);
    }

    public boolean hasSetter(String name) {
        return reflector.hasSetter(name);
    }
}

3.3 MetaObject - 元对象

MetaObject 是反射模块对外的统一门面(Facade)。它最大的特点是能够透明地处理不同类型的对象(JavaBean、Map、Collection),并支持嵌套属性访问。

public abstract class MetaObject {
    private Object originalObject;
    private MetaClass metaClass;
    private ObjectWrapper objectWrapper;

    // 获取属性值(支持嵌套)
    public Object getValue(String name) {
        PropertyTokenizer prop = 
            new PropertyTokenizer(name);

        if (prop.hasNext()) {
            // 处理嵌套属性,递归获取
            return metaClass.getValue(
                prop.getName(), originalObject);
        } else {
            // 当前层属性,委托给ObjectWrapper
            return objectWrapper.get(prop);
        }
    }

    // 设置属性值(支持嵌套)
    public void setValue(String name, Object value) {
        PropertyTokenizer prop = 
            new PropertyTokenizer(name);

        if (prop.hasNext()) {
            metaClass.setValue(
                prop.getName(), originalObject, value);
        } else {
            objectWrapper.set(prop, value);
        }
    }

    // 工厂方法:根据对象类型创建对应的MetaObject
    public static MetaObject forObject(
        Object object,
        ObjectFactory objectFactory,
        ObjectWrapperFactory objectWrapperFactory) {

        if (object == null) {
            return SystemMetaObject.NULL_META_OBJECT;
        }

        // 根据对象类型分发创建逻辑
        if (object instanceof Map) {
            return new MapMetaObject(
                (Map) object, 
                objectFactory, 
                objectWrapperFactory);
        } else if (object instanceof Collection) {
            return new CollectionMetaObject(
                (Collection) object,
                objectFactory,
                objectWrapperFactory);
        } else {
            // 普通JavaBean
            return new BeanMetaObject(
                object,
                objectFactory,
                objectWrapperFactory);
        }
    }
}

四、对象实例化

MyBatis对象实例化策略与ObjectFactory详解

4.1 ObjectFactory创建对象

在实际使用中,ObjectFactory 让对象创建变得非常简单:

ObjectFactory objectFactory = 
    new DefaultObjectFactory();

// 创建简单对象(调用无参构造)
User user = objectFactory.create(User.class);

// 创建带参对象
Class<?>[] classes = {String.class, String.class};
Object[] values = {"张三", "zhangsan@example.com"};
User user = objectFactory.create(
    User.class, classes, values);

// 创建集合对象(ObjectFactory知道如何实例化接口)
List<User> users = 
    objectFactory.create(ArrayList.class);

4.2 处理特殊类型

我们可以通过扩展 DefaultObjectFactory 来处理接口或抽象类等特殊类型的实例化需求:

public class CustomObjectFactory 
    extends DefaultObjectFactory {

    @Override
    public <T> T create(Class<T> type) {
        // 处理接口类型
        if (type.isInterface()) {
            if (type.equals(List.class)) {
                return (T) new ArrayList<>();
            } else if (type.equals(Map.class)) {
                return (T) new HashMap<>();
            }
            throw new RuntimeException(
                "Cannot create instance of interface: " 
                + type);
        }

        // 处理抽象类
        if (Modifier.isAbstract(type.getModifiers())) {
            throw new RuntimeException(
                "Cannot create instance of abstract class: "
                + type);
        }

        return super.create(type);
    }
}

4.3 对象包装器 - ObjectWrapper

ObjectWrapper 接口定义了统一的属性访问契约,使得 MetaObject 能够以相同的方式操作Bean、Map和Collection。

public interface ObjectWrapper {
    // 获取属性值
    Object get(PropertyTokenizer prop);

    // 设置属性值
    void set(PropertyTokenizer prop, Object value);

    // 获取属性类型
    Class<?> getSetterType(String name);

    // 判断是否存在setter/getter
    boolean hasSetter(String name);
    boolean hasGetter(String name);
}

BeanWrapper 是处理普通JavaBean的实现,其 get 方法展示了如何处理嵌套属性:

public class BeanWrapper extends BaseWrapper 
    implements ObjectWrapper {

    private final Object object;
    private final MetaClass metaClass;

    @Override
    public Object get(PropertyTokenizer prop) {
        try {
            Invoker method = metaClass.getGetInvoker(
                prop.getName());
            Object value = method.invoke(object);

            if (prop.hasNext() && value != null) {
                // 递归获取嵌套属性:为子对象创建新的MetaObject
                return MetaObject
                    .forObject(value, ...)
                    .getValue(prop.getChildren());
            }
            return value;
        } catch (Throwable t) {
            throw new RuntimeException(
                "Error getting property '" 
                + prop.getName() + "'", t);
        }
    }
    // ... set方法逻辑类似,支持嵌套设置
}

五、类型解析

MyBatis类型系统:别名注册、处理器与转换流程

5.1 TypeParameterResolver - 泛型解析

这个工具类用于解析复杂的泛型类型信息,确保在映射时能获取到准确的类型。

public class TypeParameterResolverImpl 
    implements TypeParameterResolver {

    private final Type type;

    @Override
    public Type resolveType(Type rawType, Type jniType) {
        // 解析泛型类型
        if (rawType instanceof Class) {
            return resolveClass((Class<?>) rawType);
        } else if (rawType instanceof ParameterizedType) {
            return resolveParameterizedType(
                (ParameterizedType) rawType);
        } else if (rawType instanceof GenericArrayType) {
            return resolveGenericArrayType(
                (GenericArrayType) rawType);
        }
        return rawType;
    }
}

5.2 TypeAliasRegistry - 类型别名

TypeAliasRegistry 维护了一个别名到实际类名的映射,极大地简化了XML配置。

public class TypeAliasRegistry {
    // 别名(小写)到类型的映射
    private final Map<String, Class<?>> typeAliases 
        = new HashMap<>();

    // 注册类型别名
    public void registerAlias(String alias, 
                             Class<?> value) {
        if (alias == null) {
            throw new TypeException(
                "Type alias cannot be null");
        }
        typeAliases.put(alias, value);
    }

    // 根据别名解析类型
    public Class<?> resolveAlias(String string) {
        if (string == null) {
            return null;
        }

        String key = string.toLowerCase(Locale.ENGLISH);
        Class<?> value = typeAliases.get(key);

        if (value == null) {
            try {
                // 如果别名未注册,尝试直接加载类
                return Class.forName(string);
            } catch (ClassNotFoundException e) {
                throw new TypeException(
                    "Could not resolve type alias '" 
                    + string + "'", e);
            }
        }
        return value;
    }
}

5.3 TypeHandlerRegistry - 类型处理器

TypeHandlerRegistryJDBC类型与Java类型转换的中央枢纽。

public class TypeHandlerRegistry {
    // 多层映射:Java类型 -> JDBC类型 -> 具体的TypeHandler
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> 
        typeHandlerMap = new HashMap<>();

    // 获取类型处理器
    public <T> TypeHandler<T> getTypeHandler(
        Type type, 
        JdbcType jdbcType) {

        Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap 
            = typeHandlerMap.get(type);

        if (jdbcTypeHandlerMap != null) {
            TypeHandler<?> handler = 
                jdbcTypeHandlerMap.get(jdbcType);
            if (handler != null) {
                return (TypeHandler<T>) handler;
            }
        }
        // 找不到对应类型的处理器,返回默认的Object处理器
        return getTypeHandler(Object.class);
    }
}

六、元数据获取

元数据获取流程:Reflector、MetaClass与MetaObject的协作

6.1 获取类信息

反射模块需要获取类的基本信息以进行后续操作。

public class ClassInfo {
    public static void analyze(Class<?> clazz) {
        System.out.println("类名: " + clazz.getName());
        System.out.println("简单名称: " + clazz.getSimpleName());
        System.out.println("包名: " + clazz.getPackage().getName());
        System.out.println("父类: " + clazz.getSuperclass().getName());
        System.out.println("接口: " + Arrays.toString(clazz.getInterfaces()));

        // 获取修饰符
        int modifiers = clazz.getModifiers();
        System.out.println("是否为public: " + Modifier.isPublic(modifiers));
        System.out.println("是否为abstract: " + Modifier.isAbstract(modifiers));
    }
}

6.2 获取方法信息

准确识别getter和setter方法是实现属性映射的基础。

public class MethodInfo {
    // 判断是否为getter方法
    private static boolean isGetter(Method method) {
        String name = method.getName();
        return (name.startsWith("get") 
                && name.length() > 3 
                && method.getParameterTypes().length == 0)
            || (name.startsWith("is") 
                && name.length() > 2 
                && method.getParameterTypes().length == 0
                && method.getReturnType() == boolean.class);
    }

    // 判断是否为setter方法
    private static boolean isSetter(Method method) {
        String name = method.getName();
        return name.startsWith("set") 
            && name.length() > 3 
            && method.getParameterTypes().length == 1
            && method.getReturnType() == void.class;
    }

    // 从方法名提取属性名
    private static String getPropertyName(Method method) {
        String name = method.getName();
        if (name.startsWith("get") || name.startsWith("set")) {
            name = name.substring(3);
        } else if (name.startsWith("is")) {
            name = name.substring(2);
        }
        // 将首字母转为小写(遵循JavaBean规范)
        return Character.toLowerCase(name.charAt(0)) 
               + name.substring(1);
    }
}

6.3 ReflectorFactory - 工厂模式

ReflectorFactory 采用工厂模式管理 Reflector 实例,并实现全局缓存,这是性能优化的核心。

public class ReflectorFactory {
    // 使用并发Map缓存Reflector,键为Class对象
    private final ConcurrentMap<Class<?>, Reflector> 
        reflectorMap = new ConcurrentHashMap<>();

    // 获取Reflector(如果缓存中没有则创建并缓存)
    public Reflector findForClass(Class<?> type) {
        return reflectorMap.computeIfAbsent(type, k -> {
            return new Reflector(k); // 创建过程可能较耗时,但仅一次
        });
    }

    public boolean hasReflectorFor(Class<?> type) {
        return reflectorMap.containsKey(type);
    }
}

七、最佳实践

7.1 性能优化建议

  • ✅ 利用内置缓存ReflectorReflectorFactory 已实现缓存,无需自己额外缓存Class信息。
  • ✅ 优先使用高层API:尽量使用 MetaObject 而非直接操作 Reflector 或原生反射API,其内部已做优化。
  • ✅ 注意对象创建开销:在极高并发场景,可考虑池化频繁创建的对象或使用自定义ObjectFactory进行优化。
  • ✅ 延迟加载:MyBatis本身是懒加载的,确保你的实体类设计与之匹配,避免不必要的元数据解析。

7.2 使用建议

  • ✅ 首选MetaObject:进行动态属性操作时,MetaObject.forObject() 是你的首选工具。
  • ✅ 继承BaseWrapper:如需实现自定义的对象包装逻辑,继承 BaseWrapper 可以省去大量工作。
  • ✅ 扩展ObjectFactory:当你有特殊的对象实例化需求(如依赖注入、使用特定构造器),自定义ObjectFactory是标准方式。
  • ✅ 善用类型别名:在MyBatis配置文件中注册类型别名,可以使配置文件更简洁清晰。

7.3 常见问题解决

问题1:属性映射失败,提示找不到getter/setter。

// 错误原因:属性名不匹配
// User类字段:userName
// getter方法:getName() ❌ 不符合JavaBean规范

// 正确做法:确保方法名与字段名对应
public String getUserName() {  // ✅
    return userName;
}
public void setUserName(String userName) {
    this.userName = userName;
}

问题2:如何访问嵌套属性或集合元素?
MetaObject 完美支持复杂的属性表达式。

MetaObject metaObject = MetaObject.forObject(user, ...);

// 直接访问
Object address = metaObject.getValue("address");

// 嵌套访问
Object city = metaObject.getValue("address.city");

// 带索引的访问(假设orders是一个List)
Object item = metaObject.getValue("orders[0].itemName");

问题3:自定义类型如何与数据库类型转换?
通过注册自定义的 TypeHandler 来解决。

// 使用TypeHandlerRegistry处理类型转换
TypeHandlerRegistry registry = new TypeHandlerRegistry();
TypeHandler<String> handler = 
    registry.getTypeHandler(String.class);

// 设置参数到PreparedStatement
handler.setParameter(ps, 1, "张三");

// 从ResultSet获取结果
String result = handler.getResult(rs, "name");

八、总结

关键组件回顾

  • ObjectFactory - 对象工厂,负责所有对象实例的创建。
  • PropertyTokenizer - 属性分词器,解析 user.address.city 这类表达式。
  • PropertyCopier - 属性复制器,实现对象间属性拷贝。
  • Reflector - 反射器,缓存类的反射信息,性能基石。
  • MetaClass - 元数据类,封装Reflector,提供更友好的操作接口。
  • MetaObject - 元对象,统一门面,支持Bean、Map、Collection等多种类型的透明操作。

设计思想精髓

MyBatis反射模块的设计处处体现着经典软件工程思想:

  • 💎 缓存优化:通过 ReflectorFactory 等组件缓存元数据,空间换时间,避免重复反射。
  • 💎 封装复杂度:将繁琐的原生反射API封装在底层,对外提供如 MetaObject.getValue() 这样简洁的API。
  • 💎 统一接口ObjectWrapperMetaObject 定义了统一的操作契约,隔离了不同数据类型的差异。
  • 💎 扩展性强:通过工厂模式(ObjectFactory, ReflectorFactory)和注册机制(TypeAliasRegistry),允许用户轻松扩展。
  • 💎 性能优先:整个设计贯穿了“一次解析,多次使用”的原则,确保了框架在高并发下的性能。

结语
MyBatis的反射模块远不止是 Java.lang.reflect 的简单包装。它是一个经过深度优化、结构清晰、扩展性强的对象操作基础设施。理解其内部机制,不仅能帮助我们更高效地使用MyBatis,避免常见的坑,更能学习到如何在框架设计中平衡灵活性、性能与易用性。希望这篇深入剖析能成为你探索MyBatis乃至其他优秀框架设计之美的起点。

本文由云栈社区技术团队整理,旨在深度解析主流框架的核心原理。欢迎在社区交流更多关于JVM设计模式的实战经验。




上一篇:分页查询性能分析:排序与筛选条件的影响与优化
下一篇:Kali Linux 2025.4 发布:GNOME 49、Plasma 6.5 桌面与 Wayland 支持详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 11:55 , Processed in 0.310909 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表