Loading

24-自定义持久层框架

1. JDBC 的使用问题

代码示例:

public class JDBCTest {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            // 加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 通过驱动管理类获取数据库链接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "root");
            // 定义sql语句 ? 表示占位符
            String sql = "select * from user where username = ?";
            // 获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
            preparedStatement.setString(1, "tom");
            // 向数据库发出sql执行查询,查询出结果集
            resultSet = preparedStatement.executeQuery();
            // 遍历查询结果集
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                // 封装User
                User user = new User();
                user.setId(id);
                user.setUsername(username);
                System.out.println(user);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

下面剖开代码,逐个分析其中问题:

(1)加载驱动,获取连接

存在问题1:数据库配置信息存在硬编码问题。

优化思路:使用配置文件!

存在问题2:频繁创建、释放数据库连接问题。

优化思路:使用数据连接池!

(2)定义sql、设置参数、执行查询

存在问题3:SQL语句、设置参数、获取结果集参数均存在硬编码问题 。

优化思路:使用配置文件!

(3)遍历查询结果集

存在问题4:手动封装返回结果集,较为繁琐。

优化思路:使用 Java 反射、内省!

针对 JDBC 各个环节中存在的不足,现在我们整理出对应的优化思路,统一汇总:

存在问题 优化思路
数据库配置信息存在硬编码问题 使用配置文件
频繁创建、释放数据库连接问题 使用数据连接池
SQL语句、设置参数、获取结果集参数均存在硬编码问题 使用配置文件
手动封装返回结果集,较为繁琐 使用 Java 反射、内省

2. 自定义框架分析

JDBC 是个人作战,凡事亲力亲为,低效而高险,自己加载驱动,自己建连接,自己做所有事。而持久层框架好比是多工种协作,分工明确,执行高效,有专门负责解析注册驱动建立连接的,有专门管理数据连接池的,有专门执行sql语句的,有专门做预处理参数的,有专门装配结果集的 …

框架的作用,就是为了帮助我们减去繁重开发细节与冗余代码,使我们能更加专注于业务应用开发。

  • 配置数据源(地址/数据名/用户名/密码)===> 全局配置文件
  • 编写SQL与参数准备(SQL语句/参数类型/返回值类型)===> SQL 映射文件

持久层框架对比:

  • Mybatis:SQL 映射框架;半自动化的持久层框架
  • Hibernate:ORM 框架;全自动化的持久层框架

框架,除了思考本身的工程设计,还需要考虑到实际项目端的使用场景,干系方涉及两端:使用端(实际项目)、持久层框架本身。手写持久层框架基本思路图如下所示。

核心接口/类重点说明:

分工协作 角色定位 类名定义
负责读取配置文件 资源辅助类 Resources
负责存储数据库连接信息 数据库资源类 Configuration
负责存储 SQL 映射定义、存储结果集映射定义 SQL 与结果集资源类 MappedStatement
负责解析配置文件,创建会话工厂 SqlSessionFactory 会话工厂构建者 SqlSessionFactoryBuilder
负责创建会话 SqlSession 会话工厂 SqlSessionFactory
指派执行器 Executor 会话 SqlSession
负责执行 SQL (配合指定资源 MappedStatement) 执行器 Executor

正常来说项目只对应一套数据库环境,一般对应一个 SqlSessionFactory 实例对象,我们使用单例模式只创建一个 SqlSessionFactory 实例。如果需要配置多套数据库环境,那需要做一些拓展,例如 Mybatis 中通过 environments 等配置就可以支持多套测试/生产数据库环境进行切换。

2.1 项目使用端

(1)调用框架 API,需引入自定义持久层框架的 jar 包

(2)提供两部分配置信息

  1. sqlMapConfig.xml : 数据库配置信息(地址/数据名/用户名/密码),以及 mapper.xml 的全路径;
  2. mapper.xml : SQL 配置信息,存放 SQL 语句、参数类型、返回值类型相关信息。

2.2 持久层框架本身

3. 框架设计

3.1 使用端设计

(1)引入 ipersistent 即自定义持久化框架的依赖

(2)创建 sqlMapConfig.xml

<configuration>

