Qwen3模型API的Agent智能体封装与任务编排

最近在折腾AI应用开发,发现一个挺有意思的事儿。很多朋友把大模型API直接拿来就用,输入一个问题,得到一个回答,这当然没问题。但遇到稍微复杂点的任务,比如“帮我写一份完整的项目方案”,或者“分析这份数据并生成报告”,单次调用就显得力不从心了。模型可能生成一段不错的开头,但后续的规划、信息整合、多轮交互,还得靠开发者自己写代码去“拼装”。

这让我想起了“智能体”(Agent)这个概念。简单说,就是给大模型装上“大脑”和“手脚”,让它不仅能回答问题,还能自己规划步骤、调用工具、管理状态,最终完成一个复杂的多步骤目标。今天,我就结合Qwen3模型的API,跟大家聊聊怎么动手封装一个具备自主规划能力的AI Agent,并以一个“技术文档生成Agent”为例,看看它如何从零开始,帮你搞定一份结构清晰、图文并茂的文档。

1. 为什么需要Agent?从单次问答到任务编排

直接调用大模型API,就像请了一位知识渊博但“一次只能做一件事”的专家。你问,他答,回合结束。而Agent模式,则是为这位专家配备了一个项目经理、一个工具箱和一套工作流程。当接到一个复杂任务时,Agent会自己拆解任务、制定计划、按需使用工具(比如搜索、计算、画图),并管理整个执行过程的状态,直到任务完成。

举个例子,用户说:“我想了解如何用Python进行Web开发,请给我写一份入门指南。”如果只用单次API调用,模型可能会生成一篇笼统的文章。但一个文档生成Agent会怎么做呢?它可能会先规划:第一步,搜索(或基于内部知识)确定Python Web开发的核心框架(如Django, Flask)。第二步,为每个框架规划章节,包括简介、安装、快速示例。第三步,依次调用文本生成能力撰写每个章节的内容。第四步,识别哪些部分适合用代码示例或架构图说明,并调用相应的工具生成或描述这些视觉元素。第五步,将所有内容整合、格式化,输出一份完整的文档。

这个过程中,Agent的核心能力就体现出来了:自主规划、工具调用和状态管理。接下来,我们就看看如何利用Qwen3的API来构建这样一个智能体。

2. 构建基石:Qwen3 API与智能体基础架构

在开始封装Agent之前,我们得先和Qwen3模型“打好交道”。Qwen3提供了功能丰富的API,特别是其强大的长文本理解和生成能力,以及良好的指令遵循(Function Calling)特性,这使其非常适合作为Agent的“大脑”。

2.1 Qwen3 API快速接入

首先,你需要获取API密钥并安装必要的SDK。这里以Python为例:

# 安装官方SDK (示例,请以官方最新文档为准)
# pip install dashscope

from http import HTTPStatus
import dashscope

# 设置你的API Key
dashscope.api_key = '你的API-KEY'

def call_qwen3(prompt, model='qwen-max'):
    """调用Qwen3进行文本生成"""
    response = dashscope.Generation.call(
        model=model,
        prompt=prompt,
        max_tokens=1500,
        temperature=0.7, # 控制创造性,文档生成可以调低如0.3
    )
    if response.status_code == HTTPStatus.OK:
        return response.output.text
    else:
        print(f'请求失败,状态码: {response.status_code}, 错误信息: {response.message}')
        return None

# 简单测试
test_response = call_qwen3("请用一句话介绍Python。")
print(test_response)

这只是最基础的文本补全。对于Agent,我们更需要模型能够理解“工具”和“计划”。Qwen3支持类似OpenAI的Function Calling功能,可以让模型在对话中决定何时、以及如何调用我们定义好的函数(工具)。

2.2 智能体的核心架构设计

