Mybatis的源码解析之处理占位符相关的类TokenHandler和GenericTokenParser

前言

Mybatis在处理${}和#{}占位符时,底层使用到了GenericTokenParser类和TokenHandler的实现类。它的实现原理及其简单。

GenericTokenParser

GenericTokenParser的作用是完成对字符窜中${}和#{}的内容定位,每次定位完成后,调用TokenHandler进行内容替换。

GenericTokenParser的定位原理很简单,将${}或者#{}进行拆分。${或#{部分为openToken,}部分为closeToken,然后不断使用indexOf进行循环定位替换。


package org.apache.ibatis.parsing;

/**
 * 通用属性解析器 用于解析占位符标签
 */
public class GenericTokenParser {

    /**
     * 占位符开始标志
     */
    private final String openToken;
    /**
     * 占位符结束标志
     */
    private final String closeToken;
    /**
     *
     */
    private final TokenHandler handler;

    public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
        this.openToken = openToken;
        this.closeToken = closeToken;
        this.handler = handler;
    }

    /**
     *这段代码主要处理{@code text}拥有多个符合以{@code openToken}开头,{@code closeToken}结尾的字符窜的情况
     *
     * 同时还要处理拥有{@code openToken}或{@code closeToken},但是使用了转义字符的情况。
     *
     * @param text
     * @return
     */
    public String parse(String text) {
        //非空判断
        if (text == null || text.isEmpty()) {
            return "";
        }
        // search open token
        //获取第一个{@code openToken}的位置
        int start = text.indexOf(openToken);
        //如果这个位置不存在 则直接返回原字符窜
        if (start == -1) {
            return text;
        }
        //生成原字符窜的数组
        char[] src = text.toCharArray();
        //字符窜拥有多个符合条件的{@code openToken}时,将会进行多轮分析,以确认每一轮的{@code openToken}在原字符窜的位置,而
        //offset就表示每一轮解析时,应该从原字符窜的哪个位置开始
        int offset = 0;
        //builder是拼接最后结果,进行输出的
        final StringBuilder builder = new StringBuilder();
        //expression的内容表示{@code openToken}和{@code closeToken}之间的内容
        StringBuilder expression = null;
        //下面这个循环 就是循环处理多个{@code openToken}、{@code closeToken}的情况
        while (start > -1) {

            if (start > 0 && src[start - 1] == '\\') {
                //寻找{@code openToken}的条件分支一:这一个条件判断 是处理出现了{@code openToken},但是这个{@code openToken}前面出现了转移字符
                // this open token is escaped. remove the backslash and continue.
                //这里表示既然遇到了转义字符 那么这个开始标识符不能当做开始标识符
                // 因此它不是需要替换的部分,所以就要将从本轮开启的位置 到{@code openToken}结束位置的字符都直接拼接到{@code builder}上
                builder.append(src, offset, start - offset - 1).append(openToken);
                //确认新一轮的开始位置
                offset = start + openToken.length();
            } else {
                //寻找{@code openToken}的条件分支二:下面的条件判断表示 出现了{@code openToken} 且 这个{@code openToken}前面没有转移字符的情况===
                // found open token. let's search close token.
                //重置复用expression
                if (expression == null) {
                    expression = new StringBuilder();
                } else {
                    expression.setLength(0);
                }
                //这里表示如果有转义字符 则拼接转义的开始字符到真正的开始字符之间的部分
                builder.append(src, offset, start - offset);
                //{@code openToken}找到了,接下来来需要找{@code closeToken},其实{@code closeToken}的状况和{@code openToken}
                //一样的情况
                offset = start + openToken.length();
                int end = text.indexOf(closeToken, offset);
                //遍历循环一直找{@code closeToken}的位置
                while (end > -1) {
                    if (end > offset && src[end - 1] == '\\') {
                        //寻找{@code closeToken}的分支条件一:如果找到的{@code closeToken}是拥有转义字符的,则继续寻找,但是expression需要拼接本轮解析开始
                        //位置到{@code openToken}间的字符,因为这个也属于{@code openToken}和{@code closeToken}间的内容,然后进行下一轮
                        // this close token is escaped. remove the backslash and continue.
                        expression.append(src, offset, end - offset - 1).append(closeToken);
                        offset = end + closeToken.length();
                        end = text.indexOf(closeToken, offset);
                    } else {
                        //寻找{@code closeToken}的分支条件二:这里表示找到了符合条件的{@code closeToken},那么将内容拼接到{@code expression}里
                        expression.append(src, offset, end - offset);
                        break;
                    }
                }
                if (end == -1) {
                    //综合评定 条件分支一:{@code closeToken}位置没有找到,那么结束了,直接拼接
                    // close token was not found.
                    builder.append(src, start, src.length - start);
                    offset = src.length;
                } else {
                    //综合评定 条件分支二:{@code closeToken}位置也找到了,那么说明expression里也存放好了{@code openToken}和{@code closeToken}
                    //内容,这时候用handler去处理。
                    builder.append(handler.handleToken(expression.toString()));
                    offset = end + closeToken.length();
                }
            }
            //这里表示 从offset位置 从新获取start的位置,很显然如果为0,start还是不变
            start = text.indexOf(openToken, offset);
        }
        if (offset < src.length) {
            builder.append(src, offset, src.length - offset);
        }
        return builder.toString();
    }
}