    <!-- 1.配置数据库信息 -->
    <dataSource>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///ipersistent?useSSL=false&amp;characterEncoding=UTF-8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </dataSource>

    <!-- 2.引入映射配置文件 -->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"></mapper>
    </mappers>

</configuration>

(2)创建 mapper/UserMapper.xml

<mapper namespace="com.itheima.dao.IUserDao">

    <!-- 唯一标识:namespace.id  statementId -->
    <!-- 查询所有 -->
    <!--
        规范:接口的全路径要和namespace的值保持一致
              接口中的方法名要和id的值保持一致
    -->
    <select id="findAll" resultType="com.itheima.pojo.User">
        select * from user
    </select>

    <!-- 按条件进行查询 -->
    <!--
        User user = new User();
        user.setId(1);
        user.setUserName("tom");
    -->
   <select id="findByCondition" resultType="com.itheima.pojo.User" parameterType="com.itheima.pojo.User">
        select * from user where id = #{id} and username = #{username}
   </select>

</mapper>

(3)创建 User 实体

package com.itheima.pojo;

public class User {

    private Integer id;
    private String username;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                '}';
    }

}

(4)创建 Dao

package com.itheima.dao;

import com.itheima.pojo.User;

import java.util.List;

public interface IUserDao {

    /**
     * 查询所有
     */
    List<User> findAll() throws Exception;

    /**
     * 根据多条件查询
     */
    User findByCondition(User user) throws Exception;
    
}

3.2 框架类图

4. 框架实现

4.1 配置装载类

a. Resources

package com.itheima.io;

public class Resources {

    /**
     * 根据配置文件的路径,加载配置文件成字节输入流,存到内存中
     */
    public static InputStream getResourceAsSteam(String path) {
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }
}

b. Configuration

package com.itheima.pojo;

/**
 * 全局配置类:存放核心配置文件解析出来的内容
 */
public class Configuration {

    /**
     * 数据源对象
     */
    private DataSource dataSource;

    /**
     * [key] statementId:namespace.id
     * [val] 封装好的MappedStatement对象
     */
    private Map<String, MappedStatement> mappedStatementMap = new HashMap();

    // 省略 Getter/Setter ...

}

c. MappedStatement

package com.itheima.pojo;

/**
 * 映射配置类:存放mapper.xml解析内容
 */
public class MappedStatement {

    /**
     * 唯一标识(statementId:namespace.id)
     */
    private String statementId;
    
    /**
     * 返回值类型
     */
    private String resultType;
    
    /**
     * 参数值类型
     */
    private String parameterType;
    
    /**
     * SQL语句
     */
    private String sql;

    /**
     * 判断当前是什么操作的一个属性
     */
    private String sqlCommandType;
  
  
    // 省略 Getter/Setter ...

}

4.2 装载配置

a. SqlSessionFactoryBuilder

package com.itheima.sqlSession;

public class SqlSessionFactoryBuilder {

    /**
     * 1.解析配置文件,封装容器对象
     * 2.创建SqlSessionFactory工厂对象
     */
    public SqlSessionFactory build(InputStream inputStream) throws DocumentException {

        // 1. ===> 解析配置文件,封装容器对象 (XMLConfigBuilder 专门解析核心配置文件的解析类)
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parse(inputStream);

        // 2. 创建SqlSessionFactory工厂对象
        return new DefaultSqlSessionFactory(configuration);
        
    }
}

b. XMLConfigBuilder

package com.itheima.config;

public class XMLConfigBuilder {

    private Configuration configuration;

    public XMLConfigBuilder() {
        this.configuration = new Configuration();
    }