一个典型的任务型Agent,可以抽象成几个核心组件:

  1. 规划器(Planner):核心“大脑”,通常由大模型本身担任。它负责理解用户目标,并将其分解成一系列可执行的子任务或步骤。
  2. 工具集(Toolkit):Agent的“手脚”。是一系列函数,每个函数能完成一个特定操作,如“搜索网络”、“执行计算”、“生成图片描述”、“保存文件”等。规划器可以决定调用哪个工具。
  3. 执行器(Executor):负责实际调用规划器选择的工具,并处理工具执行的结果。
  4. 状态管理器(State Manager):维护任务执行的上下文。它记录当前计划、已完成的步骤、各步骤的结果、以及下一步该做什么。这是实现多轮交互和复杂流程的关键。
  5. 记忆(Memory):存储对话历史、工具调用历史等,帮助模型在长程任务中保持一致性。

它们之间的关系,可以用一个简单的循环来描述:接收用户输入 -> 更新状态 -> 规划器基于当前状态决定下一步行动(调用工具或直接回答)-> 执行器执行 -> 将结果反馈并更新状态 -> 判断任务是否完成,若未完成则继续循环

接下来,我们就用代码来具象化这个架构,打造我们的文档生成Agent# 1. 概述

本文主要分享 MyBatis 的初始化流程

相信很多同学都使用过 MyBatis ,那么先一起来回忆下,它的基本使用步骤。代码如下:

// 1.创建 SqlSessionFactory 对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// 2.创建 SqlSession 对象
SqlSession session = sqlSessionFactory.openSession();

// 3.获取 Mapper 对象
UserMapper userMapper = session.getMapper(UserMapper.class);

// 4.执行 SQL 语句
List<User> users = userMapper.listByUsername("calm");
  • #1 处,基于 mybatis-config.xml 配置文件,创建 SqlSessionFactory 对象。MyBatis 的初始化,就发生在 SqlSessionFactory 创建的过程中
  • #2 处,基于 SqlSessionFactory 对象,创建 SqlSession 对象。
  • #3 处,基于 SqlSession 对象,获得 Mapper 对象。
  • #4 处,基于 Mapper 对象,进行数据库操作。

那么,本文我们就来看看,SqlSessionFactoryBuilder#build(inputStream) 方法中,MyBatis 初始化的过程。在这个过程中,会加载 mybatis-config.xml 配置文件,以及 Mapper 映射配置文件。最终,我们会得到一个 Configuration 对象,它是 MyBatis 初始化的核心结果。后续,SqlSessionFactory 基于 Configuration 对象进行创建。

另外,胖友如果对 MyBatis 的初始化不了解,建议先阅读下 《MyBatis 文档 —— 入门》《MyBatis 文档 —— 配置》

2. 加载 mybatis-config.xml

老艿艿:本小节,我们主要看 mybatis-config.xml 配置文件的加载解析过程。

2.1 SqlSessionFactoryBuilder

org.apache.ibatis.session.SqlSessionFactoryBuilder ,SqlSessionFactory 构造器。代码如下:

// SqlSessionFactoryBuilder.java

public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(Reader reader) {
        return build(reader, null, null);
    }

    public SqlSessionFactory build(Reader reader, String environment) {
        return build(reader, environment, null);
    }

    public SqlSessionFactory build(Reader reader, Properties properties) {
        return build(reader, null, properties);
    }

    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        try {
            // 创建 XMLConfigBuilder 对象
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            // 执行 XML 解析
            // 创建 DefaultSqlSessionFactory 对象
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                reader.close();
            } catch (IOException e) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }

    public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
    }

    public SqlSessionFactory build(InputStream inputStream, String environment) {
        return build(inputStream, environment, null);
    }

    public SqlSessionFactory build(InputStream inputStream, Properties properties) {
        return build(inputStream, null, properties);
    }

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // 创建 XMLConfigBuilder 对象
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 执行 XML 解析
            // 创建 DefaultSqlSessionFactory 对象
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException e) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

}
  • 虽然,SqlSessionFactoryBuilder 有非常多的 #build(...) 方法,但是最终都是创建一个 XMLConfigBuilder 对象,调用 XMLConfigBuilder#parse() 方法,执行 XML 解析,返回 Configuration 对象。然后,再创建出 DefaultSqlSessionFactory 对象。
  • 所以,实际上,SqlSessionFactoryBuilder 是不重复的。也就是说,SqlSessionFactoryBuilder 的最佳作用域是方法作用域(也就是局部方法变量)。