TokenHandler

TokenHandler的作用也很简单,他针对传入的字符窜获取对应的内容,至于如何获取的内容,将交由它的子类去实现。这些子类分别有BindingTokenParser、DynamicCheckerTokenParser、ParameterMappingTokenHandler、VariableTokenHandler等,这些实现类的唯一区别就是,获取content对应的数据源来自哪里。

 

public interface TokenHandler {
    //根据传入的content获取对应的内容
    String handleToken(String content);
}

DynamicCheckerTokenParser

DynamicCheckerTokenParser的作用是配合GenericTokenParaser使用的,它的目的是,一旦该对象的handleToken被调用,就证明字符窜里有符合GenericTokenParaser

的openToken和closeToken的内容。源码如下:

    /**
     * 动态标签  这个类的作用主要是看是否被调用过,如果被调用过isDynamic必为true
     */
    private static class DynamicCheckerTokenParser implements TokenHandler {

        private boolean isDynamic;

        public DynamicCheckerTokenParser() {
            // Prevent Synthetic Access
        }

        public boolean isDynamic() {
            return isDynamic;
        }

        /**
         * 如果这个方法 
         *
         * @param content
         * @return
         */
        @Override
        public String handleToken(String content) {
            this.isDynamic = true;
            return null;
        }
    }

 DynamicCheckerTokenParser的用处

DynamicCheckerTokenParser使用实再解析XML的Sql语句时,查看是否Sql里是否包含${},如果包含就叫动态SQl

 

ParameterMappingTokenHandler

ParameterMappingTokenHandler的作用是配合着GenericTokenParaser完成Mybatis的占位符#{}格式的处理。它的处理方式是将每个#{}的内容,使用?进行替换,并且将#{}里的内容转变成ParameterMapping对象。