    /**
     * 使用dom4j+xpath解析配置文件,封装Configuration对象
     * 1. dataSource数据源对象
     * 2. MappedStatement集合
     */
    public Configuration parse(InputStream inputStream) throws DocumentException {

        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();

        List<Element> list = rootElement.selectNodes("//property");

        // <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        Properties properties = new Properties();
        for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name, value);
        }

        // 创建数据源对象
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(properties.getProperty("driverClassName"));
        druidDataSource.setUrl(properties.getProperty("url"));
        druidDataSource.setUsername(properties.getProperty("username"));
        druidDataSource.setPassword(properties.getProperty("password"));

        // 创建好的数据源对象存入Configuration对象中
        configuration.setDataSource(druidDataSource);

        // ---------------------- 解析映射配置文件 ----------------------
        // <mapper resource="mapper/UserMapper.xml"></mapper>
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            // a. 获取映射配置文件的路径
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
            // b. 根据路径进行映射配置文件的加载解析 (XMLMapperBuilder 专门解析映射配置文件的对象)
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            // c. 解析结果封装 MappedStatement 存入 Configuration.mappedStatementMap
            xmlMapperBuilder.parse(resourceAsSteam);
        }
      
        return configuration;
    }
}

c. XMLMapperBuilder

package com.itheima.config;

public class XMLMapperBuilder {

    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 解析映射配置文件 -> MappedStatement -> Configuration.mappedStatementMap
     */
    public void parse(InputStream resourceAsSteam) throws DocumentException {
        // 1. 解析映射配置文件
        Document document = new SAXReader().read(resourceAsSteam);
        Element rootElement = document.getRootElement();
        /*
         * <select id="selectOne" resultType="com.itheima.pojo.User" parameterType="com.itheima.pojo.User">
         *   select * from user where id = #{id} and username = #{username}
         * </select>
         */
        List<Element> selectList = rootElement.selectNodes("//select");
        String namespace = rootElement.attributeValue("namespace");
        for (Element element : selectList) {

            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String parameterType = element.attributeValue("parameterType");
            String sql = element.getTextTrim();

            // 2. 封装MappedStatement对象
            MappedStatement mappedStatement = new MappedStatement();

            // statementId = namespace.id
            String statementId = namespace + "." + id;

            mappedStatement.setStatementId(statementId);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParameterType(parameterType);
            mappedStatement.setSql(sql);
            mappedStatement.setSqlCommandType("select");

            // 3. 将封装好的MappedStatement封装到configuration中的map集合中
            configuration.getMappedStatementMap().put(statementId, mappedStatement);

        }

    }
}

4.3 创建 SqlSessionFactory

SqlSessionFactoryBuilder.build 操作的第一步是解析配置文件,封装 Configuration;第二步就是以 configuration 作为入参创建 SqlSessionFactory 工厂对象。

a. SqlSessionFactory

package com.itheima.sqlSession;

public interface SqlSessionFactory {

    /**
     * 1. 生产 SqlSession 对象
     * 2. 创建 Executor 对象
     */
    SqlSession openSession();
   
}

b. DefaultSqlSessionFactory

package com.itheima.sqlSession;

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        // 1.创建执行器对象
        Executor simpleExecutor = new SimpleExecutor();
        // 2.生产SqlSession对象
        DefaultSqlSession defaultSqlSession = new DefaultSqlSession(configuration, simpleExecutor);

        return defaultSqlSession;
    }
}

4.4 创建 Executor

a. Executor

package com.itheima.executor;

public interface Executor {

    <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object param) throws Exception;

    void close();
}

b. BoundSql

package com.itheima.config;

import com.itheima.utils.ParameterMapping;

import java.util.List;

public class BoundSql {

    private String finalSql;

    private List<ParameterMapping> parameterMappingList;

    public BoundSql(String finalSql, List<ParameterMapping> parameterMappingList) {
        this.finalSql = finalSql;
        this.parameterMappingList = parameterMappingList;
    }

    public String getFinalSql() {
        return finalSql;
    }

    public void setFinalSql(String finalSql) {
        this.finalSql = finalSql;
    }

    public List<ParameterMapping> getParameterMappingList() {
        return parameterMappingList;
    }

    public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
        this.parameterMappingList = parameterMappingList;
    }
}

c. SimpleExecutor

package com.itheima.executor;

public class SimpleExecutor implements Executor {

