Java【代码 22】反射机制处理传递给mapper文件的非Map类型参数对象(指定属性为空则设置默认值)

本文介绍了通过反射机制处理MyBatis mapper文件中非Map类型参数对象的方法。核心是通过实现Interceptor拦截器,在动态SQL执行前检查参数对象属性值并进行默认值设置。对于非Map对象,利用反射获取属性信息,通过getter/setter方法检查和设置schemaName属性值;对于Map对象则直接操作键值对。

重点展示了如何通过Field和Method类实现反射操作,并提供了参数代理、字段名转换等辅助方法,最终实现对mapper参数对象的统一处理。

1. 为什么会有这个问题

为什么要拦截传递给 mapper 文件的参数对象呢?因为要对指定属性设置默认值。如何拦截传递给 mapper 文件的参数对象可以参考《使用(org.apache.ibatis.plugin.Interceptor)拦截器实现全局参数注入》这里我们只贴出处理拦截对象的核心方法。

2. 核心代码分享

既然是核心方法,无关的@Override方法不再贴出, 参数对象是Map类型的不是重点,这里主要看一下封装对象借助反射机制通过 getter 和 setter 方法获取和设置指定属性值的操作。

java 复制代码
public class SchemaInterceptor implements Interceptor {

    /**
     * mapper.xml 使用SCHEMA时的参数名称
     */
    private static final String SCHEMA = "schemaName";

    /**
     * 设置默认的schema
     */
    private String schema = "public";

    /**
     * 拦截到的动态SQL
     */
    private Set<Integer> sourceStorage = new HashSet<>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        SqlSource sqlSource = mappedStatement.getSqlSource();
        // 只拦截动态SQL
        if (sqlSource instanceof DynamicSqlSource) {
            // 获取到sqlNode对象
            Field field = DynamicSqlSource.class.getDeclaredField("rootSqlNode");
            field.setAccessible(true);
            SqlNode sqlNode = (SqlNode) field.get(sqlSource);
            Object argParameter = args[1];
            // 获取传递给 mapper 的参数对象
            if (!sourceStorage.contains(sqlSource.hashCode()) && argParameter != null) {
                if (argParameter instanceof HashMap) {
                    // 处理 map 类型的参数
                    Map<String, Object> argMap = (Map<String, Object>) argParameter;
                    // 判断是否传递 schemaName或schema 如果已经传递则使用用户传递的值 否则使用默认值
                    String schemaNameStr = "schemaName", schemaStr = "schema";
                    if (StringUtils.isEmpty(MapUtils.getString(argMap, schemaNameStr)) && StringUtils.isEmpty(MapUtils.getString(argMap, schemaStr))) {
                        SqlNode proxyNode = proxyNode(sqlNode);
                        field.set(sqlSource, proxyNode);
                    }
                } else {
                    // 处理封装成请求对象类型的参数
                    Class<?> clz = argParameter.getClass();
                    // 获取实体类的所有属性
                    Field[] fields = clz.getDeclaredFields();
                    for (Field f : fields) {
                        String fieldName = f.getName();
                        Class<?> fieldType = f.getType();
                        // 处理 schemaName 属性
                        if (SCHEMA.equals(fieldName)) {
                            // 获取 schemaName 属性的 getter 方法
                            String methodName = getMethodName(fieldName);
                            String getName = "get" + methodName;
                            Method getFieldValue = clz.getMethod(getName);
                            String val = (String) getFieldValue.invoke(argParameter);
                            // schemaName 属性值为空则使用默认值
                            if (StringUtils.isBlank(val)) {
                                String setName = "set" + methodName;
                                Method setFieldValue = clz.getMethod(setName, fieldType);
                                setFieldValue.invoke(argParameter, schema);
                            }
                        }
                    }
                }
                sourceStorage.add(sqlSource.hashCode());
            }
        }
        return invocation.proceed();
    }

    /**
     * 通过动态代理对象 添加schema参数
     *
     * @param sqlNode SQL节点
     * @return SqlNode
     */
    private SqlNode proxyNode(SqlNode sqlNode) {
        return (SqlNode) Proxy.newProxyInstance(sqlNode.getClass().getClassLoader(),
                new Class[]{SqlNode.class}, new SqlNodeInvocationHandler(sqlNode));
    }

    private class SqlNodeInvocationHandler implements InvocationHandler {
        private SqlNode target;

        SqlNodeInvocationHandler(SqlNode target) {
            super();
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            DynamicContext context = (DynamicContext) args[0];
            setSchema(schema);
            context.getBindings().put(SCHEMA, schema);
            return method.invoke(target, args);
        }
    }

    /**
     * 给schema 添加.
     *
     * @param schema schemaName
     */
    private void setSchema(String schema) {
        String pointStr = ".";
        if (StringUtils.isNotBlank(schema)) {
            if (!schema.endsWith(pointStr)) {
                schema += pointStr;
            }
        }
        this.schema = schema;
    }

    /**
     * 将首字母变成大写
     *
     * @param fieldName 字段名称
     * @return 首字母大写的字段名称
     */
    private static String getMethodName(String fieldName) {
        byte[] items = fieldName.getBytes();
        items[0] = (byte) ((char) items[0] - 'a' + 'A');
        return new String(items);
    }

