MyBatis体系结构

MyBatis的工作流程

  • 在MyBatis启动的时候我们要去解析配置文件,包括全局配置文件和映射器配置文件,我们会把它们解析成一个Configuration对象,里面会包含各种配置文件的参数信息
  • 创建一个包含Configuration会话工厂SqlSessionFactory ,通过它来创建SqlSession对象,SqlSession是我们操作数据库的接口,代表跟数据库之间的一次连接
  • 执行具体的SQL或接口方法 实际底层是通过SqlSession实现类里的Executor封装了对数据库的操作

MyBatis的主要工作流程里面,不同的功能是由很多不同的类协作完成的,可以看下Mybatis jar包的结构

大体可以把相关的类按照功能划分为如下几个层次

MyBatis源码解读和工作原理

我们可以从MyBatis最基本的使用着手看源码

@Test
public void Test() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//1.通过SqlSessionFactoryBuilder解析配置文件创建工厂类SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//2.通过SqlSessionFactory创建SqlSessionSqlSession session = sqlSessionFactory.openSession();try {//3.获得一个Mapper对象BlogMapper mapper = session.getMapper(BlogMapper.class);//4.执行对应的方法Blog blog = mapper.selectBlogById(1);System.out.println(blog);} finally {session.close();}}

配置解析

 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {SqlSessionFactory var5;try {//MyBatis有各种各样的XMLXXXBuilder,ConfigBuilder用来解析mybatis-config.xml,还有XMLMapperBuilder,XMLStatementBuilder等//它们都继承自抽象父类BaseBuilderXMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);var5 = this.build(parser.parse());} catch (Exception var14) {throw ExceptionFactory.wrapException("Error building SqlSession.", var14);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException var13) {}}return var5;}public Configuration parse() {//如果解析过配置文件就不会报错,MyBatis的配置文件只在启动的时候解析一次就够了if (this.parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");} else {this.parsed = true;//真正解析配置文件的方法,从根节点configuration开始解析mybatis-config.xmlthis.parseConfiguration(this.parser.evalNode("/configuration"));return this.configuration;}}//解析mybatis-config.xml里的各种标签
private void parseConfiguration(XNode root) {try {this.propertiesElement(root.evalNode("properties"));Properties settings = this.settingsAsProperties(root.evalNode("settings"));this.loadCustomVfs(settings);this.loadCustomLogImpl(settings);this.typeAliasesElement(root.evalNode("typeAliases"));this.pluginElement(root.evalNode("plugins"));this.objectFactoryElement(root.evalNode("objectFactory"));this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));this.reflectorFactoryElement(root.evalNode("reflectorFactory"));this.settingsElement(settings);this.environmentsElement(root.evalNode("environments"));this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));//解析TypeHandler,最后存储在Map<Type, Map<JdbcType, TypeHandler<?>>> 的嵌套Map里this.typeHandlerElement(root.evalNode("typeHandlers"));this.mapperElement(root.evalNode("mappers"));} catch (Exception var3) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);}}private void propertiesElement(XNode context) throws Exception {//解析Properties标签的时候会获取子标签和属性字段if (context != null) {Properties defaults = context.getChildrenAsProperties();String resource = context.getStringAttribute("resource");String url = context.getStringAttribute("url");if (resource != null && url != null) {throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");}if (resource != null) {defaults.putAll(Resources.getResourceAsProperties(resource));} else if (url != null) {defaults.putAll(Resources.getUrlAsProperties(url));}Properties vars = this.configuration.getVariables();if (vars != null) {defaults.putAll(vars);}//把解析得到的Properties对象defaults赋值给XPathParser和configuration对象this.parser.setVariables(defaults);this.configuration.setVariables(defaults);}}private void typeAliasesElement(XNode parent) {if (parent != null) {Iterator var2 = parent.getChildren().iterator();while(var2.hasNext()) {XNode child = (XNode)var2.next();String alias;if ("package".equals(child.getName())) {alias = child.getStringAttribute("name");this.configuration.getTypeAliasRegistry().registerAliases(alias);} else {alias = child.getStringAttribute("alias");String type = child.getStringAttribute("type");try {Class<?> clazz = Resources.classForName(type);if (alias == null) {this.typeAliasRegistry.registerAlias(clazz);} else {//会把配置的别名和类型注册到typeAliasRegistry里this.typeAliasRegistry.registerAlias(alias, clazz);}} catch (ClassNotFoundException var7) {throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);}}}}//解析对应的plugins标签,保存到InterceptorChain的Interceptor集合里private void pluginElement(XNode parent) throws Exception {if (parent != null) {Iterator var2 = parent.getChildren().iterator();while(var2.hasNext()) {XNode child = (XNode)var2.next();String interceptor = child.getStringAttribute("interceptor");Properties properties = child.getChildrenAsProperties();Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();interceptorInstance.setProperties(properties);this.configuration.addInterceptor(interceptorInstance);}}}public void addInterceptor(Interceptor interceptor) {this.interceptorChain.addInterceptor(interceptor);}//对于settings标签的子标签的处理
//二级标签里面有很多的配置,比如二级缓存,延迟加载等。之前提到的所有的默认值,都是在这里赋值的
private void settingsElement(Properties props) {this.configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));this.configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));this.configuration.setCacheEnabled(this.booleanValueOf(props.getProperty("cacheEnabled"), true));this.configuration.setProxyFactory((ProxyFactory)this.createInstance(props.getProperty("proxyFactory")));this.configuration.setLazyLoadingEnabled(this.booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));this.configuration.setAggressiveLazyLoading(this.booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));this.configuration.setMultipleResultSetsEnabled(this.booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));this.configuration.setUseColumnLabel(this.booleanValueOf(props.getProperty("useColumnLabel"), true));this.configuration.setUseGeneratedKeys(this.booleanValueOf(props.getProperty("useGeneratedKeys"), false));this.configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));this.configuration.setDefaultStatementTimeout(this.integerValueOf(props.getProperty("defaultStatementTimeout"), (Integer)null));this.configuration.setDefaultFetchSize(this.integerValueOf(props.getProperty("defaultFetchSize"), (Integer)null));this.configuration.setMapUnderscoreToCamelCase(this.booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));this.configuration.setSafeRowBoundsEnabled(this.booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));this.configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));this.configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));this.configuration.setLazyLoadTriggerMethods(this.stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));this.configuration.setSafeResultHandlerEnabled(this.booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));this.configuration.setDefaultScriptingLanguage(this.resolveClass(props.getProperty("defaultScriptingLanguage")));this.configuration.setDefaultEnumTypeHandler(this.resolveClass(props.getProperty("defaultEnumTypeHandler")));this.configuration.setCallSettersOnNulls(this.booleanValueOf(props.getProperty("callSettersOnNulls"), false));this.configuration.setUseActualParamName(this.booleanValueOf(props.getProperty("useActualParamName"), true));this.configuration.setReturnInstanceForEmptyRow(this.booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));this.configuration.setLogPrefix(props.getProperty("logPrefix"));this.configuration.setConfigurationFactory(this.resolveClass(props.getProperty("configurationFactory")));}
...对于其他的标签就不一一解释了