    private Connection connection = null;
    private PreparedStatement preparedStatement = null;
    private ResultSet resultSet = null;

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object param) throws Exception {

        // 1. 加载驱动,获取数据库连接
        connection = configuration.getDataSource().getConnection();

        // 2. 获取PreparedStatement预编译对象
        // 获取要执行的sql语句
        /*                             自定义的占位符
                select * from user where id = #{id} and username = #{username}
         替换:  select * from user where id = ? and username = ?
                解析替换的过程中:#{id}里面的值保存下来
         */
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        String finalSql = boundSql.getFinalSql();
        preparedStatement = connection.prepareStatement(finalSql);

        // 3.设置参数
        // com.itheima.pojo.User
        String parameterType = mappedStatement.getParameterType();

        if (parameterType != null) {
            Class<?> parameterTypeClass = Class.forName(parameterType);

            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                // id || username
                String paramName = parameterMapping.getContent();
                // 反射
                Field declaredField = parameterTypeClass.getDeclaredField(paramName);
                // 暴力访问
                declaredField.setAccessible(true);
                Object value = declaredField.get(param);
                // 赋值占位符
                preparedStatement.setObject(i + 1, value);
            }
        }

        // 4.执行sql发起查询
        resultSet = preparedStatement.executeQuery();

        // 5.处理返回结果集
        ArrayList<E> list = new ArrayList<>();
        while (resultSet.next()) {
            // 元数据信息包含了字段名、字段值
            ResultSetMetaData metaData = resultSet.getMetaData();

            String resultType = mappedStatement.getResultType();
            Class<?> resultTypeClass = Class.forName(resultType);
            Object o = resultTypeClass.newInstance();

            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                // 字段名 id  username
                String columnName = metaData.getColumnName(i);
                // 字段的值
                Object value = resultSet.getObject(columnName);

                // 问题:现在要封装到哪一个实体中
                // 属性描述器:通过API方法获取某个属性的读写方法
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                // 参数1:实例对象 参数2:要设置的值
                writeMethod.invoke(o, value);
            }
            list.add((E) o);

        }

        return list;
    }

    /**
     * 1. #{} 占位符替换成?
     * 2. 解析替换的过程中 将 #{} 里的值保存下来
     *
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        // 1. 创建标记处理器:配合标记解析器完成标记的处理解析工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        // 2. 创建标记解析器
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);

        // #{} 占位符替换成 ?
        String finalSql = genericTokenParser.parse(sql);
        // 解析替换的过程中,将 #{} 里的值保存下来
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        BoundSql boundSql = new BoundSql(finalSql, parameterMappings);
        return boundSql;
    }

    /**
     * 释放资源
     */
    @Override
    public void close() {
        // 释放资源
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }


    }
}

4.5 创建 SqlSession

a. SqlSession

package com.itheima.sqlSession;

public interface SqlSession {

    /**
     * 生成代理对象
     */
    <T> T getMapper(Class<?> mapperClass);
  
    /**
     * 查询多个结果
     * sqlSession.selectList() 定位到要执行的sql语句,从而执行
     * select * from user where username like '% ? %'
     */
    <E> List<E> selectList(String statementId, Object param) throws Exception;

    /**
     * 查询单个结果
     */
    <T> T selectOne(String statementId, Object param) throws Exception;

    /**
     * 清除资源
     */
    void close();

}

b. DefaultSqlSession

