MyBatis源码学习三之查询主逻辑

news/2025/1/11 14:31:31/

MyBatis源码学习三之查询主逻辑

继续上一章节。

MyBatis的一个主要流程图。从图中可以看出,核心的东西主要集中在3个Handler中。分别是入参处理,执行sql语句处理,以及返回结果处理。

在这里插入图片描述

一 实例

Test

public static void main(String[] args) {InputStream inputStream = null;try {inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession session = factory.openSession();UserMapper mapper = session.getMapper(UserMapper.class);User user = mapper.getUserById("20");System.err.println(user.getId() + " : " + user.getUserName());} catch (IOException e) {throw new RuntimeException(e);}
}

UserMapper

public interface UserMapper {@Select("select name from user where id=#{id}")User getUserById(@Param String id);
}

上一节,我们看到MapperProxy.invokeMapperMethod.execute那我们就接着进入这里来看。

MapperMethod.execute

这里的command.getType是select。而且是一个单结果集的返回,就会进入到case select中最后一个else中。

public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName()+ "' attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");}return result;
}

二 参数处理

MethodSignature.convertArgsToSqlCommandParam

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);if (resolvedReturnType instanceof Class<?>) {this.returnType = (Class<?>) resolvedReturnType;} else if (resolvedReturnType instanceof ParameterizedType) {this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();} else {this.returnType = method.getReturnType();}this.returnsVoid = void.class.equals(this.returnType);this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();this.returnsCursor = Cursor.class.equals(this.returnType);this.returnsOptional = Optional.class.equals(this.returnType);this.mapKey = getMapKey(method);this.returnsMap = this.mapKey != null;this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);this.paramNameResolver = new ParamNameResolver(configuration, method);
}public Object convertArgsToSqlCommandParam(Object[] args) {return paramNameResolver.getNamedParams(args);
}

ParamNameResolver.getNamedParams

public Object getNamedParams(Object[] args) {final int paramCount = names.size();if (args == null || paramCount == 0) {return null;}// 没有@Param注解,并且参数只有一个,符合这个条件if (!hasParamAnnotation && paramCount == 1) {// 把实际传入的value值赋值给参数Object value = args[names.firstKey()];// 进行了类型包装,只有当类型是Collection和Array才生效。return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : null);} else {final Map<String, Object> param = new ParamMap<>();int i = 0;for (Map.Entry<Integer, String> entry : names.entrySet()) {param.put(entry.getValue(), args[entry.getKey()]);// add generic param names (param1, param2, ...)final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);// ensure not to overwrite parameter named with @Paramif (!names.containsValue(genericParamName)) {param.put(genericParamName, args[entry.getKey()]);}i++;}return param;}
}
public static Object wrapToMapIfCollection(Object object, String actualParamName) {if (object instanceof Collection) {ParamMap<Object> map = new ParamMap<>();map.put("collection", object);if (object instanceof List) {map.put("list", object);}Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));return map;}if (object != null && object.getClass().isArray()) {ParamMap<Object> map = new ParamMap<>();map.put("array", object);Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));return map;}return object;
}

这里完了后,再次回到MapperMethod.execute中,继续往下走到sqlSession.selectOne

DefaultSqlSession.selectOne

多次重载后,进入到private的selectList方法中。从configuration中拿取了 MapperStatement,然后调用executor.query

而这个executor有2个实现类,分别是BaseExecutorCacheExecutor。而CacheExecutor是做二级缓存的,我们不开启二级缓存,那么就进入到BaseExecutor中。

@Override
public <T> T selectOne(String statement, Object parameter) {// Popular vote was to return null on 0 results and throw exception on too many.List<T> list = this.selectList(statement, parameter);if (list.size() == 1) {return list.get(0);}if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {try {MappedStatement ms = configuration.getMappedStatement(statement);dirty |= ms.isDirtySelect();return executor.query(ms, wrapCollection(parameter), rowBounds, handler);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}

BaseExecutor.query

获取到原生的sql语句以及参数的映射,然后再创建一个用作缓存的key值,这个值的创建考虑的因素比较多。这里就不介绍了。

然后再继续调用query方法。

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)throws SQLException {// 获取到原生的sql语句以及参数的映射BoundSql boundSql = ms.getBoundSql(parameter);// 创建一个缓存的keyCacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {if (closed) {throw new ExecutorException("Executor was closed.");}CacheKey cacheKey = new CacheKey();cacheKey.update(ms.getId());cacheKey.update(rowBounds.getOffset());cacheKey.update(rowBounds.getLimit());cacheKey.update(boundSql.getSql());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();// mimic DefaultParameterHandler logicfor (ParameterMapping parameterMapping : parameterMappings) {if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}cacheKey.update(value);}}if (configuration.getEnvironment() != null) {// issue #176cacheKey.update(configuration.getEnvironment().getId());}return cacheKey;
}@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {queryStack++;// localCache中没有存放任何东西,所以这里的list也是null,进入查询数据库的语句中list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;
}private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;
}
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler, BoundSql boundSql) throws SQLException;

