Qwen3模型API的Agent智能体封装与任务编排
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,可以抽象成几个核心组件:
- 规划器(Planner):核心“大脑”,通常由大模型本身担任。它负责理解用户目标,并将其分解成一系列可执行的子任务或步骤。
- 工具集(Toolkit):Agent的“手脚”。是一系列函数,每个函数能完成一个特定操作,如“搜索网络”、“执行计算”、“生成图片描述”、“保存文件”等。规划器可以决定调用哪个工具。
- 执行器(Executor):负责实际调用规划器选择的工具,并处理工具执行的结果。
- 状态管理器(State Manager):维护任务执行的上下文。它记录当前计划、已完成的步骤、各步骤的结果、以及下一步该做什么。这是实现多轮交互和复杂流程的关键。
- 记忆(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>处,设置configuration的variables属性。<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);
}
}
- 逐个调用方法,解析对应的节点。详细解析,见 《精尽 MyBatis 源码分析 —— MyBatis 初始化(二)之加载 Mapper 映射配置文件》 和 《精尽 MyBatis 源码分析 —— MyBatis 初始化(三)之加载 Statement》 。
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.dtd 和 mybatis-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); } } }- 关于 XMLStatementBuilder 类,我们放在 《精尽 MyBatis 源码分析 —— MyBatis 初始化(三)之加载 Statement》 中,详细解析。
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 源码分析 - 配置文件解析过程》 文章。
更多推荐


所有评论(0)