package com.itheima.sqlSession;

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    private Executor executor;

    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }

    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        // 使用JDK动态代理生成基于接口的代理对象
        Object proxy = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), 
                                              new Class[]{mapperClass}, (object, method, args) -> {
            // 具体的逻辑 :执行底层的JDBC
            // 通过调用 SqlSession 里的方法来完成方法调用
            // 参数的准备:1.statementId: com.itheima.dao.IUserDao.findAll  2.param
            // [问题1] 如何获取statementId?
            // => findAll
            String methodName = method.getName();
            // => com.itheima.dao.IUserDao
            String className = method.getDeclaringClass().getName();
            // => 组装
            String statementId = className + "." + methodName;

            // [问题2] 要调用sqlSession中增删改查的什么方法呢?
            MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
            // => sqlCommandType: select/update/delete/insert
            String sqlCommandType = mappedStatement.getSqlCommandType();
            switch (sqlCommandType) {
                case "select":
                    // 执行查询方法调用
                    // [问题3] 该调用selectList还是selectOne?
                    Type genericReturnType = method.getGenericReturnType();
                    // => 判断是否实现了 泛型类型参数化
                    // => ParameterizedType represents a parameterized type such as Collection<String>.
                    if (genericReturnType instanceof ParameterizedType) {
                        if (args != null) {
                            return selectList(statementId, args[0]);
                        }
                        return selectList(statementId, null);
                    }
                    return selectOne(statementId, args[0]);
                case "update":
                    // TODO 执行update方法调用
                    break;
                case "delete":
                    // TODO 执行delete方法调用
                    break;
                case "insert":
                    // TODO 执行insert方法调用
                    break;
            }
            return null;
        });
        return (T) proxy;
    }

    @Override
    public <E> List<E> selectList(String statementId, Object param) throws Exception {
        // 将查询操作委派给底层的执行器
        // query(): 执行底层的JDBC 1.数据库配置信息 2.sql配置信息
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        List<E> list = executor.query(configuration, mappedStatement, param);
        return list;
    }

    @Override
    public <T> T selectOne(String statementId, Object param) throws Exception {
        List<Object> list = this.selectList(statementId, param);
        if (list.size() == 1) {
            return (T) list.get(0);
        } else if (list.size() > 1) {
            throw new RuntimeException("result.size > 1!");
        } else {
            return null;
        }
    }
  
    @Override
    public void close() {
        executor.close();
    }

}

4.6 工具类

a. GenericTokenParser

package com.itheima.utils;

/**
 * @author Clinton Begin
 */
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;
    }

    /**
     * 解析${}和#{}
     *
     * @param text
     * @return 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
     * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
     */
    public String parse(String text) {
        // 验证参数问题,如果是null,就返回空字符串。
        if (text == null || text.isEmpty()) {
            return "";
        }

        // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
        int start = text.indexOf(openToken, 0);
        if (start == -1) {
            return text;
        }

        // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
        // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
        char[] src = text.toCharArray();
        int offset = 0;
        final StringBuilder builder = new StringBuilder();
        StringBuilder expression = null;
        while (start > -1) {
            // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
            if (start > 0 && src[start - 1] == '\\') {
                builder.append(src, offset, start - offset - 1).append(openToken);
                offset = start + openToken.length();
            } else {
                // 重置expression变量,避免空指针或者老数据干扰。
                if (expression == null) {
                    expression = new StringBuilder();
                } else {
                    expression.setLength(0);
                }
                builder.append(src, offset, start - offset);
                offset = start + openToken.length();
                int end = text.indexOf(closeToken, offset);
                while (end > -1) {////存在结束标记时
                    if (end > offset && src[end - 1] == '\\') {
                        // 如果结束标记前面有转义字符时
                        // 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 {
                        // 不存在转义字符,即需要作为参数进行处理
                        expression.append(src, offset, end - offset);
                        offset = end + closeToken.length();
                        break;
                    }
                }
                if (end == -1) {
                    // close token was not found.
                    builder.append(src, start, src.length - start);
                    offset = src.length;
                } else {
                    // 首先根据参数的key(即expression)进行参数处理,返回?作为占位符
                    builder.append(handler.handleToken(expression.toString()));
                    offset = end + closeToken.length();
                }
            }
            start = text.indexOf(openToken, offset);
        }
        if (offset < src.length) {
            builder.append(src, offset, src.length - offset);
        }
        return builder.toString();
    }
}

b. TokenHandler

接口

package com.itheima.utils;

public interface TokenHandler {
    String handleToken(String content);
}

实现类

package com.itheima.utils;

import java.util.ArrayList;
import java.util.List;


public class ParameterMappingTokenHandler implements TokenHandler {
    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

    // context 是参数名称 #{id} #{username}
    public String handleToken(String content) {
        parameterMappings.add(buildParameterMapping(content));
        return "?";
    }

    private ParameterMapping buildParameterMapping(String content) {
        ParameterMapping parameterMapping = new ParameterMapping(content);
        return parameterMapping;
    }

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

    public void setParameterMappings(List<ParameterMapping> parameterMappings) {
        this.parameterMappings = parameterMappings;
    }

}

c. ParameterMapping

public class ParameterMapping {

    private String content;

    public ParameterMapping(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
posted @ 2024-04-23 17:41  tree6x7  阅读(1)  评论(0编辑  收藏  举报