作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬
学习必须往深处挖,挖的越深,基础越扎实!
阶段1、深入多线程
阶段2、深入多线程设计模式
阶段3、深入juc源码解析
码哥源码部分
码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】
码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】
码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】
码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】
打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】
上一篇我们讲解到mapperElement方法用来解析mapper的,我们以packae属性为例详细分析一下:
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.addMappers(mapperPackage);
}
...
}
...
}
...
}
取得mappers标签下的package标签的name属性,调用configuration.addMappers(mapperPackage)方法,
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
我们看一下MapperRegistry的addMappers方法:
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
addMappers方法将包packageName下的所有类对象传入了addMapper方法,
addMapper方法将接口类对象放入一个map中,key为class对象value为new MapperProxyFactory(type),这个MapperProxyFactory就是为类对象产生代理对象的工厂。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
可以看到MapperProxyFactory就是使用JDK的动态代理Proxy.newProxyInstance方法创建一个代理对象,动态代理需要的InvocationHandler对象就是
这个mapperProxy,这个mapperProxy对象包含了从外部传入的SqlSession对象,动态代理需要的接口和一个Map<Method, MapperMethod>。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
- 如果调用代理对象方法是从Object继承的方法,直接调用父类的方法。
- 若果调用的方法是接口的静态抽象方法,以方法句柄的方式调用方法,方法句柄比反射拥有更好的性能
- 否则包装一个MapperMethod对象然后调用execute方法,在构造MapperMethod对象的时候会对其缓存避免重复调用此方法时重新构造MapperMethod
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
到现在为止我们知道调用Mapper对象的方法实际就是调用apperMethodM对象的execute方法就可以了,后面分析完mapper的xml解析后再详细看一下MapperMethod,因为MapperMethod会用到解析mapper的xml文件解析出来的MappedStatement,毕竟调用Mapper接口的代理对象的方法才会出发execute方法,那时Mapper映射文件早已经解析好放入Configuration中了。
再addMapper方法中下面这两句代码就是解析映射文件的了。
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
接下来逐步分析一下parse方法中的关键步骤。
1 loadXmlResource
private void loadXmlResource() {
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
XMLMapperBuilder从名字可以看出是准们解析xml映射文件的,BaseBuilder的子类,根据类名定位到xml文件,调用xmlParser.parse()方法解析。
1.1 xmlParser.parse()
public void parse() {
if (!configuration.isResourceLoaded(resource)) {//避免重复解析
configurationElement(parser.evalNode("/mapper"));//画重点
configuration.addLoadedResource(resource); //解析过了
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
1.1.1 configurationElement
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));//共用别的namespace的缓存实现,也就是cache-ref属性指定的映射文件中<cache>的实现类。
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
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);
}
}
1.1.1.1 cacheElement
private void cacheElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
type就是我们在标签type属性指定的类名,要求实现Cache接口,如果type没有指定就用默认的PERPETUAL(早已经注册过的别名的PerpetualCache),一个永不过期的缓存实现内部就是一个HashMap
eviction属性指定缓存过期的淘汰方式也是一个Cache实现,需要包装type的实现,所以是一个包装者模式的应用,默认为LRU(早已经注册过的别名的LruCache)最近最少使用到的先淘汰。
builderAssistant.useNewCache内部会创建一个CacheBuilder来设置上面解析出来的属性,通过反射调用Cache实现类的带有一个String构造参数的构造方法将namespace传入实例化我们的type。
所以我们编写自己的缓存类时要注意这一点。
public Cache build() {
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);//id就是namespace
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);//eviction装饰type
setCacheProperties(cache);
}
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
private Constructor<? extends Cache> getBaseCacheConstructor(Class<? extends Cache> cacheClass) {
try {
return cacheClass.getConstructor(String.class);
} catch (Exception e) {
throw new CacheException("Invalid base cache implementation (" + cacheClass + "). " +
"Base cache implementations must have a constructor that takes a String id as a parameter. Cause: " + e, e);
}
}
1.1.1.2 parameterMapElement parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
1.1.1.3 resultMapElement
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
大体意思就是将resultMap标签转换成ResultMap对象,resultMap标签的子标签result转换成ResultMapping对象,如果没有指定result的javaType,Mybatis就会根据property和type算出实际的javaType。最终将ResultMap存入Configuration的resultMaps(一个HashMap)中。
1.1.1.4 sqlElement
存储sql到Configuration.sqlFragments供引用
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);//存储sql到Configuration.sqlFragments
}
}
}
1.1.1.5 buildStatementFromContext
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
XMLStatementBuilder看名字解析...标签的。
public void parseStatementNode() {
String id = context.getStringAttribute("id");
... //获取属性值 太长了 省略了
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());//注释一
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);//注释二
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
注释一:
如果包含则通过refid去configuration找到对应的,此步骤可以根据configuration的variables动态替换refid中${..}的内容
然后替换的文本中的${..}用variables,将替换后的文本插入到当前节点中替换掉原有的。
注释二:
langDriver.createSqlSource中通过XMLScriptBuilder将node节点转换为SqlSource。我们着重分析一下XMLScriptBuilder
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
initNodeHandlerMap();
}
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
在构造函数中,注册了9种NodeHandler,每一种NodeHandler负责处理一种动态标签如WhereHandler处理标签。
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
parseDynamicTags方法负责将一个节点(如)下的子节点包装进一个MixedSqlNode对象中。在xml映射文件中,每一个元素都是一种SqlNode,MixedSqlNode将所有子节点统一管理,它们都继承了SqlNode,所以说MixedSqlNode是一个委派模式。 public class MixedSqlNode implements SqlNode { private final List<SqlNode> contents; public MixedSqlNode(List<SqlNode> contents) { this.contents = contents; } @Override public boolean apply(DynamicContext context) { for (SqlNode sqlNode : contents) { sqlNode.apply(context); } return true; } } isDynamic = true;有两种情况,一种是TextSqlNode含有${..}一种是含有这种元素。根据isDynamic返回分为DynamicSqlSource和RawSqlSource。 public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource = null; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; } 最后通过builderAssistant对象将以上从等标签解析出来的一系列对象组拼起来形成一个MappedStatement对象放入configuration中。
1.1.2 bindMapperForNamespace
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
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
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
对于那些以xml文件为入口而非class或package为入口的也就是下配置的是resource属性的,会通过namespace获取到MapperClass放入configuration,所以对于这种形式配置无需xml和Mapper放在同一目录下。
1.1.3 parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();
分别为因:
- resultMap的extends的没有解析,等待其余resultMap都解析完毕再次调用ResultMapResolver的resolve方法解析。此时若从configuration还得不到id为extends的ResultMap则忽略,因为parsePendingResultMaps中捕获了IncompleteElementException并啥也不做。
- refId引用的还未解析缓存下来,等到解析完毕再次解析。
- Cache-ref为解析完被耽误的MappedStatement再次解析。
2 parseCache();parseCacheRef();
解析标记在Mapper类上的@CacheNamespace和@CacheNamespaceRef,此处作用于xml映射文件的<cache>作用一样,如果两处都使用了则注解会覆盖xml映射文件。
private void parseCache() {
CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
if (cacheDomain != null) {
Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
Properties props = convertToProperties(cacheDomain.properties());
assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
}
}
3 parseStatement(method)方法与XMLStatementBuilder的作用一样,XMLStatementBuilder转换映射文件到MappedStatement,这个方法是处理Method以及其上的Mybatis注解。