2.2 XMLConfigBuilder

org.apache.ibatis.builder.xml.XMLConfigBuilder ,继承 BaseBuilder 抽象类,XML 配置构建器,主要负责解析 mybatis-config.xml 配置文件。即 《MyBatis 文档 —— 配置》 下的各个标签。

2.2.1 构造方法

// XMLConfigBuilder.java

/**
 * 是否已解析
 */
private boolean parsed;
/**
 * 基于 Java XPath 解析器
 */
private final XPathParser parser;
/**
 * 环境
 */
private String environment;
/**
 * ReflectorFactory 对象
 */
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    // <1> 创建 Configuration 对象
    super(new Configuration());
    ErrorContext.instance().location("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}
  • <1> 处,创建 Configuration 对象,并赋值给 BaseBuilder.configuration 属性。
  • <2> 处,设置 configurationvariables 属性。
  • <3> 处,创建 XPathParser 对象,并赋值给 parser 属性。XPathParser 是内部使用 XPath 解析 XML 的封装类。详细解析,见 《精尽 MyBatis 源码分析 —— 解析器模块》

2.2.2 parse

#parse() 方法,解析 XML 成 Configuration 对象。代码如下:

// XMLConfigBuilder.java

public Configuration parse() {
    // 若已解析,抛出 BuilderException 异常
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    // 标记已解析
    parsed = true;
    // 解析 XML configuration 节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}
  • 调用 XPathParser#evalNode(String expression) 方法,获得 XML <configuration /> 节点,后调用 #parseConfiguration(XNode root) 方法,解析该节点。

2.2.3 parseConfiguration

#parseConfiguration(XNode root) 方法,解析 <configuration /> 节点。代码如下:

// XMLConfigBuilder.java

private void parseConfiguration(XNode root) {
    try {
        //issue #117 read properties first
        // 解析 <properties /> 标签
        propertiesElement(root.evalNode("properties"));
        // 解析 <settings /> 标签
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        // 加载自定义的 VFS 实现类
        loadCustomVfs(settings);
        // 加载自定义的 Log 实现类
        loadCustomLogImpl(settings);
        // 解析 <typeAliases /> 标签
        typeAliasesElement(root.evalNode("typeAliases"));
        // 解析 <plugins /> 标签
        pluginElement(root.evalNode("plugins"));
        // 解析 <objectFactory /> 标签
        objectFactoryElement(root.evalNode("objectFactory"));
        // 解析 <objectWrapperFactory /> 标签
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        // 解析 <reflectorFactory /> 标签
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        // 赋值 <settings /> 到 Configuration 属性
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        // 解析 <environments /> 标签
        environmentsElement(root.evalNode("environments"));
        // 解析 <databaseIdProvider /> 标签
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // 解析 <typeHandlers /> 标签
        typeHandlerElement(root.evalNode("typeHandlers"));
        // 解析 <mappers /> 标签
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

2.3 BaseBuilder

org.apache.ibatis.builder.BaseBuilder ,基础构造器抽象类,为子类提供通用的工具类方法。

2.3.1 构造方法

// BaseBuilder.java

/**
 * MyBatis Configuration 对象
 */
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;

public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
  • 每个 BaseBuilder 的子类,都会持有 Configuration 对象。

2.3.2 其它方法

BaseBuilder 中,提供了多个常用的方法。代码如下:

// BaseBuilder.java

// 创建 ErrorContext 对象
protected ErrorContext instance() {
    return ErrorContext.instance();
}

// 创建 Pattern 对象
protected Pattern parseExpression(String regex, String defaultValue) {
    return Pattern.compile(regex == null ? defaultValue : regex);
}

// 创建 Boolean 对象
protected Boolean booleanValueOf(String value, Boolean defaultValue) {
    return value == null ? defaultValue : Boolean.valueOf(value);
}

// 创建 Integer 对象
protected Integer integerValueOf(String value, Integer defaultValue) {
    return value == null ? defaultValue : Integer.valueOf(value);
}

// 创建 Set 对象
protected Set<String> stringSetValueOf(String value, String defaultValue) {
    value = value == null ? defaultValue : value;
    return new HashSet<>(Arrays.asList(value.split(",")));
}

// 解析 JdbcType 类型
protected JdbcType resolveJdbcType(String alias) {
    if (alias == null) {
        return null;
    }
    try {
        return JdbcType.valueOf(alias);
    } catch (IllegalArgumentException e) {
        throw new BuilderException("Error resolving JdbcType. Cause: " + e);
    }
}

// 解析 ResultSetType 类型
protected ResultSetType resolveResultSetType(String alias) {
    if (alias == null) {
        return null;
    }
    try {
        return ResultSetType.valueOf(alias);
    } catch (IllegalArgumentException e) {
        throw new BuilderException("Error resolving ResultSetType. Cause: " + e);
    }
}

// 解析 ParameterMode 类型
protected ParameterMode resolveParameterMode(String alias) {
    if (alias == null) {
        return null;
    }
    try {
        return ParameterMode.valueOf(alias);
    } catch (IllegalArgumentException e) {
        throw new BuilderException("Error resolving ParameterMode. Cause: " + e);
    }
}

// 创建指定类
protected Object createInstance(String alias) {
    // 获得类
    Class<?> clazz = resolveClass(alias);
    if (clazz == null) {
        return null;
    }
    try {
        // 创建对象
        return clazz.newInstance();
    } catch (Exception e) {
        throw new BuilderException("Error creating instance. Cause: " + e);
    }
}

// 创建指定类
@SuppressWarnings("unchecked") // 压制告警。因为方法返回类型,和 clazz 类型一样
protected <T> T createInstance(Class<T> clazz) {
    if (clazz == null) {
        return null;
    }
    try {
        return clazz.newInstance();
    } catch (Exception e) {
        throw new BuilderException("Error creating instance. Cause: " + e);
    }
}

// 解析类
protected Class<?> resolveClass(String alias) {
    if (alias == null) {
        return null;
    }
    try {
        return resolveAlias(alias);
    } catch (Exception e) {
        throw new BuilderException("Error resolving class. Cause: " + e);
    }
}

// 解析类
protected Class<?> resolveAlias(String alias) {
    return typeAliasRegistry.resolveAlias(alias);
}

// 解析 TypeHandler 类
protected TypeHandler<?> resolveTypeHandler(Class<?> javaType, String typeHandlerAlias) {
    if (typeHandlerAlias == null) {
        return null;
    }
    // 获得 TypeHandler 类
    Class<?> type = resolveClass(typeHandlerAlias);
    // 如果不存在,抛出 BuilderException 异常
    if (type != null && !TypeHandler.class.isAssignableFrom(type)) {
        throw new BuilderException("Type " + type.getName() + " is not a valid TypeHandler because it does not implement " + TypeHandler.class.getName());
    }
    @SuppressWarnings("unchecked") // 压制告警。因为方法返回类型,和 clazz 类型一样
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) type;
    // 创建 TypeHandler 对象
    return resolveTypeHandler(javaType, typeHandlerClass);
}

// 解析 TypeHandler 类
protected TypeHandler<?> resolveTypeHandler(Class<?> javaType, Class<? extends TypeHandler<?>> typeHandlerClass) {
    if (typeHandlerClass == null) {
        return null;
    }
    // javaType defaults to Object
    if (javaType == null) {
        javaType = Object.class;
    }
    // 获得 TypeHandler 对象
    TypeHandler<?> handler = typeHandlerRegistry.getMappingTypeHandler(typeHandlerClass);
    if (handler == null) {
        // 如果不存在,创建 TypeHandler 对象
        handler = typeHandlerRegistry.getInstance(javaType, typeHandlerClass);
    }
    return handler;
}

// 解析类型
protected Class<?> resolveTypeHandlerClass(Class<?> javaType, Class<? extends TypeHandler<?>> typeHandlerClass) {
    if (typeHandlerClass == null) {
        return null;
    }
    // javaType defaults to Object
    if (javaType == null) {
        javaType = Object.class;
    }
    // 获得 TypeHandler 对象
    TypeHandler<?> handler = typeHandlerRegistry.getMappingTypeHandler(typeHandlerClass);
    if (handler == null) {
        // 如果不存在,创建 TypeHandler 对象
        handler = typeHandlerRegistry.getInstance(javaType, typeHandlerClass);
    }
    return handler.getClass();
}
  • 比较简单,胖友自己瞅瞅。

2.4 XMLMapperEntityResolver

org.apache.ibatis.builder.xml.XMLMapperEntityResolver ,实现 EntityResolver 接口,XML Mapper 的 EntityResolver 实现类,用于加载本地的 mybatis-3-config.dtdmybatis-3-mapper.dtd 这两个 DTD 文件。代码如下:

// XMLMapperEntityResolver.java

public class XMLMapperEntityResolver implements EntityResolver {

    private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
    private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
    private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
    private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";

    /**
     * 本地 mybatis-config.dtd 文件
     */
    private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
    /**
     * 本地 mybatis-mapper.dtd 文件
     */
    private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";

    /**
     * Converts a public DTD into a local one
     *
     * @param publicId The public id that is what comes after "PUBLIC"
     * @param systemId The system id that is what comes after the public id.
     * @return The InputSource for the DTD
     * @throws org.xml.sax.SAXException If anything goes wrong
     */
    @Override
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
        try {
            if (systemId != null) {
                String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
                // 本地 mybatis-config.dtd 文件
                if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
                    return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
                // 本地 mybatis-mapper.dtd 文件
                } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
                    return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
                }
            }
            return null;
        } catch (Exception e) {
            throw new SAXException(e.toString());
        }
    }

    private InputSource getInputSource(String path, String publicId, String systemId) {
        InputSource source = null;
        if (path != null) {
            try {
                // 创建 InputSource 对象
                InputStream in = Resources.getResourceAsStream(path);
                source = new InputSource(in);
                // 设置  publicId、systemId 属性
                source.setPublicId(publicId);
                source.setSystemId(systemId);
            } catch (IOException e) {
                // ignore, null is ok
            }
        }
        return source;
    }

}
  • 代码比较简单,胖友自己瞅瞅。