3.方法解析

拦截到的参数对象是 Object 类型,可以通过方法名获取其 getter 和 setter 方法,使用获取到的方法执行即可获取或设置指定的属性值:

java 复制代码
        Object argParameter = args[1];
        // 处理封装成请求对象类型的参数
        Class<?> clz = argParameter.getClass();
        // 获取实体类的所有属性
        Field[] fields = clz.getDeclaredFields();
        for (Field f : fields) {
        	// 属性的名称
            String fieldName = f.getName();
            // 属性的类型
            Class<?> fieldType = f.getType();
            // 处理 schemaName 属性
            if (SCHEMA.equals(fieldName)) {
                // 获取 schemaName 属性的 getter 方法
                String methodName = getMethodName(fieldName);
                // 方法名
                String getName = "get" + methodName;
                // 获取到的 getter 方法
                Method getFieldValue = clz.getMethod("get" + methodName);
                // 执行 getter 方法
                String val = (String) getFieldValue.invoke(argParameter);
                // schemaName 属性值为空则使用默认值
                if (StringUtils.isBlank(val)) {
                	// 方法名
                    String setName = "set" + methodName;
                    // 获取 setter 方法 需要传递当前属性的类型
                    Method setFieldValue = clz.getMethod(setName, fieldType);
                    // 执行 setter 方法
                    setFieldValue.invoke(argParameter, schema);
                }
            }
        }

将首字母大写:

java 复制代码
    /**
     * 将首字母变成大写
     *
     * @param fieldName 字段名称【字段的第一位必须是小写的字母】
     * @return 首字母大写的字段名称
     */
    private static String getMethodName(String fieldName) {
        byte[] items = fieldName.getBytes();
        items[0] = (byte) ((char) items[0] - 'a' + 'A');
        return new String(items);
    }

4.总结

反射机制是Java里比较重要的知识点,也能帮我们实现很多功能。

相关推荐
杨DaB2 小时前
【SpringMVC】拦截器,实现小型登录验证
java·开发语言·后端·servlet·mvc
努力的小雨8 小时前
还在为调试提示词头疼?一个案例教你轻松上手!
后端
魔都吴所谓9 小时前
【go】语言的匿名变量如何定义与使用
开发语言·后端·golang
陈佬昔没带相机9 小时前
围观前后端对接的 TypeScript 最佳实践,我们缺什么?
前端·后端·api
Livingbody11 小时前
大模型微调数据集加载和分析
后端
Livingbody11 小时前
第一次免费使用A800显卡80GB显存微调Ernie大模型
后端
Goboy12 小时前
Java 使用 FileOutputStream 写 Excel 文件不落盘?
后端·面试·架构
Goboy12 小时前
讲了八百遍,你还是没有理解CAS
后端·面试·架构
麦兜*12 小时前
大模型时代,Transformer 架构中的核心注意力机制算法详解与优化实践
jvm·后端·深度学习·算法·spring·spring cloud·transformer
树獭叔叔12 小时前
Python 多进程与多线程:深入理解与实践指南
后端·python