其代码如下:


    /**
     * 用于处理 #{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}格式的入参
     */
    private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

        /**
         * {@link org.apache.ibatis.mapping.ParameterMapping}
         */
        private List<ParameterMapping> parameterMappings = new ArrayList<>();
        private Class<?> parameterType;
        private MetaObject metaParameters;

        public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
            super(configuration);
            this.parameterType = parameterType;
            this.metaParameters = configuration.newMetaObject(additionalParameters);
        }

        public List<ParameterMapping> getParameterMappings() {
            return parameterMappings;
        }

        /**
         * 处理展位符
         *
         * @param content
         * @return
         */
        @Override
        public String handleToken(String content) {
            //这里对内容进行了分析,并且注册到全局里。
            parameterMappings.add(buildParameterMapping(content));
            //结果返回的是一个?,也就是说#{}类型的参数都会被替换成?
            return "?";
        }

        private ParameterMapping buildParameterMapping(String content) {
            // #{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
            //这一步处理,是将{@code content}的内容进行了拆分
            Map<String, String> propertiesMap = parseParameterMapping(content);
            //将参数进行组装
            String property = propertiesMap.get("property");
            Class<?> propertyType;
            if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
                propertyType = metaParameters.getGetterType(property);
            } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
                propertyType = parameterType;
            } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
                propertyType = java.sql.ResultSet.class;
            } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
                propertyType = Object.class;
            } else {
                MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
                if (metaClass.hasGetter(property)) {
                    propertyType = metaClass.getGetterType(property);
                } else {
                    propertyType = Object.class;
                }
            }
            ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
            Class<?> javaType = propertyType;
            String typeHandlerAlias = null;
            for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
                String name = entry.getKey();
                String value = entry.getValue();
                if ("javaType".equals(name)) {
                    javaType = resolveClass(value);
                    builder.javaType(javaType);
                } else if ("jdbcType".equals(name)) {
                    builder.jdbcType(resolveJdbcType(value));
                } else if ("mode".equals(name)) {
                    builder.mode(resolveParameterMode(value));
                } else if ("numericScale".equals(name)) {
                    builder.numericScale(Integer.valueOf(value));
                } else if ("resultMap".equals(name)) {
                    builder.resultMapId(value);
                } else if ("typeHandler".equals(name)) {
                    typeHandlerAlias = value;
                } else if ("jdbcTypeName".equals(name)) {
                    builder.jdbcTypeName(value);
                } else if ("property".equals(name)) {
                    // Do Nothing
                } else if ("expression".equals(name)) {
                    throw new BuilderException("Expression based parameters are not supported yet");
                } else {
                    throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + PARAMETER_PROPERTIES);
                }
            }
            if (typeHandlerAlias != null) {
                builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
            }
            return builder.build();
        }

        /**
         * 解析#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap} 解析结果是一个Map里
         *
         * @param content
         * @return
         */
        private Map<String, String> parseParameterMapping(String content) {
            try {
                //这里可以看出来ParameterExpression是继承了Map的
                return new ParameterExpression(content);
            } catch (BuilderException ex) {
                throw ex;
            } catch (Exception ex) {
                throw new BuilderException("Parsing error was found in mapping #{" + content + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
            }
        }
    }

我们发现对于,handlerToken的方法是极其简单的,只要被调用,就会返回?。比较复杂的是如何将#{}的内容转换成ParameterMapping对,完成该这一动作的核心代码是在

buildParameterMapping方法里,而解析内容则放到了parseParameterMapping方法里,而这个方法也很简单,直接返回了ParameterExpression对象,也就是说对于#{}内容的解析是在

ParameterExpression内部进行的。这个解析过程是怎么样的呢?

ParameterExpression介绍

ParameterExpression内部完成了#{}内容的分解,并将其属性以key/value的形式放到HashMap里。具体的解析如下

public class ParameterExpression extends HashMap<String, String> {

    private static final long serialVersionUID = -2417552199605158680L;

    /**
     * 这里完成了实际的实际的内容拆分
     *
     * @param expression
     */
    public ParameterExpression(String expression) {
        parse(expression);
    }

    /**
     * 完成对{@code expression}的解析代码
     *
     * @param expression
     */
    private void parse(String expression) {
        //这一步目的是为了去掉{@code expression}开头部分的滤掉控制字符或通信专用字符、和(
        int p = skipWS(expression, 0);
//        "("的ASC码为0x28
        if (expression.charAt(p) == '(') {
            expression(expression, p + 1);
        } else {
            property(expression, p);
        }
    }

    /**
     * 这里是找到闭合的的表达式()
     *
     * @param expression
     * @param left
     */
    private void expression(String expression, int left) {
        int match = 1;
        int right = left + 1;
        //这一段代码就是找到闭合的() 这里如果到结束都找到不 会抛异常
        while (match > 0) {
            if (expression.charAt(right) == ')') {
                match--;
            } else if (expression.charAt(right) == '(') {
                match++;
            }
            right++;
        }
        put("expression", expression.substring(left, right - 1));
        jdbcTypeOpt(expression, right);
    }

    /**
     * 最左边的位置
     *
     * @param expression 表示要解析的内容
     * @param left 表示{@code expression}的位置
     */
    private void property(String expression, int left) {
        if (left < expression.length()) {
            //这段代码是从{@code expression}的{@code left}的位置,获取第一个符合",:"的内容
            int right = skipUntil(expression, left, ",:");
            //trimmedStr的作用是截取{@code expression}从{@code left}到{@code right}之间的字符窜
            put("property", trimmedStr(expression, left, right));
            jdbcTypeOpt(expression, right);
        }
    }