3. 加载 Mapper 映射配置文件

老艿艿:本小节,我们主要看 Mapper 映射配置文件的加载解析过程。

XMLConfigBuilder#mapperElement(XNode context) 方法中,加载 Mapper 映射配置文件。代码如下:

// XMLConfigBuilder.java

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        // 遍历子节点
        for (XNode child : parent.getChildren()) {
            // 包扫描
            if ("package".equals(child.getName())) {
                // 获得包名
                String mapperPackage = child.getStringAttribute("name");
                // 添加到 configuration 中
                configuration.addMappers(mapperPackage);
            // 类扫描
            } else {
                // 获得 resource、url、class 属性
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                // 使用 resource
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    // 获得 resource 的 InputStream 对象
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    // 创建 XMLMapperBuilder 对象
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    // 执行解析
                    mapperParser.parse();
                // 使用 url
                } else if (resource == null && url != null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    // 获得 url 的 InputStream 对象
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    // 创建 XMLMapperBuilder 对象
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    // 执行解析
                    mapperParser.parse();
                // 使用 class
                } else if (resource == null && url == null && mapperClass != null) {
                    // 获得 Mapper 接口
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    // 添加到 configuration 中
                    configuration.addMapper(mapperInterface);
                } else {
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}
  • 遍历 <mappers /> 的子节点,处理每个节点。根据节点是 <package /> ,还是 <mapper /> ,执行不同的逻辑。但是,最终的目的都是解析 Mapper 映射配置文件,将每个 MappedStatement 注册到 Configuration 中。
  • 关于 configuration.addMappers(mapperPackage)configuration.addMapper(mapperInterface) 方法,我们放在下一篇博客,和 Mapper 注解一起分享。
  • 关于 XMLMapperBuilder 类,我们放在 《精尽 MyBatis 源码分析 —— MyBatis 初始化(二)之加载 Mapper 映射配置文件》 中,详细解析。

4. 加载 Statement

老艿艿:本小节,我们主要看 Statement 的加载解析过程。

XMLMapperBuilder#configurationElement(XNode context) 方法中,加载 Statement 。代码如下:

// XMLMapperBuilder.java

private void configurationElement(XNode context) {
    try {
        // 获得 namespace 属性
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        // 设置 namespace 属性
        builderAssistant.setCurrentNamespace(namespace);
        // 解析 cache-ref 节点
        cacheRefElement(context.evalNode("cache-ref"));
        // 解析 cache 节点
        cacheElement(context.evalNode("cache"));
        // 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // 解析 resultMap 节点
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // 解析 sql 节点
        sqlElement(context.evalNodes("/mapper/sql"));
        // 解析 select|insert|update|delete 节点
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
}
  • 关于 #buildStatementFromContext(context.evalNodes("select|insert|update|delete")) 方法,是加载 Statement 们。代码如下:

    // XMLMapperBuilder.java
    
    private void buildStatementFromContext(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
            buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
    }
    
    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        // 遍历 <select /> <insert /> <update /> <delete /> 节点们
        for (XNode context : list) {
            // 创建 XMLStatementBuilder 对象,执行解析
            final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
            try {
                statementParser.parseStatementNode();
            } catch (IncompleteElementException e) {
                configuration.addIncompleteStatement(statementParser);
            }
        }
    }
    

5. 绑定 Mapper 接口

在 Mapper 映射配置文件解析完成后,我们需要将 Mapper 接口,和 Mapper 映射配置文件绑定起来。这样,后续调用 Mapper 接口方法,就能执行对应的 SQL 语句。代码如下:

// XMLMapperBuilder.java

private void bindMapperForNamespace() {
    // 获得 namespace 属性
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
        // 获得 Mapper 接口
        Class<?> boundType = null;
        try {
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
        }
        if (boundType != null) {
            // 如果当前没有绑定,则进行绑定
            if (!configuration.hasMapper(boundType)) {
                // Spring may not know the real resource name so we set a flag
                // to prevent loading again this resource from the mapper interface
                // look at MapperAnnotationBuilder#loadXmlResource
                // 标记 namespace 已经添加过
                configuration.addLoadedResource("namespace:" + namespace);
                // 添加到 configuration 中
                configuration.addMapper(boundType);
            }
        }
    }
}
  • 关于 configuration.addMapper(boundType) 方法,我们放在下一篇博客,和 Mapper 注解一起分享。