再进入到doQuery方法。而这个doQuery是一个抽象的方法。那么只能看看实现类了。

在这里插入图片描述

而在初始化的时候,我们没有指定Executor的类型,那么这个类型就是默认的SimpleExecutor

SimpleExecutor.doQuery

现获取到configuration对象,再去创建StatementHandler对象。

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}
}

Configuration.newStatementHandler

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// 实例化statementHandlerStatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,rowBounds, resultHandler, boundSql);// 用插件进行装饰return (StatementHandler) interceptorChain.pluginAll(statementHandler);
}

RoutingStatementHandler

ms的statementType默认是PREPARED

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler, BoundSql boundSql) {switch (ms.getStatementType()) {case STATEMENT:delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case PREPARED:delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case CALLABLE:delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;default:throw new ExecutorException("Unknown statement type: " + ms.getStatementType());}}

PreparedStatementHandler

public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter,RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}

BaseStatementHandler

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {this.configuration = mappedStatement.getConfiguration();this.executor = executor;this.mappedStatement = mappedStatement;this.rowBounds = rowBounds;this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();this.objectFactory = configuration.getObjectFactory();if (boundSql == null) { // issue #435, get the key before calculating the statementgenerateKeys(parameterObject);boundSql = mappedStatement.getBoundSql(parameterObject);}this.boundSql = boundSql;// 实例化另外2个插件修饰的对象this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler,resultHandler, boundSql);
}

Configuration

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,parameterObject, boundSql);return (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
}public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds,ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler,resultHandler, boundSql, rowBounds);return (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
}

这里初始化就完成了,开始调用真正的query。

PreparedStatementHandler.query

调用jdbc中ps的execute方法。resultSetHandler刚才已经实例过,直接调用

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

DefaultResultHandler.handleResultSets