    /**
     * 剔除{@code  expression}的开头部分中所有的控制字符或通信专用字符
     *
     * @param expression 需要查看的字符窜
     * @param p          本轮expression开始的位置
     * @return 从{@code expression}的{@code p}位置开始 获取第一个非控制字符或通信专用字符所在位置
     */
    private int skipWS(String expression, int p) {
        for (int i = p; i < expression.length(); i++) {
//          0x20是Asc空格,这个条件的意思是过滤掉控制字符或通信专用字符
            if (expression.charAt(i) > 0x20) {
                return i;
            }
        }
        return expression.length();
    }

    /**
     * 从{@code expression}的p位置起,查找任意一个包含在{@code endChars}里字符,并且返回其在{@code expression}中的位置
     *
     * @param expression 表达式
     * @param p          表示{@code expression}开始的位置
     * @param endChars   表示结束的字符,如果{@code expression}包含{@code endChars}里的任何字符,都返回其对应的位置
     * @return 从{@code expression}的p位置起,查找任意一个包含在{@code endChars}里字符,并且返回其在{@code expression}中的位置
     */
    private int skipUntil(String expression, int p, final String endChars) {
        for (int i = p; i < expression.length(); i++) {
            char c = expression.charAt(i);
            if (endChars.indexOf(c) > -1) {
                return i;
            }
        }
        return expression.length();
    }

    /**
     * jdbc的选项剔除
     *
     * @param expression
     * @param p
     */
    private void jdbcTypeOpt(String expression, int p) {
        //过滤从{@code expression}的{@code p}位置,过滤到控制字符和通信字符 已经(
        p = skipWS(expression, p);
        if (p < expression.length()) {
            if (expression.charAt(p) == ':') {
                //这一步的条件说明对于#{}的内容配置,其实是可以写成#{property:jdbcType}类型的,如果是这样的类型就走这一步
                jdbcType(expression, p + 1);
            } else if (expression.charAt(p) == ',') {
                //这一步是走#{property,jdbcType=xx...}等类型的解析
                option(expression, p + 1);
            } else {
                throw new BuilderException("Parsing error in {" + expression + "} in position " + p);
            }
        }
    }

    /**
     * 这里可以看出来,#{}可以使用propertis:jdbcType的方式进行设置
     *
     * @param expression
     * @param p
     */
    private void jdbcType(String expression, int p) {
        int left = skipWS(expression, p);
        int right = skipUntil(expression, left, ",");
        if (right > left) {
            put("jdbcType", trimmedStr(expression, left, right));
        } else {
            throw new BuilderException("Parsing error in {" + expression + "} in position " + p);
        }
        option(expression, right + 1);
    }

    /**
     * 递归处理的 xxx=xxx的
     *
     * @param expression
     * @param p
     */
    private void option(String expression, int p) {
        int left = skipWS(expression, p);
        if (left < expression.length()) {
            int right = skipUntil(expression, left, "=");
            String name = trimmedStr(expression, left, right);
            left = right + 1;
            right = skipUntil(expression, left, ",");
            String value = trimmedStr(expression, left, right);
            put(name, value);
            option(expression, right + 1);
        }
    }

    /**
     * 获取stri的start和end之间的内容,并且清除控制字符或通信专用字符
     *
     * @param str
     * @param start
     * @param end
     * @return
     */
    private String trimmedStr(String str, int start, int end) {
        //0x20以下的都是控制字符或通信专用字符
        while (str.charAt(start) <= 0x20) {
            start++;
        }
        while (str.charAt(end - 1) <= 0x20) {
            end--;
        }
        return start >= end ? "" : str.substring(start, end);
    }

}

从分析代码里我们可以得出如下两个结论

结论一:#{}内部支持

#{property, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}类型的解析,也支持
#{property:javaType}类型的解析。

结论二:#{}内部 会自动过滤到非每个属性前后的()和控制字符、通信字符等。

VariableTokenHandler

VariableTokenHandler主要是配合GenericTokenParaser完成Mybatis的占位符${}格式的处理,这个处理相对来说比较简单,就是完成${}内容的全局替换。同时这里面有两个属性比较重要分别是
org.apache.ibatis.parsing.PropertyParser.enable-default-value和org.apache.ibatis.parsing.PropertyParser.default-value-separator,他分别代表着是否开启默认值,以及如果开启了默认值,默认值和key之间的分隔符是什么。org.apache.ibatis.parsing.PropertyParser.enable-default-value默认值为false,org.apache.ibatis.parsing.PropertyParser.default-value-separator默认为:。如果开启了默认值,我们就可以写作${AA:BB}这样的入参,如果AA对应的值不存在,则他的默认值就是BB
private static class VariableTokenHandler implements TokenHandler {
        private final Properties variables;
        //这个表示是否使用默认值 默认是不适用
        private final boolean enableDefaultValue;
        //这个指的是占位符里的默认值的分隔符 默认是:
        private final String defaultValueSeparator;