再分析下关于Mapper的解析

//如果Mapper引入的时候使用的Mapper的class,会调用addMapper注册到MapperRegistry里,保存在Map<Class<?>, MapperProxyFactory<?>>容器里
//如果引入使用的mapper.xml,会通过XMLMapperBuilder来解析mapper配置文件private void mapperElement(XNode parent) throws Exception {if (parent != null) {Iterator var2 = parent.getChildren().iterator();while(true) {while(var2.hasNext()) {XNode child = (XNode)var2.next();String resource;//子节点有两种情况,package:把包下的所有的mapper解析为映射器,mapper:针对指定的mapper文件解析成映射器if ("package".equals(child.getName())) {resource = child.getStringAttribute("name");this.configuration.addMappers(resource);} else {resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");//根据resource,url,class三个属性的值选择不同的解析方法                 XMLMapperBuilder mapperParser;InputStream inputStream;if (resource != null && url == null && mapperClass == null) {//通过XMLMapperBuilder来解析MapperErrorContext.instance().resource(resource);inputStream = Resources.getResourceAsStream(resource);mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);inputStream = Resources.getUrlAsStream(url);mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());mapperParser.parse();} else {if (resource != null || url != null || mapperClass == null) {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}Class<?> mapperInterface = Resources.classForName(mapperClass);this.configuration.addMapper(mapperInterface);}}}return;}}}//XMLMapperBuilder的解析过程
public void parse() {if (!this.configuration.isResourceLoaded(this.resource)) {//解析mappr.xml里的所有标签,其中buildStatementFromContext()方法最终会将MappedStatement对象添加到configurationthis.configurationElement(this.parser.evalNode("/mapper"));this.configuration.addLoadedResource(this.resource);//通过namespace绑定Mapper类,如果是configurationElement里没有注册到mapperRegistry的会调用Configuration的addMapper方法注册进去this.bindMapperForNamespace();}this.parsePendingResultMaps();this.parsePendingCacheRefs();this.parsePendingStatements();}

如果<mappers>标签里引入的是mapper.xml,那么会解析对应的xml文件,组装成MappedStatement对象,然后添加到Configuration的 Map<String, MappedStatement> mappedStatements 里

------------MapperRegistry 类
public <T> void addMapper(Class<T> type) {if (type.isInterface()) {if (this.hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {//把Mapper的class和对应的MapperProxyFactory添加到knownMappers map里this.knownMappers.put(type, new MapperProxyFactory(type));MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);//通过MapperAnnotationBuilder解析Mapperparser.parse();loadCompleted = true;} finally {if (!loadCompleted) {this.knownMappers.remove(type);}}}}----- MapperAnnotationBuilder 的parse()方法public void parse() {String resource = this.type.toString();if (!this.configuration.isResourceLoaded(resource)) {this.loadXmlResource();this.configuration.addLoadedResource(resource);this.assistant.setCurrentNamespace(this.type.getName());//对@CacheNamespace 和@CacheNamespaceRef注解进行处理this.parseCache();this.parseCacheRef();Method[] methods = this.type.getMethods();Method[] var3 = methods;int var4 = methods.length;for(int var5 = 0; var5 < var4; ++var5) {Method method = var3[var5];try {if (!method.isBridge()) {//遍历Mapper的所有方法,解析得到MappedStatement添加到configuration里,以namespace + statement id为key,MappedStatement为valuethis.parseStatement(method);}} catch (IncompleteElementException var8) {this.configuration.addIncompleteMethod(new MethodResolver(this, method));}}}this.parsePendingMethods();}

在addMapper里,会创建一个MapperAnnotationBuilder对象来对注解进行处理
parseCache() 和 parseCacheRef() 方 法 其 实 是 对 @CacheNamespace 和@CacheNamespaceRef这两个注解的处理。
parseStatement()方法里也都是对注解的解析,比如@Options,@SelectKey,@ResultMap等。
最后同样会解析成 MappedStatement 对象

解析完成后的build方法会使用Configuration创建一个DefaultSqlSessionFactory对象

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {SqlSessionFactory var5;try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);var5 = this.build(parser.parse());} catch (Exception var14) {throw ExceptionFactory.wrapException("Error building SqlSession.", var14);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException var13) {}}return var5;
}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);
}

会话创建过程

//这里创建DefaultSqlSession会先通过configuration里的environment创建一个事务工厂
//再通过事务和定义的执行器类型创建对应的Executor,这是真正执行SQL的核心类
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;DefaultSqlSession var8;try {Environment environment = this.configuration.getEnvironment();TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);Executor executor = this.configuration.newExecutor(tx, execType);var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);} catch (Exception var12) {this.closeTransaction(tx);throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);} finally {ErrorContext.instance().reset();}return var8;}//根据不同的executorType创建对应的Executor,默认是Simple
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? this.defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Object executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}//如果配置了开启二级缓存(默认cacheEnabled = true),会使用CachingExecutor装饰executor---装饰器模式if (this.cacheEnabled) {executor = new CachingExecutor((Executor)executor);}//这里通过配置的plugin插件对executor进行处理包装  具体的原理后面说明Executor executor = (Executor)this.interceptorChain.pluginAll(executor);return executor;}