public List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());final List<Object> multipleResults = new ArrayList<>();int resultSetCount = 0;// 获取ResutlSet的包装类ResultSetWrapper rsw = getFirstResultSet(stmt);List<ResultMap> resultMaps = mappedStatement.getResultMaps();int resultMapCount = resultMaps.size();validateResultMapsCount(rsw, resultMapCount);while (rsw != null && resultMapCount > resultSetCount) {ResultMap resultMap = resultMaps.get(resultSetCount);// 处理结果handleResultSet(rsw, resultMap, multipleResults, null);rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}String[] resultSets = mappedStatement.getResultSets();if (resultSets != null) {while (rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);if (parentMapping != null) {String nestedResultMapId = parentMapping.getNestedResultMapId();ResultMap resultMap = configuration.getResultMap(nestedResultMapId);handleResultSet(rsw, resultMap, null, parentMapping);}rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}}return collapseSingleResultList(multipleResults);
}private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,ResultMapping parentMapping) throws SQLException {try {// 这里是处理多返回结果的if (parentMapping != null) {handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);} else if (resultHandler == null) {// resultHandler还未实例化,进入到这里DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);multipleResults.add(defaultResultHandler.getResultList());} else {handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);}} finally {// issue #228 (close resultsets)closeResultSet(rsw.getResultSet());}
}
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {if (resultMap.hasNestedResultMaps()) {ensureNoRowBounds();checkResultHandler();handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);} else {// 单返回结果处理handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {DefaultResultContext<Object> resultContext = new DefaultResultContext<>();ResultSet resultSet = rsw.getResultSet();// 跳过逻辑分页skipRows(resultSet, rowBounds);while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);//  对对象进行赋值Object rowValue = getRowValue(rsw, discriminatedResultMap, null);storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}
}private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {rs.absolute(rowBounds.getOffset());}} else {for (int i = 0; i < rowBounds.getOffset(); i++) {if (!rs.next()) {break;}}}
}private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {final ResultLoaderMap lazyLoader = new ResultLoaderMap();// 实例化返回的对象,也就是User类 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final MetaObject metaObject = configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;// 需要自动映射对象,也就是id对应字段User中的idif (shouldApplyAutomaticMappings(resultMap, false)) {foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}return rowValue;
}// 实例化返回的对象,也就是User类,这里一定用的是反射
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,String columnPrefix) throws SQLException {this.useConstructorMappings = false; // reset previous mapping resultfinal List<Class<?>> constructorArgTypes = new ArrayList<>();final List<Object> constructorArgs = new ArrayList<>();Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();for (ResultMapping propertyMapping : propertyMappings) {// issue gcode #109 && issue #149if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,objectFactory, constructorArgTypes, constructorArgs);break;}}}this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping resultreturn resultObject;
}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,List<Object> constructorArgs, String columnPrefix) throws SQLException {final Class<?> resultType = resultMap.getType();// 反射类final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();if (hasTypeHandlerForResultObject(rsw, resultType)) {return createPrimitiveResultObject(rsw, resultMap, columnPrefix);}if (!constructorMappings.isEmpty()) {return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,columnPrefix);} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {// 最终使用objectFactory.create了User对象出来了 return objectFactory.create(resultType);} else if (shouldApplyAutomaticMappings(resultMap, false)) {return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,constructorArgs);}throw new ExecutorException("Do not know how to create an instance of " + resultType);
}private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,String columnPrefix) throws SQLException {List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);boolean foundValues = false;if (!autoMapping.isEmpty()) {for (UnMappedColumnAutoMapping mapping : autoMapping) {final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);if (value != null) {foundValues = true;}if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {// gcode issue #377, call setter on nulls (value is not 'found')// 通过反射一步步的把数据set进去metaObject.setValue(mapping.property, value);}}}return foundValues;
}

到这里再把结果封装一下,然后再返回给List对象,就完成了。


http://www.ppmy.cn/news/83669.html

相关文章

想变身“科技型”企业?掌汇云数字化服务平台为工业升级加分

40万亿元&#xff01;占GDP比重达到33.2%&#xff0c;这就是国新办公布的2022年数据&#xff0c;中国工业可以说是当之无愧的支柱产业。 中国工业规模大、覆盖面广&#xff0c;企业员工众多&#xff0c;项目遍及海内外。但由于科技欠发达、信息不流通等因素&#xff0c;近些年…

【加解密】bcryptjs | CryptoJS | JSEncrypt | node-rsa 加密| 解密 | RSA | ASE | MD5

加解密 1、 bcryptjs 解密 - 只可加密&#xff0c;比对密码&#xff0c;不可解密 下载 npm i bcryptjs 作用&#xff1a;字符串加密&#xff0c;已加密的字符串不可破解&#xff0c;只可比对。优点&#xff1a;加密的字符不可解密缺点&#xff1a;已加密的字符不可解密&#…

WebSocket全双工通信SpringBoot实现

【IT老齐238】十分钟上手WebSocket全双工通信协议_哔哩哔哩_bilibili【IT老齐238】十分钟上手WebSocket全双工通信协议, 视频播放量 8348、弹幕量 23、点赞数 318、投硬币枚数 157、收藏人数 257、转发人数 30, 视频作者 IT老齐, 作者简介 老齐的个人V: itlaoqi001 ~~欢迎前来交…

鳌图 AutoCapital:进化中的新型机构长什么样?

鳌图 AutoCapital 提供了一条新的产业投资思路。 数科星球原创 作者丨苑晶 编辑丨大兔 时代塑造个人&#xff0c;时代也会迭代出现新的投资机构。第一代投资人和投资机构&#xff0c;或是学成于美国或就职于投行&#xff1b;目前人民币基金越来越多出现了产业和投资双背景的…

详细介绍一下Go1.17 特性,优缺点以及需要改进的地方

Go1.17 是 Go 语言的一个重要版本,于2021年8月16日发布。这个版本包含了一些新的特性、改进和 bug 修复,下面对其进行详细介绍。 Go 编译器和工具链的速度得到了大幅度提升。在新版本中,新增的 SSA 后端可以使 Go 编译器的编译速度提升 20% 至 30%。并且在 Go 1.17 版本中,…

Android 12.0屏蔽掉SystemUI的某些通知提示音

1.概述 在12.0的系统开发中,在系统SystemUI中会发一些通知的声音,但是同时也会在开机的时候,会有一些通知的声音,特别是不想要的一些通知的声音, 这些对于产品还是有一些影响的,所以为了产品体验,就需要屏蔽掉一些开机的通知的声音 2.屏蔽某些通知的提示音的核心代码 …

uniapp沉浸式渐变状态栏

插件地址&#xff1a;沉浸式渐变状态栏 - DCloud 插件市场 标准用法 <v-headerview actionBarColor"#fac90f" titleColor"#ffffff" pageTitle这是标题 ><template v-slot:title><!--如需自定义标题&#xff0c;用这个slot自定义内容。否则…

【计算机网络】网络命令的使用

文章目录 一、实验目的二、实验工具三、实验要求四、实验过程01 ping 命令的使用应用1&#xff1a;验证本地计算机上是否正确安装了 TCP/IP 协议应用2&#xff1a;测试某个目的主机可达性应用3&#xff1a;键入 ping&#xff0c;查看 ping 的其他参数含义 02 netstat 命令的典型…