        private VariableTokenHandler(Properties variables) {
            this.variables = variables;

            this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
            this.defaultValueSeparator = getPropertyValue(KEY_DEFAULT_VALUE_SEPARATOR, DEFAULT_VALUE_SEPARATOR);
        }


        /**
         * 这里表示 从外部参数里获取enable-default-value的对应的值 并且默认为false
         *
         * @param key
         * @param defaultValue
         * @return
         */
        private String getPropertyValue(String key, String defaultValue) {
            return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
        }

        /**
         * 解析占位符的内容,content就是占位符的内容,这里表示能解析就解析,不能解析返回原值
         *
         * @param content
         * @return
         */
        @Override
        public String handleToken(String content) {
            if (variables != null) {
                String key = content;
                //处理占位符里的默认值问题
                if (enableDefaultValue) {
                    //这个获取默认值分隔标识的位置
                    final int separatorIndex = content.indexOf(defaultValueSeparator);
                    String defaultValue = null;
                    if (separatorIndex >= 0) {
                        key = content.substring(0, separatorIndex);
                        defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
                    }
                    if (defaultValue != null) {
                        return variables.getProperty(key, defaultValue);
                    }
                }
                if (variables.containsKey(key)) {
                    return variables.getProperty(key);
                }
            }
            return "${" + content + "}";
        }
    }

 

BindingTokenParser

BindingTokenParser的作用是使用OGNL表达式,完成对content内容的解析,该类的最好体现就是<if></if>标签里的text内容,其具体的源码如下。这里面最重要的类是 OgnlCache,该类是对OGNL表达式进行封装的类,它的主要功能在封装ONGL表达式的基础上,缓存了表达式的内容。

/**
     * 解决OGl表达式
     */
    private static class BindingTokenParser implements TokenHandler {

        /**
         * 相当于OGl的表达式
         */
        private DynamicContext context;
        /**
         * 正则拦截器
         */
        private Pattern injectionFilter;

        public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
            this.context = context;
            this.injectionFilter = injectionFilter;
        }

        @Override
        public String handleToken(String content) {
            /
            Object parameter = context.getBindings().get("_parameter");
            if (parameter == null) {
                context.getBindings().put("value", null);
            } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
                context.getBindings().put("value", parameter);
            }
            //使用Ognl进行解析
            Object value = OgnlCache.getValue(content, context.getBindings());
            String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"
            checkInjection(srtValue);
            return srtValue;
        }

        private void checkInjection(String value) {
            if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
                throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
            }
        }
    }

OgnlCache

OgnlCache的作用是对ONGL表达式进行了封装,因此在了解OgnlCache的作用前必须要先熟悉Ognl表达式的使用流程。Ognl的使用如下:

 @Test
    public void paraseToken() throws OgnlException {


        //第一步创建数据源
        Map<String, Object> map = new HashMap<>();

        User user = new User();
        user.setName("test");
        user.setAge(2);
        map.put("a", 1);
        map.put("b", user);
        //第二步 创建元素的访问权限
        MemberAccess memberAccess = new IMemberClass();
        //第三步 创建类解析器
        ClassResolver classResolver = new DefaultClassResolver();

        //第四步 创建
        TypeConverter converter = new DefaultTypeConverter();


        /**
         * 上下文配置信息
         */
        OgnlContext context = (OgnlContext) Ognl.createDefaultContext(map, memberAccess, classResolver, converter);

        /**
         * 表达式
         */
        Object expression = Ognl.parseExpression("3==b.age + a");

        //调用Ognl表达式获取结果
        Object result2 = Ognl.getValue(expression, context,map);

        System.out.println(result2);


    }

OGNL有三个关键性语法,OGNL表达式、OGNL上下文、根对象,关于这三个参数可参考这篇文章。需要在这篇文章上补充的是,OGNL上下文,该对象包含四个参数

,分别是MemberAccess对象、ClassResolver对象、还有TypeConverter。