三种不同的Executor:

SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor:会重复使用Statement对象。使用完之后,不关闭 Statement 对象,而是存储在 Map 内,供下一次使用
BatchExecutor:支持批处理的Executor 。

获得Mapper对象

我们通过SqlSession调用getMapper接口最终会通过mapperRegistry根据type和当前会话获取Mapper

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return this.mapperRegistry.getMapper(type, sqlSession);
}public <T> T getMapper(Class<T> type, SqlSession sqlSession) {MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");} else {try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception var5) {throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);}}
}-------MapperProxyFactoryprotected T newInstance(MapperProxy<T> mapperProxy) {return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);}public T newInstance(SqlSession sqlSession) {MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);return this.newInstance(mapperProxy);}

从上面的源码不难看出,我们获取到的Mapper实际是根据解析配置时注册的MapperProxyFactory通过动态代理生成的代理对象,这样就解释了为什么我们可以不需要创建Mapper的实现类就可以直接调用方法,因为MyBatis帮我们返回了代理对象。而我们使用Mapper只是为了根据接口类型+方法的名称,找到对应namespace下的Statement ID,所以不需要实现类,在 MapperProxy 里面直接执行SQL就可以。

执行SQL

由于所有的 Mapper 都是使用 MapperProxy 增强的代理对象,所以执行任何方法其实真实执行的都是MapperProxy的invoke()方
法。

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {//Object本身的方法和Java 8中接口的默认方法不需要去执行SQL,直接执行原来的方法即可if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);}//判断是否是默认方法(Java 8 接口中新增了默认方法)if (this.isDefaultMethod(method)) {return this.invokeDefaultMethod(proxy, method, args);}} catch (Throwable var5) {throw ExceptionUtil.unwrapThrowable(var5);}//获取方法缓存,这是为了提升效率,MyBatis一旦启动,方法对应的SQL也就确定了MapperMethod mapperMethod = this.cachedMapperMethod(method);//mapperMethod的execute是真正执行SQL的方法return mapperMethod.execute(this.sqlSession, args);}------MapperMethod 对于不同类型的SQL执行不同的方法public Object execute(SqlSession sqlSession, Object[] args) {Object result;Object param;switch(this.command.getType()) {case INSERT://将方法的参数转化成SQL的参数param = this.method.convertArgsToSqlCommandParam(args);//调用sqlSession的方法来执行SQLresult = this.rowCountResult(sqlSession.insert(this.command.getName(), param));break;case UPDATE:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.update(this.command.getName(), param));break;case DELETE:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));break;case SELECT:if (this.method.returnsVoid() && this.method.hasResultHandler()) {this.executeWithResultHandler(sqlSession, args);result = null;} else if (this.method.returnsMany()) {result = this.executeForMany(sqlSession, args);} else if (this.method.returnsMap()) {result = this.executeForMap(sqlSession, args);} else if (this.method.returnsCursor()) {result = this.executeForCursor(sqlSession, args);} else {param = this.method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(this.command.getName(), param);if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + this.command.getName());}if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");} else {return result;}}

以select举例来说,之后都会执行到DefaultSqlSession的selectList方法

 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {List var5;try {//先通过对应的Statement ID 即command name从configuration中获得MappedStatement//MappedStatement包含SQL上所有的参数id,resultMap,parameterMap等MappedStatement ms = this.configuration.getMappedStatement(statement);//使用sqlSession的executor执行query方法var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception var9) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);} finally {ErrorContext.instance().reset();}return var5;
}

执行query之后会先调用BaseExecutor的query()方法,最后会执行具体的执行器的doQuery方法(模板模式)

创建StatementHandler

 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;List var9;try {Configuration configuration = ms.getConfiguration();//先创建对应的StatementHandlerStatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);//通过handler创建对应的Statementstmt = this.prepareStatement(handler, ms.getStatementLog());var9 = handler.query(stmt, resultHandler);} finally {this.closeStatement(stmt);}return var9;}----------RoutingStatementHandler
在创建StatementHandler的时候会根据 MappedStatement 里面的 statementType 决定
StatementHandler的类型默认是PREPARED,包含ParameterHandler和ResultHandler,这里是在BaseStatementHandler(StatementHandler实现类的抽象父类)初始化的时候创建的public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {switch(ms.getStatementType()) {case STATEMENT:this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case PREPARED:this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case CALLABLE:this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;default:throw new ExecutorException("Unknown statement type: " + ms.getStatementType());}}

通过StatementHandler创建Statement

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Connection connection = this.getConnection(statementLog);Statement stmt = handler.prepare(connection, this.transaction.getTimeout());//对statement进行预编译,处理参数handler.parameterize(stmt);return stmt;}public void parameterize(Statement statement) throws SQLException {this.parameterHandler.setParameters((PreparedStatement)statement);}

最后一步:执行SQL

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement)statement;ps.execute();return this.resultSetHandler.handleResultSets(ps);}

画了下时序图,这里的SQL执行画的是update:

MyBatis(二)MyBatis基本流程源码分析相关推荐

  1. 二次开发:flowable审批流程实践与创建流程源码分析

    二次开发:flowable审批流程实践与创建流程源码分析 上一篇已经描述了基于开源项目https://doc.iocoder.cn/的flowable的快速开发,创建了一个租户,创建了用户和相应的岗位 ...

  2. 【源码分析】storm拓扑运行全流程源码分析

    [源码分析]storm拓扑运行全流程源码分析 @(STORM)[storm] 源码分析storm拓扑运行全流程源码分析 一拓扑提交流程 一stormpy 1storm jar 2def jar 3ex ...

  3. YOLOv3反向传播原理 之 全流程源码分析

    YOLOv3反向传播原理 之 全流程源码分析 1.YOLOv3网络训练中反向传播主体流程 1.1 初始化 1.2 batch内梯度累加 1.3 network和layer 中的关键变量 2.YOLO层 ...

  4. SpringBoot2 | SpringBoot启动流程源码分析(一)

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 SpringBoot2 | SpringBoot启动流程源码分析(一) 置顶 张书康 201 ...

  5. OkHttp原理流程源码分析

    OkHttp已经是非常流行的android客户端的网络请求框架,我其实在项目中使用也已经好几年了,之前一直把重心放在如何快速的搞定业务上.迭代的效率上,这一点来讲,对于一个公司优秀员工是没有毛病的.但 ...

  6. Activity启动流程源码分析-浅析生命周期函数

    源码分析 接着上一篇 Activity启动流程源码分析-setContentView源码阅读 的讲解,本节介绍一下Activity的生命周期函数何时被调用 要看Activity的生命周期函数何时被调用 ...

  7. nimble源码学习——广播流程源码分析1

    广播流程源码分析1 在controller层有多种状态:广播.空闲.连接等等,这次分析的是广播这个状态或者叫 做角色.在前面controller层循环的分析中,可以明确controller层也有eve ...

  8. Activity启动流程源码分析(基于Android N)

    Activity启动流程源码分析 一个Activity启动分为两种启动方式,一种是从Launcher界面上的图标点击启动,另一种是从一个Activity中设置按钮点击启动另外一个Activity.这里 ...

  9. springboot MVC视图解析流程源码分析

    Servlet的基础知识 为什么要先了解Servlet的知识呢,因为后面你会看到我们所熟悉的SpringMVC其实也是一个Servlet,只是它封装了很多的东西并和Spring进行了整合,后面我们进行 ...

最新文章

  1. react 之 setState
  2. ORA-00913错误:PL/SQL: ORA-00913: too many values
  3. Fedora 安装WIN字体(如 宋体)
  4. 【Android 应用开发】分析各种Android设备屏幕分辨率与适配 - 使用大量真实安卓设备采集真实数据统计
  5. mysql修改表引擎Engine
  6. micropython随笔-hello,world
  7. VTK:PolyData之SurfacePointPlacer
  8. Stanford CS230深度学习(五)CNN和ResNet
  9. Mike Stout关于BOSS战的分享
  10. 犯罪分子社工GoDaddy 员工,获得密币相关网站域名的控制权
  11. 使用原生XMLHttpRequest对象演示ajax(Ajax)功能
  12. Why not inherit from ListT?
  13. Himall商城枚举帮助类EnumHelper(2)
  14. 二本b类大学计算机专业,位于广东的二本B类大学有哪些?
  15. Codewar刷题总结
  16. 观察者模式实际应用场景「扩展点实战系列」- 第439篇
  17. 企业级大数据平台智能运维好帮手——星环科技多模数据平台监控软件Aquila Insight
  18. 物联网与无线传感器网络期末考试复习资料(教材--刘伟荣,何云--电子工业出版社)
  19. 【Java】继承、多态、接口
  20. 实现对 2:3 或者3:2的图片进行1:1裁剪

热门文章

  1. Solr-4.10.2安装
  2. delete 会不会锁表_MySQL的insert into select 引发锁表
  3. 计算机领域中,增量是什么意思?
  4. 使用SpringMVC 的MultipartFile文件上传时参数获取的一个坑
  5. zabbix启动无效,无法监听10051
  6. 深入浅出LVS:企业集群平台负载均衡的三种模式和算法实现
  7. 【网络知识点】防火墙主备冗余技术
  8. nginx 漏洞(适用于0.1.0-0.8.14)补丁
  9. 牛客 - 王国(虚树+树的直径)
  10. 中石油训练赛 - Swapity Swap(矩阵快速幂)