Mybatis查询语句sql拼装与Ognl源码解析

@[toc]

## Mybatis查询语句sql拼装源码解析

### 带着问题学习源码(从加载mapper到sql拼装)

#### 问题现象

**后端用Integer接收0传入param.pushStatus,为什么param.pushStatus !=''判断为false**

后端使用Integer接收

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kgpQR0NO-1650516052826)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/d896a8c5d9e669c7b1c36a1e4a1610d7)\]](https://img-blog.csdnimg.cn/b4a279b8e4ce4c61a62bde1c1ebc7ff8.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_12,color_FFFFFF,t_70,g_se,x_16)![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yhqAzZE8-1650516052827)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/9602944814709df16db8d90d17c9a5d3)\]](https://img-blog.csdnimg.cn/8c8537f9ef384c7f898cb99774756a25.png)

#### 原因

mapper接口传入的参数类型为Integer值为0时,mybaits 在进行 **param.pushStatus !=''**的时候会默认<font color='red'>""和0 都转换成double进行比较 都是0.0 </font> ,结果不是重点,重点在于下面过程。

#### 源码解析(Mybatis-plus)

Mybatis-plus很多类重写了Mybatis,此处以Mybatis-plus源码出发

##### 1、加载SqlSessionFactory

项目启动会通过springboot的自动装配原理加载MybatisPlusAutoConfiguration从而加载SqlSessionFactory(加载mapper到MybatisSqlSessionFactoryBean)

```java

public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();

        factory.setDataSource(dataSource);

        ...

        if (StringUtils.hasText(this.properties.getConfigLocation())) {

//下面的ConfigLocation为:classpath:mybatis/mybatis-config.xml

factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));

        }

        ...

//这里是装载插件(之后会加入责任链中)适用于慢sql查询拦截器等

        if (!ObjectUtils.isEmpty(this.interceptors)) {

            factory.setPlugins(this.interceptors);

        }

...

//...注入很多属性比如:自定义枚举包、注入主键生成器、注入sql注入器、注入ID生成器等

factory.getObject();

}

//factory.getObject()--会进行后置属性设置(MybatisSqlSessionFactoryBean)

```

```java

//MybatisSqlSessionFactoryBean

//这里关注mapper的设置

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

        MybatisXMLConfigBuilder xmlConfigBuilder = null;

        if (this.configuration != null) {

            ...

        } else if (this.configLocation != null) {

            // this.configLocation 里面包括mybatis/mybatis-config.xml

//如果在配置文件有<mapper>标签解析

            xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);

            targetConfiguration = xmlConfigBuilder.getConfiguration();

        } else {

            ...

        }

    ......

        if (xmlConfigBuilder != null) {

            try {

                //第一种--解析配置文件mybatis-config.xml信息

                xmlConfigBuilder.parse();

        }

    ......

//mapperLocations是提前扫描自定义的classpath:mapper/*.xml文件

        //例如:file [E:\code\trade\trade\target\classes\mapper\SellReconciliationMapper.xml]

if (this.mapperLocations != null) {

            if (this.mapperLocations.length == 0) {

              ...

            } else {

//循环遍历所有的xxxx.xml

                for (Resource mapperLocation : this.mapperLocations) {

                    if (mapperLocation == null) {

                        continue;

                    }

                    try {

                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),

                            targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());

                        //这里解析mapper

                        xmlMapperBuilder.parse();

                  }

            }

        }

}

```

###### xmlConfigBuilder.parse();

```java

public Configuration parse() {

        if (parsed) {

            throw new BuilderException("Each XMLConfigBuilder can only be used once.");

        }

        parsed = true;

        parseConfiguration(parser.evalNode("/configuration"));

        return configuration;

    }

    private void parseConfiguration(XNode root) {

        try {

//将mybatis-config.xml通过XPath解析成XNode再进行解析

            propertiesElement(root.evalNode("properties"));

            Properties settings = settingsAsProperties(root.evalNode("settings"));

            loadCustomVfs(settings);

            loadCustomLogImpl(settings);

            typeAliasesElement(root.evalNode("typeAliases"));

            pluginElement(root.evalNode("plugins"));

            objectFactoryElement(root.evalNode("objectFactory"));

            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

            reflectorFactoryElement(root.evalNode("reflectorFactory"));

            settingsElement(settings);

            environmentsElement(root.evalNode("environments"));

            databaseIdProviderElement(root.evalNode("databaseIdProvider"));

            typeHandlerElement(root.evalNode("typeHandlers"));

//这里可以配置mapper

            mapperElement(root.evalNode("mappers"));

        } catch (Exception e) {

            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);

        }

    }

```

###### xmlMapperBuilder.parse();

```java

public void parse() {

    if (!configuration.isResourceLoaded(resource)) {

//前面已经获取了SellReconciliationMapper.xml,通过命名空间解析mapper

      configurationElement(parser.evalNode("/mapper"));

      configuration.addLoadedResource(resource);

      //再通过命名空间绑定mapper

      bindMapperForNamespace();

    }

//解析ResultMap等属性

    parsePendingResultMaps();

    parsePendingCacheRefs();

    parsePendingStatements();

  }

private void bindMapperForNamespace() {

    //com.jdh.trade.mapper.SellReconciliationMapper

    String namespace = builderAssistant.getCurrentNamespace();

    if (namespace != null) {

      Class<?> boundType = null;

      try {

        //获取命名空间获取mapper接口

        boundType = Resources.classForName(namespace);

      }

      if (boundType != null && !configuration.hasMapper(boundType)) {

        //Spring可能不知道真正的资源名,所以我们设置了一个标志

//防止从映射器接口再次加载这个资源

        configuration.addLoadedResource("namespace:" + namespace);

        //关键这里将mapper增加到map中

        configuration.addMapper(boundType);

      }

    }

  }

//key mapper接口 value mapper代理工厂

//private final Map<Class<?>, MybatisMapperProxyFactory<?>> knownMappers = new HashMap<>();

@Override

    public <T> void addMapper(Class<T> type) {

        if (type.isInterface()) {

            ...

            boolean loadCompleted = false;

            try {

                //!!!!保存到map!!!!

                knownMappers.put(type, new MybatisMapperProxyFactory<>(type));

                //在运行解析器之前添加类型是很重要的否则,将自动尝试绑定映射器解析器。如果类型已经知道,则不会尝试.

                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);

                parser.parse();

                loadCompleted = true;

            }...

    }

```

##### 2、mapper接口生成代理对象

@service 加载bean之后会通过

doGetObjectFromFactoryBean方法中执行factory.getObject()获取到bean实例MybatisMapperProxy。

```java

//MapperFactoryBean

//对于mybatis相关的mapper

  @Override

  public T getObject() throws Exception {

      //this.mapperInterface 相当于接口com.jdh.trade.mapper.xxxMapper

    return getSqlSession().getMapper(this.mapperInterface);

  }

//SqlSessionTemplate

  @Override

  public <T> T getMapper(Class<T> type) {

    return getConfiguration().getMapper(type, this);

  }

//MybatisMapperRegistry

@Override

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {

        //knownMappers 这个map是上面保存的

        final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);

...

        try {

            //生成代理对象

            return mapperProxyFactory.newInstance(sqlSession);

        } catch (Exception e) {

            throw new BindingException("Error getting mapper instance. Cause: " + e, e);

        }

    }

//MybatisMapperProxyFactory

//mapperProxyFactory.newInstance(sqlSession);

protected T newInstance(MybatisMapperProxy<T> mapperProxy) {

    //Proxy 生成动态代理实例

        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);

    }

    public T newInstance(SqlSession sqlSession) {

        final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);

        return newInstance(mapperProxy);

    }

```

##### 3、调用查询方法

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t4H8ej0Q-1650516052829)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/3f07629179b2a67f00a75a1c969ca3ad)\]](https://img-blog.csdnimg.cn/46df55196bb443e1915cd7f1609898fd.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

执行mapper的查询等语句就会进入代理对象的invoke方法

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z0AArxR6-1650516052829)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/4639d1b1bcbc912c3f7c5a2353ad3da6)\]](https://img-blog.csdnimg.cn/9d4560591b2e4285836cff47aa7a9e2b.png)

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

MybatisMapperProxy

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-43rqFH0W-1650516052830)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/50c83add18d67470cdd256f3a1fe4546)\]](https://img-blog.csdnimg.cn/ba4cb1f79fab492b88d5ce22574bc813.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

从缓存获取MybatisMapperMethod-属性如下

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YaYw3jeo-1650516052830)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/d4fe14619bcf0e7cfef5d3c509f65069)\]](https://img-blog.csdnimg.cn/2a3241c6c4644e1daf7f0cd53c300c61.png)

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fDPYeN03-1650516052831)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/3e0d6569d357998f88a74fbe6521245a)\]](https://img-blog.csdnimg.cn/00ac9576c2a24ada9bdff1302839a088.png)

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yE1T20pP-1650516052831)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/13a45f906187eca9674e49b990f29602)\]](https://img-blog.csdnimg.cn/194138e438d84d5d85e940b99ad33b60.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

最终执行sql走的是MybatisMapperMethod.execute(sqlSession, args)

```java

public Object execute(SqlSession sqlSession, Object[] args) {

        Object result;

//根据是查询还是更新进入不同分支

        switch (command.getType()) {

            case INSERT: {

                Object param = method.convertArgsToSqlCommandParam(args);

                result = rowCountResult(sqlSession.insert(command.getName(), param));

                break;

            }

            case UPDATE: {

                Object param = method.convertArgsToSqlCommandParam(args);

                result = rowCountResult(sqlSession.update(command.getName(), param));

                break;

            }

            case DELETE: {

                Object param = method.convertArgsToSqlCommandParam(args);

                result = rowCountResult(sqlSession.delete(command.getName(), param));

                break;

            }

            case SELECT:

                if (method.returnsVoid() && method.hasResultHandler()) {

                    executeWithResultHandler(sqlSession, args);

                    result = null;

                } else if (method.returnsMany()) {

                    //比如 selectList 就会走这里

                    result = executeForMany(sqlSession, args);

                } else if (method.returnsMap()) {

                    result = executeForMap(sqlSession, args);

                } else if (method.returnsCursor()) {

                    result = executeForCursor(sqlSession, args);

                } else {

                    Object param = method.convertArgsToSqlCommandParam(args);

                    // 分页查询

                    if (IPage.class.isAssignableFrom(method.getReturnType())) {

                        ...

                        //关注这里 执行分页 默认也是执行selectList

                        result = executeForIPage(sqlSession, args);

                        ...

                }

}

/**

    * TODO IPage 专用

    */

    private <E> List<E> executeForIPage(SqlSession sqlSession, Object[] args) {

        Object param = method.convertArgsToSqlCommandParam(args);

        //执行SqlSessionTemplate的selectList

        return sqlSession.selectList(command.getName(), param);

    }

  @Override

  public <E> List<E> selectList(String statement, Object parameter) {

  //DefaultSqlSession

    return this.sqlSessionProxy.selectList(statement, parameter);

  }

```

###### DefaultSqlSession代理对象获取sqlSession

```java

private class SqlSessionInterceptor implements InvocationHandler {

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    //加载sqlSession-getSqlSession-通过sessionFactory.openSession(executorType);

    //从中会获取执行器和加载插件

      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,

          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

      try {

//拿到sqlSession才能去执行查询

        Object result = method.invoke(sqlSession, args);

        ....

    }

```

```java

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,

      PersistenceExceptionTranslator exceptionTranslator) {

...

    //DefaultSqlSessionFactory.openSession

    session = sessionFactory.openSession(executorType);

...

    return session;

  }

@Override

  public SqlSession openSession(ExecutorType execType) {

    return openSessionFromDataSource(execType, null, false);

  }

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {

    Transaction tx = null;

    try {

      final Environment environment = configuration.getEnvironment();

      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

  //新建事务

      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

        //创建执行器 MybatisConfiguration.newExecutor

//(默认是simple执行器)

      final Executor executor = configuration.newExecutor(tx, execType);

        //最后返回默认DefaultSqlSession

      return new DefaultSqlSession(configuration, executor, autoCommit);

    } catch (Exception e) {

      closeTransaction(tx); // may have fetched a connection so lets call close()

      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);

    } finally {

      ErrorContext.instance().reset();

    }

  }

```

###### 装饰者模式创建executor和责任链模式interceptorChain加载插件

```java

@Override

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

        executorType = executorType == null ? defaultExecutorType : executorType;

        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

        Executor executor;

        if (ExecutorType.BATCH == executorType) {

            executor = new MybatisBatchExecutor(this, transaction);

        } else if (ExecutorType.REUSE == executorType) {

            executor = new MybatisReuseExecutor(this, transaction);

        } else {

            executor = new MybatisSimpleExecutor(this, transaction);

        }

        if (cacheEnabled) {

            //装饰者模式 装饰了简单执行器

            executor = new MybatisCachingExecutor(executor);

        }

        //责任链 进行增加所有执行器 并执行plugin

        //通过判断插件,new Plugin(target, interceptor, signatureMap))生成MybatisCachingExecutor代理对象!!!

        executor = (Executor) interceptorChain.pluginAll(executor);

        return executor;

    }

```

##### 继续执行查询

DefaultSqlSession.selectList

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rqw7uuW7-1650516052832)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/7e7eada5ee523b9358e3746bb94237eb)\]](https://img-blog.csdnimg.cn/6711ad8cde8f4c7db48da47dac668b2d.png)

```java

//DefaultSqlSession

@Override

  public <E> List<E> selectList(String statement, Object parameter) {

    return this.selectList(statement, parameter, RowBounds.DEFAULT);

  }

  @Override

//rowBounds 是用来逻辑分页(按照条件将数据从数据库查询到内存中,在内存中进行分页)

//wrapCollection(parameter)是用来装饰集合或者数组参数 里面有查询条件

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {

    try {

    //statement:

      //名字com.jdh.trade.mapper.SellReconciliationMapper.selectSellReconciliationByPage

      //获取MappedStatement对象,通过配置信息从StrictMap缓存中获取

      MappedStatement ms = configuration.getMappedStatement(statement);

        //执行executor对象里面的query方法

        //这里的executor是在DefaultSqlSessionFactory中,

        //mybatis 通过Configuration对象创建的 对应CachingExecutor 

        //mybatis-plus 通过MybatisConfiguration 创建MybatisCachingExecutor

        //根据不同的配置,会有不同的Executor 无论那个执行器查询最终都会到下面的查询

      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

    }

```

这一次获得MappedStatement ms 如下

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVkJL02K-1650516052833)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/bbd0c079418e440b6bef01551fadbd4f)\]](https://img-blog.csdnimg.cn/19932814ebb54cdcb317dfb280ff29b0.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

###### 关键查询

```java

MybatisCachingExecutor

@Override

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {

        //组装sql--我们主要研究这里的组装

        BoundSql boundSql = ms.getBoundSql(parameterObject);

//获取一级缓存key

        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);

        //从缓存查询还是直接查询数据库等

        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

    }

```

```java

MappedStatement

public BoundSql getBoundSql(Object parameterObject) {

    //sqlSource 为 DynamicSqlSource(动态sql拼接)见上图

//SqlSouce里面已经解析mapper对应的sql

//已经被解析为MixedSqlNode

    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

    ....

    return boundSql;

  }

```

```xml

<select id="selectSellReconciliationByPage"

            resultType="com.jdh.trade.model.bankcontribution.resp.QuerySellReconciliationResp">

        SELECT

        ...

        WHERE sr.root_code = po.root_code

        ...

        <if test="param.pushStatus != null and param.pushStatus !='' ">

            and sr.push_status = #{param.pushStatus}

</select>

```

###### 解析sql过程

```java

@Override

  public BoundSql getBoundSql(Object parameterObject) {

    //下面有实体属性

    DynamicContext context = new DynamicContext(configuration, parameterObject);

    //处理一个个的sqlNode 编译出一个完整的xml的sql

    //rootSqlNode是 MixedSqlNode 合并sql节点

    //${} 已经在静态节点赋值了 因为是直接静态判断${ 然后替换 会有sql注入风险

    rootSqlNode.apply(context);

    //创建sql信息解析器

    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);

    //获取入参类型

    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();

    //执行解析:将带有#{}和${}的sql语句进行解析,然后封装到StaticSqlSource中

      //比如and sr.push_status = #{param.pushStatus} 解析成  and sr.push_status = ?

    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());

    //将解析后的sql语句还有入参绑定到一起(封装到一个对象中,此时还没有将参数替换到SQL占位符?)

      //此时变为静态绑定 StaticSqlSource

    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

    context.getBindings().forEach(boundSql::setAdditionalParameter);

    return boundSql;

  }

```

###### sql解析为MixedSqlNode节点

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NjkqYKO0-1650516052833)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/6f3a80fa6d0587f8844927a0a4e36141)\]](https://img-blog.csdnimg.cn/90b0406c995743c588dc9c6120647ce7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WYiKs5H5-1650516052834)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/fd0edc96898cccdbdfdebc55531df78f)\]](https://img-blog.csdnimg.cn/1f3e827aeccd4f549e764c7258637311.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

**将每一个节点进行各自的解析 然后拼装xml的sql 到 sqlBuilder (其实是装入string属性)**

我们来看一下 rootSqlNode.apply(context);

######  rootSqlNode.apply(context)

每个节点根据自己的规则判断是否组装到sqlBuilder

MixedSqlNode.apply

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wl8g1ejY-1650516052834)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/0fc7f8cea1ab54cc583068836c25b9c5)\]](https://img-blog.csdnimg.cn/6aacecbe823c437396fa0d59f2ecc7b3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

这里我们注意一下为什么IfSqlNode节点会失效呢

<font color='red'>注意:为什么param.pushStatus != '' 进入这个方法会返回false</font>

```java

@Override

  public boolean apply(DynamicContext context) {

      //test = param.pushStatus != null and param.pushStatus != ''

      //context.getBindings() 有前端传的参数条件

      //判断符合条件就加入

    if (evaluator.evaluateBoolean(test, context.getBindings())) {

      contents.apply(context);

      return true;

    }

    return false;

  }

```

调用ExpressionEvaluator.evaluateBoolean 进行判断

```java

public boolean evaluateBoolean(String expression, Object parameterObject) {

    //问题出现在这里 value 返回了false

    Object value = OgnlCache.getValue(expression, parameterObject);

    if (value instanceof Boolean) {

      //从这里返回boolean值 false

      return (Boolean) value;

    }

    if (value instanceof Number) {

      return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;

    }

    return value != null;

  }

```

再调用OgnlCache.getValue方法 获取校验结果

```java

//表达式expression = param.pushStatus != null and param.pushStatus != ''

//root 入参

public static Object getValue(String expression, Object root) {

    try {

      //Mybatis底层校验使用Ognl语法

      Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);

        //这里调用Ognl.getValue进行比较

      return Ognl.getValue(parseExpression(expression), context, root);

    } catch (OgnlException e) {

      throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);

    }

  }

```

parseExpression(expression) 会从缓存中读取Node 如果没有 去解析成特定类型的Node对象

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jtSyNZzf-1650516052834)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/7f5a45ede1b4c446ca6c010177e944aa)\]](https://img-blog.csdnimg.cn/bd19badffcab45b2922ab70e97345819.png)

```java

//OgnlCache

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;

  }

```

比如这里转化成 ASTAND

以下节点都是继承SimpleNode

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0pTTkrP2-1650516052835)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/79511546ca850b7612af95c39ef14591)\]](https://img-blog.csdnimg.cn/94023c27a12d4fb4a47f8a5bd263ca8a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

Ognl.getValue(parseExpression(expression), context, root)获取最终结果

```java

public static Object getValue(Object tree, Map context, Object root)

            throws OgnlException

    {

        return getValue(tree, context, root, null);

    }

/**

    * 计算给定的OGNL表达式树,从给定的根对象中提取值。通过addDefaultContext()为给定的上下文和根设置默认上下文。

    *

    * @param tree

    *            要计算的OGNL表达式树,由parseExpression()返回

    如:(param.pushStatus != null) && (param.pushStatus != "")

    * @param context

    *            求值的命名上下文

    * @param root

    *            OGNL表达式的根对象


    * @return   返回计算表达式的结果

    */

public static Object getValue(Object tree, Map context, Object root, Class resultType) throws OgnlException {

        OgnlContext ognlContext = (OgnlContext)addDefaultContext(root, context);

        //根据解析表达式获取的节点 该例子为 ASTAND

    Node node = (Node)tree;

        Object result;

        if (node.getAccessor() != null) {

            result = node.getAccessor().get(ognlContext, root);

        } else {

            //调用父类SimpleNode的getValue方法

//该节点为 ASTAND

            result = node.getValue(ognlContext, root);

        }

...

        return result;

    }

```

###### ASTAND节点树进行解析

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uv5gijjR-1650516052836)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/4553a1d57700a78d5d412db1f5a5a389)\]](https://img-blog.csdnimg.cn/162fa2950cd84f239eb568b39cc4e968.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

调用父类 SimpleNode. getValue

以下两个方法方法为父类方法

```java

public final Object getValue(OgnlContext context, Object source) throws OgnlException {

        Object result = null;

        if (context.getTraceEvaluations()) {

            ...

        } else {

            //获取常量值

            result = this.evaluateGetValueBody(context, source);

        }

        return result;

    }

```

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gCaqx2zA-1650516052836)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/b89e9fd510e869daf43bccaf08a6ac61)\]](https://img-blog.csdnimg.cn/9b1c7262557a4211a5774818005bc628.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

调用子类自己的判断实现getValueBody  从节点树叶子节点ASTConst一层一层根据规则返回结果给上一层

###### ASTAnd

```java

protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {

        Object result = null;

        int last = this._children.length - 1;

        for(int i = 0; i <= last; ++i) {

            result = this._children[i].getValue(context, source);

            if (i != last && !OgnlOps.booleanValue(result)) {

                break;

            }

        }

        return result;

    }

```

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4tnU2yD-1650516052837)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/eb2e2e018bc33c8ee5b76a11252e2fb1)\]](https://img-blog.csdnimg.cn/38a998291734448e90c9cbcd2ee4c9aa.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

###### ASTNotEq

```java

protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {

        Object v1 = this._children[0].getValue(context, source);

        Object v2 = this._children[1].getValue(context, source);

        return OgnlOps.equal(v1, v2) ? Boolean.FALSE : Boolean.TRUE;

    }

```

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HyFxeqay-1650516052837)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/6ada714321b6458785e31fabe2887a86)\]](https://img-blog.csdnimg.cn/49b92c25ffa1462f8ce2b5ab4a353aef.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATWlNaW5n5LuU,size_20,color_FFFFFF,t_70,g_se,x_16)

<font color='red'>这里出现了问题为什么 0 和 '' equal会相等</font>

```java

public static boolean equal(Object v1, Object v2) {

        if (v1 == null) {

            return v2 == null;

            //!isEqual 判断出问题

        } else if (v1 != v2 && !isEqual(v1, v2)) {

            if (v1 instanceof Number && v2 instanceof Number) {

                return ((Number)v1).doubleValue() == ((Number)v2).doubleValue();

            } else {

                return false;

            }

        } else {

            return true;

        }

    }

```

<font color='red'>!isEqual(v1, v2)  判断成了相等返回true</font>

```java

public static boolean isEqual(Object object1, Object object2) {

        ...

{

                    //前面根据Ognl的算法

                    result = compareWithConversion(object1, object2) == 0;

                }

        return result;

    }

public static int compareWithConversion(Object v1, Object v2) {

...

        double dv1 = doubleValue(v1);

        double dv2 = doubleValue(v2);

        return dv1 == dv2 ? 0 : (dv1 < dv2 ? -1 : 1);

    }

```

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tP6PMJ3z-1650516052838)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/d51079b0f256d5537fae3039142a2542)\]](https://img-blog.csdnimg.cn/0ccec46131624ee39dd901e7a849baa1.png)

###### 4、问题根源

```java

public static double doubleValue(Object value) throws NumberFormatException {

        if (value == null) {

            return 0.0D;

        } else {

            Class c = value.getClass();

            if (c.getSuperclass() == Number.class) {

                return ((Number)value).doubleValue();

            } else if (c == Boolean.class) {

                return (Boolean)value ? 1.0D : 0.0D;

            } else if (c == Character.class) {

                return (double)(Character)value;

            } else {

                //这里将''改成了0.0D

                String s = stringValue(value, true);

                return s.length() == 0 ? 0.0D : Double.parseDouble(s);

            }

        }

    }

```

最后是 "" 转换成了0.0 去比较了

###### ASTChain

```java

protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {

        Object result = source;

        int i = 0;

        for(int ilast = this._children.length - 1; i <= ilast; ++i) {

            boolean handled = false;

            if (i < ilast && this._children[i] instanceof ASTProperty) {

                ASTProperty propertyNode = (ASTProperty)this._children[i];

                int indexType = propertyNode.getIndexedPropertyType(context, result);

...

                }

            }

            if (!handled) {

//将最后的链值返回

                result = this._children[i].getValue(context, result);

            }

        }

        return result;

    }

```

###### ASTProperty

```java

protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {

        //通过ASTConst 直接获取返回值 如 param 或 pushStatus 再读取入参对应的值

  Object property = this.getProperty(context, source);

    //根据 param 或 pushStatus 返回对应的值

        Object result = OgnlRuntime.getProperty(context, source, property);

        if (result == null) {

            result = OgnlRuntime.getNullHandler(OgnlRuntime.getTargetClass(source)).nullPropertyValue(context, source, property);

        }

        return result;

    }

```

###### ASTConst

```java

protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {

        return this.value;

    }

```

#### 解决

(1) 不用Integer接收,使用String类型接收

(2)去掉【参数!=’‘】 的非空判断

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sex5LBqb-1650516052838)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/e872d96804f4039fa5878d896d0f970f)\]](https://img-blog.csdnimg.cn/cf172267c941471a8ea0bf6402ca0f08.png)

> 番外:如果String类型需要判断!=0,则需要写成 xxx != '0'.toString()

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容