MemberAccess

MemberAccess对象主要是控制每个属性的访问权限,用到的方法是isAccessible方法,如果该方法返回的为false,则说明无访问权限。

public interface MemberAccess
{
    
    public Object setup(Map context, Object target, Member member, String propertyName);

    public void restore(Map context, Object target, Member member, String propertyName, Object state);
    /**
     * 对每个属性的访问权限进行控制
     * @param context 上下文配置
     * @param target 目标属性目标对象
     * @param member 通过{@code target}获取{@code propertyName}对应的值时调用的方法其访;
     * @param propertyName 属性名称
     * @return
     */
    public boolean isAccessible(Map context, Object target, Member member, String propertyName);
}

TypeConverter

    TypeConverter的作用,是在Ognl表达式调用getValue(Object tree, Map context, Object root, Class resultType)时调用,该接口只有一个方法

public interface TypeConverter
{
    
    /**
     * 对每个属性的访问权限进行控制
     * @param context 上下文配置
     * @param target 目标属性目标对象
     * @param member 通过{@code target}获取{@code propertyName}对应的值时调用的方法其访;
     * @param propertyName 属性名称
     * @param value 表示propertyName对应的值
     * @param toType 表示Ognl想要将结果转换的类
     * @return
     */
    public Object convertValue(Map context, Object target, Member member, String propertyName, Object value, Class toType);
}

 

OgnlCache

对象就是基于以上案例进行封装的,主要对其获取expression进行了缓存,其代码如下

public final class OgnlCache {

    /**
     * ognl访问
     */
    private static final OgnlMemberAccess MEMBER_ACCESS = new OgnlMemberAccess();
    /**
     * ognl的类加载器 内部封装了ibatis的Resource
     */
    private static final OgnlClassResolver CLASS_RESOLVER = new OgnlClassResolver();
    /**
     * 缓存结果 内部缓存了
     */
    private static final Map<String, Object> expressionCache = new ConcurrentHashMap<>();

    private OgnlCache() {
        // Prevent Instantiation of Static Class
    }

    /**
     * @param expression 表达式
     * @param root
     * @return
     */
    public static Object getValue(String expression, Object root) {
        try {

            Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
            return Ognl.getValue(parseExpression(expression), context, root);
        } catch (OgnlException e) {
            throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
        }
    }

    /**
     * 根据表达式
     *
     * @param expression
     * @return
     * @throws OgnlException
     */
    private static Object parseExpression(String expression) throws OgnlException {
        Object node = expressionCache.get(expression);
        if (node == null) {
            node = Ognl.parseExpression(expression);
            expressionCache.put(expression, node);
        }
        return node;
    }

 

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Mybatis是一个轻量级的Java持久层开源框架,它封装了JDBC操作数据库的底层细节,提供了一个简单易用的数据库访问方式。 Mybatis源码分为核心模块和附加模块两部分,核心模块主要包括配置解析、SQL解析、SQL执行等功能,附加模块包括连接池、缓存、事务管理等功能。 在Mybatis源码中,配置解析是其中的关键部分。通过解析mybatis-config.xml配置文件,可以获取到数据库连接信息、映射器配置、插件配置等。在配置解析过程中,Mybatis会对配置文件进行校验,确保配置的正确性。 SQL解析Mybatis的另一个重要功能。Mybatis通过解析Mapper接口中的注解或XML配置文件中的SQL语句,将SQL语句解析为ParameterMapping、BoundSql等对象,并将其封装成一个MappedStatement对象,供后续的SQL执行使用。 SQL执行是Mybatis的核心功能之一。在SQL执行阶段,Mybatis会根据MappedStatement中的信息,获取数据库连接,并执行对应的SQL语句。在执行过程中,Mybatis会通过TypeHandler对参数进行型转换,并使用ResultSetHandler将查询结果封装成Java对象。 除了核心模块,Mybatis源码还包括了连接池、缓存、事务管理等附加模块的实现。连接池模块负责管理数据库连接的获取和释放,缓存模块负责缓存查询结果以提高性能,而事务管理模块则负责管理数据库的事务处理。 总之,Mybatis源码解析涉及多个关键模块的实现,包括配置解析、SQL解析、SQL执行、连接池、缓存、事务管理等。通过了解这些模块的实现原理,我们可以更好地理解和使用Mybatis框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值