6. 处理 incomplete 节点

在解析 Mapper 映射配置文件的过程,如果出现错误,会将该节点添加到 Configuration 的 incomplete* 属性中。最终,在 XMLMapperBuilder#parse() 方法中,会调用这些方法,进行解析。代码如下:

// XMLMapperBuilder.java

public void parse() {
    // 判断当前 Mapper 是否已经加载过
    if (!configuration.isResourceLoaded(resource)) {
        // 解析 `<mapper />` 节点
        configurationElement(parser.evalNode("/mapper"));
        // 标记该 Mapper 已经加载过
        configuration.addLoadedResource(resource);
        // 绑定 Mapper
        bindMapperForNamespace();
    }

    // 解析待处理的 <resultMap /> 节点
    parsePendingResultMaps();
    // 解析待处理的 <cache-ref /> 节点
    parsePendingCacheRefs();
    // 解析待处理的 SQL 语句的节点
    parsePendingStatements();
}
  • 关于 #parsePendingResultMaps()#parsePendingCacheRefs()#parsePendingStatements() 方法,我们放在下一篇博客,和 Mapper 注解一起分享。

7. 创建 SqlSessionFactory 对象

SqlSessionFactoryBuilder#build(Configuration config) 方法中,创建 SqlSessionFactory 对象。代码如下:

// SqlSessionFactoryBuilder.java

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}
  • 关于 DefaultSqlSessionFactory 类,在后续的文章中,详细解析。

8. 总结

本文主要简单梳理了 MyBatis 初始化的过程。更详细的解析,我们放在下面两篇文章中。

另外,推荐阅读 《MyBatis 源码分析 - 配置文件解析过程》 文章。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