mybatis源码学习(三):MappedStatement的解析过程

我们之前介绍过MappedStatement表示的是XML中的一个SQL。类当中的很多字段都是SQL中对应的属性。我们先来了解一下这个类的属性:

public final class MappedStatement {private String resource;private Configuration configuration;//sql的IDprivate String id;//尝试影响驱动程序每次批量返回的结果行数和这个设置值相等private Integer fetchSize;//SQL超时时间private Integer timeout;//Statement的类型,STATEMENT/PREPARE/CALLABLEprivate StatementType statementType;//结果集类型,FORWARD_ONLY/SCROLL_SENSITIVE/SCROLL_INSENSITIVE private ResultSetType resultSetType;//表示解析出来的SQLprivate SqlSource sqlSource;//缓存private Cache cache;//已废弃private ParameterMap parameterMap;//对应的ResultMapprivate List<ResultMap> resultMaps;private boolean flushCacheRequired;private boolean useCache;private boolean resultOrdered;//SQL类型,INSERT/SELECT/DELETEprivate SqlCommandType sqlCommandType;//和SELECTKEY标签有关private KeyGenerator keyGenerator;private String[] keyProperties;private String[] keyColumns;private boolean hasNestedResultMaps;//数据库ID,用来区分不同环境private String databaseId;private Log statementLog;private LanguageDriver lang;//多结果集时private String[] resultSets;MappedStatement() {// constructor disabled
  }...}

对一些重要的字段我都增加了备注,方便理解。其中真正表示SQL的字段是SqlSource这个对象。

SqlSource接口很简单,只有一个getBound方法:

public interface SqlSource {BoundSql getBoundSql(Object parameterObject);}

它有很多实现,需要我们重点关注的是StaticSqlSource,RawSqlSource和DynamicSqlSource。在正式学习他们前,我们先了解一下Mybatis动态SQL和静态SQL的区别。

动态SQL表示这个SQL节点中含有${}或是其他动态的标签(比如,if,trim,foreach,choose,bind节点等),需要在运行时根据传入的条件才能确定SQL,因此对于动态SQL的MappedStatement的解析过程应该是在运行时。

而静态SQL是不含以上这个节点的SQL,能直接解析得到含有占位符形式的SQL语句,而不需要根据传入的条件确定SQL,因此可以在加载时就完成解析。所在在执行效率上要高于动态SQL。

而DynamicSqlSource和RawSqlSource就分别对应了动态SQL和静态SQL,它们都封装了StaticSqlSource。

我们先从简单的入手,了解静态SQL的解析过程。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="org.apache.ibatis.domain.blog.mappers.AuthorMapper"><select id="selectAllAuthors" resultType="org.apache.ibatis.domain.blog.Author">select * from author</select></mapper>

这是我们要解析的XML文件,mapper节点下只有一个select节点。

public class XmlMapperBuilderTest {@Testpublic void shouldSuccessfullyLoadXMLMapperFile() throws Exception {Configuration configuration = new Configuration();String resource = "org/apache/ibatis/builder/AuthorMapper.xml";InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder builder = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());builder.parse();inputStream.close();}
}

这是我们测试解析过程的代码。我们可以看到解析是由XMLMapperBuilder开始的。我们先了解一下它的字段:

public class XMLMapperBuilder extends BaseBuilder {//用来解析XMLprivate final XPathParser parser;//再解析完成后,用解析所得的属性来帮助创建各个对象private final MapperBuilderAssistant builderAssistant;//保存SQL节点private final Map<String, XNode> sqlFragments;//...  
}

它还从父类中继承了configuration(配置对象),typeAliasRegistry(类型别名注册器)和typeHandlerRegistry(类型处理器注册器)。

接下来看一下它的parse方法:

public void parse() {//判断是否已经加载过资源    if (!configuration.isResourceLoaded(resource)) {//从mapper根节点开始解析configurationElement(parser.evalNode("/mapper"));//将该资源添加到为已经加载过的缓存中
      configuration.addLoadedResource(resource);//将解析的SQL和接口中的方法绑定
      bindMapperForNamespace();}//对一些未完成解析的节点再解析
    parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}

主要的解析过程在configurationElement中:

private void configurationElement(XNode context) {try {//解析mapper的namespace属性,并设置String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);//解析<cache-ref>节点,它有一个namespace属性,表示引用该命名空间下的缓存cacheRefElement(context.evalNode("cache-ref"));//解析<cache>节点,可以设置缓存类型和属性,或是指定自定义的缓存cacheElement(context.evalNode("cache"));//已废弃,不再使用    parameterMapElement(context.evalNodes("/mapper/parameterMap"));//解析resultMap节点resultMapElements(context.evalNodes("/mapper/resultMap"));//解析<SQL>节点,SQL节点可以使一些SQL片段被复用     sqlElement(context.evalNodes("/mapper/sql"));//解析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);}} 

我们关注SQL语句的解析过程,上述buildStatementFromContext(List<XNode>)方法会增加dateBaseId的参数,然后调用另一个重载方法:

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {//遍历XNode节点for (XNode context : list) {//为每个节点创建XMLStatementBuilder对象,final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {//解析Node
        statementParser.parseStatementNode();} catch (IncompleteElementException e) {//对不能完全解析的节点添加到incompleteStatement,在parsePendingStatements方法中再解析
        configuration.addIncompleteStatement(statementParser);}}}

先看看XMLStatementBuilder对象:

public class XMLStatementBuilder extends BaseBuilder {private final MapperBuilderAssistant builderAssistant;private final XNode context;private final String requiredDatabaseId;// ...
}
View Code

含有的字段相对简单,不再具体解释。直接看parseStatementNode方法:

 

public void parseStatementNode() {//获取idString id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");//验证databaseId是否匹配if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");//已废弃String parameterMap = context.getStringAttribute("parameterMap");//参数类型;将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);//结果类型;外部 resultMap 的命名引用。String resultMap = context.getStringAttribute("resultMap");//结果类型;表示从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。不能和resultMap同时使用。String resultType = context.getStringAttribute("resultType");String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);Class<?> resultTypeClass = resolveClass(resultType);//结果集类型;FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)。String resultSetType = context.getStringAttribute("resultSetType");//STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);String nodeName = context.getNode().getNodeName();//SQLCommand类型SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;//flushCache;在执行语句时表示是否刷新缓存boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);//是否对该语句进行二级缓存;默认值:对 select 元素为 true。boolean useCache = context.getBooleanAttribute("useCache", isSelect);//根嵌套结果相关boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);//引入SQL片段// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them.//处理selectKey
    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;}//通过buildAssistant将解析得到的参数设置构造成MappedStatement对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}

将解析得到参数通过BuilderAssistant.addMappedStatement方法,解析得到MappedStatement对象。

 

上面已经说过sqlsource表示的一个SQL语句,因此我们关注langDriver.createSqlSource这个方法。看XMLLanguageDriver这个实现。

 @Overridepublic SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode();}

可以看到他将创建sqlsource的工作交给了XMLScrpitBuilder(又一个创建者模式的应用)。来看parseScriptNode方法:

public SqlSource parseScriptNode() {//解析SQL语句节点,创建MixedSqlNode对象MixedSqlNode rootSqlNode = parseDynamicTags(context);SqlSource sqlSource = null;//根据是否是动态的语句,创建DynamicSqlSource或是RawSqlSource对象,并返回if (isDynamic) {sqlSource = new DynamicSqlSource(configuration, rootSqlNode);} else {sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);}return sqlSource;}

MixedSqlNode是SqlNode的一个实现,包含了各个子节点,用来遍历输出子节点。SqlNode还有很多不同的实现,分别对应不同的节点类型。对应关系如下:

SqlNode实现对应SQL语句中的类型
TextSqlNode${}
IfSqlNodeIf节点
TrimSqlNode/WhereSqlNode/SetSqlNodeTrim/Where/Set节点
Foreach节点foreach标签
ChooseSqlNode节点choose/when/otherwhise节点
ValDeclSqlNode节点bind节点
StaticTextSqlNode不含上述节点

 

 

 

 

 

 

除了StaticTextSqlNode节点外,其余对应的都是动态语句。

因此我们本文的关注点在StaticTextSqlNode。

让我们对应文初sql语句的解析来看一下parseDynamicTags方法,为了便于理解,我将在右边注释出每一步的结果

protected MixedSqlNode parseDynamicTags(XNode node) {// node是我们要解析的SQL语句: <select resultType="org.apache.ibatis.domain.blog.Author" id="selectAllAuthors">select * from author</select>List<SqlNode> contents = new ArrayList<SqlNode>();//获取SQL下面的子节点NodeList children = node.getNode().getChildNodes();//这里的children只有一个节点;//遍历子节点,解析成对应的sqlNode类型,并添加到contents中for (int i = 0; i < children.getLength(); i++) {XNode child = node.newXNode(children.item(i));//第一个child节点就是SQL中的文本数据:select * from author//如果是文本节点,则先解析成TextSqlNode对象if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {//获取文本信息String data = child.getStringBody("");//data:select * from author//创建TextSqlNode对象TextSqlNode textSqlNode = new TextSqlNode(data);//判断是否是动态Sql,其过程会调用GenericTokenParser判断文本中是否含有"${"字符if (textSqlNode.isDynamic()) {//如果是动态SQL,则直接使用TextSqlNode类型,并将isDynamic标识置为true
          contents.add(textSqlNode);isDynamic = true;} else {//不是动态sql,则创建StaticTextSqlNode对象,表示静态SQLcontents.add(new StaticTextSqlNode(data));}} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { //其他类型的节点,由不同的节点处理器来对应处理成本成不同的SqlNode类型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;}}//用contents构建MixedSqlNode对象return new MixedSqlNode(contents);}

上述过程中,我们主要关注静态SQL的解析过程,对于动态SQL的解析将在之后介绍。

得到MixedSqlNode后,静态的SQL会创建出RawSqlSource对象。

看一下RawSqlSource:

  public class RawSqlSource implements SqlSource {//内部封装的sqlSource对象,getBoundSql方法会委托给这个对象private final SqlSource sqlSource;public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {this(configuration, getSql(configuration, rootSqlNode), parameterType);}public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {//创建sqlSourceBuilderSqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);Class<?> clazz = parameterType == null ? Object.class : parameterType;//解析sql,创建StaticSqlSource对象sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());}//获取sql语句private static String getSql(Configuration configuration, SqlNode rootSqlNode) {DynamicContext context = new DynamicContext(configuration, null);//这里的rootSqlNode就是之前得到的MixedSqlNode,它会遍历内部的SqlNode,逐个调用sqlNode的apply方法。StaticTextSqlNode会直接context.appendSql方法
    rootSqlNode.apply(context);return context.getSql();}@Overridepublic BoundSql getBoundSql(Object parameterObject) {return sqlSource.getBoundSql(parameterObject);}}

代码相对简单,主要的步骤就是(1)通过SqlNode获得原始SQL语句;(2)创建SqlSourceBuilder对象,解析SQL语句,并创建StaticSqlSource对象;(3)将getBoundSql方法委托给内部的staticSqlSource对象。

其中比较关键的一步是解析原始SQL语句,并创建StaticSqlSource对象。因此我们继续看SqlSourceBuilder对象。

public class SqlSourceBuilder extends BaseBuilder {private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";public SqlSourceBuilder(Configuration configuration) {super(configuration);}public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {//创建TokenHandler,用来将原始Sql中的'#{}' 解析成'?'ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);//解析原始sqlString sql = parser.parse(originalSql);//创建出StaticSqlSource对象return new StaticSqlSource(configuration, sql, handler.getParameterMappings());}private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();private Class<?> parameterType;private MetaObject metaParameters;public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {super(configuration);this.parameterType = parameterType;this.metaParameters = configuration.newMetaObject(additionalParameters);}public List<ParameterMapping> getParameterMappings() {return parameterMappings;}@Overridepublic String handleToken(String content) {//解析'#{}'中的参数,创建ParameterMapping对象
      parameterMappings.add(buildParameterMapping(content));//将'#{}'替换成'?'return "?";}private ParameterMapping buildParameterMapping(String content) {Map<String, String> propertiesMap = parseParameterMapping(content);String property = propertiesMap.get("property");Class<?> propertyType;if (metaParameters.hasGetter(property)) { // issue #448 get type from additional paramspropertyType = metaParameters.getGetterType(property);} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {propertyType = parameterType;} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {propertyType = java.sql.ResultSet.class;} else if (property == null || Map.class.isAssignableFrom(parameterType)) {propertyType = Object.class;} else {MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());if (metaClass.hasGetter(property)) {propertyType = metaClass.getGetterType(property);} else {propertyType = Object.class;}}ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);Class<?> javaType = propertyType;String typeHandlerAlias = null;for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {String name = entry.getKey();String value = entry.getValue();if ("javaType".equals(name)) {javaType = resolveClass(value);builder.javaType(javaType);} else if ("jdbcType".equals(name)) {builder.jdbcType(resolveJdbcType(value));} else if ("mode".equals(name)) {builder.mode(resolveParameterMode(value));} else if ("numericScale".equals(name)) {builder.numericScale(Integer.valueOf(value));} else if ("resultMap".equals(name)) {builder.resultMapId(value);} else if ("typeHandler".equals(name)) {typeHandlerAlias = value;} else if ("jdbcTypeName".equals(name)) {builder.jdbcTypeName(value);} else if ("property".equals(name)) {// Do Nothing} else if ("expression".equals(name)) {throw new BuilderException("Expression based parameters are not supported yet");} else {throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + parameterProperties);}}if (typeHandlerAlias != null) {builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));}return builder.build();}private Map<String, String> parseParameterMapping(String content) {try {return new ParameterExpression(content);} catch (BuilderException ex) {throw ex;} catch (Exception ex) {throw new BuilderException("Parsing error was found in mapping #{" + content + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);}}}}

parse方法主要分为以下几步:

(1)创建了ParameterMappingTokenHandler对象

(2)将ParameterMappingTokenHandler对象传入GenericTokenParser的构造函数中,创建GenericTokenParser对象

(3)通过GenericTokenParser对象解析原始SQL,这个过程中会将#{}替换成?,并将#{}中的参数,解析形成ParamterMapping对象

(4)用得到的SQL和ParamterMapping对象创建StaticSqlSource对象。

 

解析完成后回到一开始的XMLMapperBuilder,它会在资源添加到已加载的列表中,并bindMapperForNamespace方法中为创建的MappedStatement添加命名空间。

转载于:https://www.cnblogs.com/insaneXs/p/9083003.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283028.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# 二十年语法变迁之 C# 8参考

C# 二十年语法变迁之 C# 8参考自从 C# 于 2000 年推出以来&#xff0c;该语言的规模已经大大增加&#xff0c;我不确定任何人是否有可能在任何时候都对每一种语言特性都有深入的了解。因此&#xff0c;我想写一系列快速参考文章&#xff0c;总结自 C# 2.0 以来所有主要的新语言…

windows 提权 cve-2018-8897

windows 提权 cve-2018-8897影响范围&#xff1a;基本上是全版本具体影响范围看详情&#xff1a;https://portal.msrc.microsoft.co … isory/CVE-2018-8897http://www.o2oxy.cn/wp-content/uploads/2018/06/cve-2018-8897.rar转载于:https://blog.51cto.com/9861015/2126608

java servlet练习测试

步骤&#xff1a; 0、首先创建web project&#xff0c;工程名&#xff1a;test_servlet 1、编写Servlet&#xff0c;TestServlet.java文件内容&#xff1a; package com.ouyang.servlet;import java.io.IOException; import java.sql.Connection; import java.sql.DriverManage…

《ASP.NET Core 6框架揭秘》实例演示[19]:数据加解密与哈希

数据保护&#xff08;Data Protection&#xff09;框架旨在解决数据在传输与持久化存储过程中的一致性&#xff08;Integrity&#xff09;和机密性&#xff08;confidentiality&#xff09;问题&#xff0c;前者用于检验接收到的数据是否经过篡改&#xff0c;后者通过对原始的数…

如何在ABAP Netweaver和CloudFoundry里记录并查看日志

Netweaver 要记录日志需要有一个checkpoint group&#xff0c;可以自行创建也可以使用标准的。这里我重用标准的group&#xff1a;DEMO_CHECKPOINT_GROUP。 tcode SAAB&#xff0c;点Display <->Activate进入编辑模式&#xff0c;将Logpoints设置为"Log"&#…

如何成为有效学习的高手(许岑)——思维导图

总结自许岑精品课《如何成为有效学习的高手》&#xff0c;图片看不清的可以看下面。 最后有彩蛋&#xff01;最后有彩蛋&#xff01;最后有彩蛋&#xff01; 定义 高效学习的定义&#xff1a;找到最适合自己的学习手法&#xff0c;在相对短的时间内集中注意力&#xff0c;以解决…

WPF Canvas 平滑笔迹

WPF Canvas 平滑笔迹控件名&#xff1a;CanvasHandWriting作者&#xff1a;小封&#xff08;邝攀升&#xff09;原文链接&#xff1a; https://github.com/WPFDevelopersOrg/WPFDevelopers编辑&#xff1a;驚鏵完整的思路如下收集路径点集。平均采样路径点集。将路径点集转为…

NetSpeed

NetSpeed公司提供的NOC包括三部分&#xff0c;可以通过NocStudio进行配置生成。 1)NetSpeed Orion&#xff0c;面向快速SoC design的可综合平台。 2)Linley NetSpeed NoC面向复杂的interconnect实现&#xff0c;同时优化内部physical implementation和timing closure. NoC是基于…

js ajax java传参_ajax参数传递与后台接收

ajax参数传递与后台接收Servlet中读取http参数的方法Enumeration getParameterNames() 返回一个 String 对象的枚举&#xff0c;包含在该请求中包含的参数的名称String getParameter(String name) 以字符串形式返回请求参数的值&#xff0c;或者如果参数不存在则返回 null。Str…

init 访问器只能初始化时赋值,是真的吗?

前言C# 提供的 init 关键字用于在属性中定义访问器方法&#xff0c;可以让属性仅能在对象初始化的时候被赋值&#xff0c;其他时候只能为只读属性的形式。例如下面代码可以正常执行&#xff1a;public class Demo {public string Name { get; init; } }var demo new Demo { Na…

eclipse实现代码块折叠-类似于VS中的#region……#endregion

背 景 刚才在写代码的时候&#xff0c;写了十几行可以说是重复的代码&#xff1a; 如果整个方法或类中代码多了&#xff0c;感觉它们太TM占地方了&#xff0c;给读者在阅读代码上造成很大的困难&#xff0c;于是想到能不能把他们“浓缩”成一行&#xff0c;脑子里第一个闪现出的…

java定义基础变量语句_java语言基础-变量

一丶变量的基本概念1.什么是变量(1).内存中的一个存储区域(2).该区域有自己的名称(变量名),和类型(数据类型)(3.)该区域的数据可以在同一类型范围内不断变化(定义变量的主要目的是因为数据的不确定性)2.为什么要定义变量用来不断存放同一类型的常量&#xff0c;并可以重复使用3…

C# WPF MVVM模式[经典]案例

01—前言Caliburn.Micro(简称CM)一经推出便备受推崇&#xff0c;作为一款MVVM开发模式的经典框架&#xff0c;越来越多的受到wpf开发者的青睐.我们看一下官方的描述&#xff1a;Caliburn是一个为Xaml平台设计的小型但功能强大的框架。Micro实现了各种UI模式&#xff0c;用于解决…

shell数组

定义数组[rootwy shell]# a(1 2 3 4)显示数组[rootwy shell]# echo ${a[]}1 2 3 4[rootwy shell]# echo ${a[*]}1 2 3 4显示数组中的某个元素[rootwy shell]# echo ${a[0]}1增加元素[rootwy shell]# a[4]9[rootwy shell]# echo ${a[*]}1 2 3 4 9修改元素值 [rootwy shell]# a[2…

LINUX中常用操作命令

LINUX中常用操作命令 引用&#xff1a;http://www.daniubiji.cn/archives/25 Linux简介及Ubuntu安装 常见指令系统管理命令打包压缩相关命令关机/重启机器Linux管道Linux软件包管理vim使用用户及用户组管理文件权限管理Linux简介及Ubuntu安装 Linux&#xff0c;免费开源&#x…

Log4j编写

来自: http://www.blogjava.net/zJun/archive/2006/06/28/55511.html Log4J的配置文件(Configuration File)就是用来设置记录器的级别、存放器和布局的&#xff0c;它可接keyvalue格式的设置或xml格式的设置信息。通过配置&#xff0c;可以创建出Log4J的运行环境。1. 配置文件L…

C# 为什么高手喜欢用StartsWith而不是Substring进行字符串匹配?

字符串的截取匹配操作在开发中非常常见&#xff0c;比如下面这个示例&#xff1a;我要匹配查找出来字符串数组中以“abc”开头的字符串并打印&#xff0c;我下面分别用了两种方式实现&#xff0c;代码如下&#xff1a;using System;namespace ConsoleApp23 {class Program{stat…

Nginx 服务器开启status页面检测服务状态

原文&#xff1a;http://www.cnblogs.com/hanyifeng/p/5830013.html 一、Nginx status monitor 和apache 中服务器状态一样。输出的内容如&#xff1a; 第1列&#xff1a; 当前与http建立的连接数&#xff0c;包括等待的客户端连接&#xff1a;2第2列&#xff1a;接受的客户端连…

在OpenCloudOS 上安装.NET 6

开源操作系统社区 OpenCloudOS 由腾讯与合作伙伴共同倡议发起&#xff0c;是完全中立、全面开放、安全稳定、高性能的操作系统及生态。OpenCloudOS 沉淀了多家厂商在软件和开源生态的优势&#xff0c;继承了腾讯在操作系统和内核层面超过10年的技术积累&#xff0c;在云原生、稳…

java产生的数字发送到页面_JAVA中数字证书的维护及生成方法

Java中的keytool.exe可以用来创建数字证书&#xff0c;所有的数字证书是以一条一条(采用别名区别)的形式存入证书库的中&#xff0c;证书库中的一条证书包含该条证书的私钥&#xff0c;公钥和对应的数字证书的信息。证书库中的一条证书可以导出数字证书文件&#xff0c;数字证书…