主頁 >  其他 > 面試:你知道MyBatis執行程序之初始化是如何執行的嗎?

面試:你知道MyBatis執行程序之初始化是如何執行的嗎?

2020-09-25 00:49:46 其他

前言

在了解MyBatis架構以及核心內容分析后,我們可以研究MyBatis執行程序,包括

  • MyBatis初始化
  • SQL執行程序

而且在面試會問到一下關于MyBatis初始化的問題,比如:

  • Mybatis需要初始化哪些?
  • MyBatis初始化的程序?

MyBatis初始化

在 MyBatis 初始化程序中,會加載 mybatis-config.xml 組態檔、Mapper.xml映射組態檔以及 Mapper 介面中的注解資訊,決議后的配置資訊會形成相應的物件并保存到 Configuration 物件中,初始化程序可以分成三部分:

  • 決議mybatis-config.xml 組態檔

    • SqlSessionFactoryBuilder
    • XMLConfigBuilder
    • Configuration
  • 決議Mapper.xml映射組態檔

    • XMLMapperBuilder::parse()
    • XMLStatementBuilder::parseStatementNode()
    • XMLLanguageDriver
    • SqlSource
    • MappedStatement
  • 決議Mapper介面中的注解

    • MapperRegistry

    • MapperAnnotationBuilder::parse()

決議mybatis-config.xml 組態檔

MyBatis 的初始化流程的入口SqlSessionFactoryBuilder::build(Reader reader, String environment, Properties properties) 方法,看看具體流程圖:

 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }

首先會使用XMLConfigBuilder::parser()決議mybatis-config.xml 組態檔,

  • 先決議標簽configuration內的資料封裝成XNodeconfiguration 也是 MyBatis 中最重要的一個標簽
  • 根據XNode決議mybatis-config.xml 組態檔的各個標簽轉變為各個物件
private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

再基于Configuration使用SqlSessionFactoryBuilder::build()生成DefaultSqlSessionFactory供給后續執行使用,

決議Mapper.xml映射組態檔

首先使用XMLMapperBuilder::parse()決議Mapper.xml,看看加載流程圖來分析分析

通過XPathParser::evalNodemapper標簽中內容決議到XNode

public void parse() {
    if (!this.configuration.isResourceLoaded(this.resource)) {
        this.configurationElement(this.parser.evalNode("/mapper"));
        this.configuration.addLoadedResource(this.resource);
        this.bindMapperForNamespace();
    }

    this.parsePendingResultMaps();
    this.parsePendingCacheRefs();
    this.parsePendingStatements();
}

再由configurationElement()方法去決議XNode中的各個標簽:

  • namespace
  • parameterMap
  • resultMap
  • select|insert|update|delete
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"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      //決議MapperState
      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);
    }
  }

其中,基于XMLMapperBuilder::buildStatementFromContext(),遍歷 <select /><insert /><update /><delete /> 節點們,逐個創建XMLStatementBuilder物件,執行決議,通過XMLStatementBuilder::parseStatementNode()決議,

  • parameterType
  • resultType
  • selectKey

并會通過LanguageDriver::createSqlSource()(默認XmlLanguageDriver)決議動態sql生成SqlSource(詳細內容請看下個小節),

  • 使用GenericTokenParser::parser()負責將 SQL 陳述句中的 #{} 替換成相應的 ?占位符,并獲取該 ? 占位符對應的

而且通過MapperBuilderAssistant::addMappedStatement()生成MappedStatement

public void parseStatementNode() {
  //獲得 id 屬性,編號
  String 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");
  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultType = context.getStringAttribute("resultType");
  String lang = context.getStringAttribute("lang");
  //獲得 lang 對應的 LanguageDriver 物件
  LanguageDriver langDriver = getLanguageDriver(lang);
  //獲得 resultType 對應的類
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultSetType = context.getStringAttribute("resultSetType");
  //獲得 statementType 對應的列舉值
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  //獲得 resultSet 對應的列舉值
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

  String nodeName = context.getNode().getNodeName();
  //獲得 SQL 對應的 SqlCommandType 列舉值
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  //決議獲得各種屬性
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  //創建 XMLIncludeTransformer 物件,并替換 <include /> 標簽相關的內容
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  //決議 <selectKey /> 標簽
  processSelectKeyNodes(id, parameterTypeClass, langDriver);
  
  //創建 SqlSource生成動態sql
  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;
  }
  //創建 MappedStatement 物件
  this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, 					fetchSize, timeout, parameterMap, parameterTypeClass, 
                    resultMap, resultTypeClass, resultSetTypeEnum, flushCache, 
                    useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, 
                    keyColumn, databaseId, langDriver, resultSets);
}

決議Mapper介面中的注解

當執行完XMLMapperBuilder::configurationElement()方法后,會呼叫XMLMapperBuilder::bindMapperForNamespace()會轉換成對介面上注解進行掃描,具體通過MapperRegistry::addMapper()呼叫MapperAnnotationBuilder實作的

MapperAnnotationBuilder::parse()是注解構造器,負責決議 Mapper 介面上的注解,決議時需要注意避免和 XMLMapperBuilder::parse() 方法沖突,重復決議,最終使用parseStatement決議,那怎么操作?

public void parse() {
  String resource = type.toString();
  //判斷當前 Mapper 介面是否應加載過,
  if (!configuration.isResourceLoaded(resource)) {
    //加載對應的 XML Mapper,注意避免和 `XMLMapperBuilder::parse()` 方法沖突
    loadXmlResource();
    //標記該 Mapper 介面已經加載過
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    //決議 @CacheNamespace 注解
    parseCache();
    parseCacheRef();
     //遍歷每個方法,決議其上的注解
    Method[] methods = type.getMethods();
    for (Method method : methods) {
      try {
        if (!method.isBridge()) {
          //執行決議
          parseStatement(method);
        }
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  //決議待定的方法
  parsePendingMethods();
}

那其中最重要的parseStatement()是怎么操作?其實跟決議Mapper.xml型別主要處理流程類似:

  • 通過加載LanguageDriver,GenericTokenParser等為生成SqlSource動態sql作準備
  • 使用MapperBuilderAssistant::addMappedStatement()生成注解@mapper,@CacheNamespace等的MappedStatement資訊
void parseStatement(Method method) {
    //獲取介面引數型別	
    Class<?> parameterTypeClass = getParameterType(method);
    //加載語言處理器,默認XmlLanguageDriver
    LanguageDriver languageDriver = getLanguageDriver(method);
    //根據LanguageDriver,GenericTokenParser生成動態SQL
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      //獲取其他屬性
        Options options = method.getAnnotation(Options.class);
        final String mappedStatementId = type.getName() + "." + method.getName();
        Integer fetchSize = null;
        Integer timeout = null;
        StatementType statementType = StatementType.PREPARED;
        ResultSetType resultSetType = null;
        SqlCommandType sqlCommandType = getSqlCommandType(method);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = !isSelect;
        boolean useCache = isSelect;

        //獲得 KeyGenerator 物件
        KeyGenerator keyGenerator;
        String keyProperty = null;
        String keyColumn = null;
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { // 有
            // first check for SelectKey annotation - that overrides everything else
            //如果有 @SelectKey 注解,則進行處理
            SelectKey selectKey = method.getAnnotation(SelectKey.class);
            if (selectKey != null) {
                keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
                keyProperty = selectKey.keyProperty();
            //如果無 @Options 注解,則根據全域配置處理
            } else if (options == null) {
                keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            // 如果有 @Options 注解,則使用該注解的配置處理
            } else {
                keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
                keyProperty = options.keyProperty();
                keyColumn = options.keyColumn();
            }
        // 無
        } else {
            keyGenerator = NoKeyGenerator.INSTANCE;
        }

        //初始化各種屬性
        if (options != null) {
            if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
                flushCache = true;
            } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
                flushCache = false;
            }
            useCache = options.useCache();
            fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
            timeout = options.timeout() > -1 ? options.timeout() : null;
            statementType = options.statementType();
            resultSetType = options.resultSetType();
        }

        // 獲得 resultMapId 編號字串
        String resultMapId = null;
        //如果有 @ResultMap 注解,使用該注解為 resultMapId 屬性
        ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
        if (resultMapAnnotation != null) {
            String[] resultMaps = resultMapAnnotation.value();
            StringBuilder sb = new StringBuilder();
            for (String resultMap : resultMaps) {
                if (sb.length() > 0) {
                    sb.append(",");
                }
                sb.append(resultMap);
            }
            resultMapId = sb.toString();
        // 如果無 @ResultMap 注解,決議其它注解,作為 resultMapId 屬性
        } else if (isSelect) {
            resultMapId = parseResultMap(method);
        }
      //構建 MappedStatement 物件
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }

生成動態SqlSource

當在執行langDriver::createSqlSource(configuration, context, parameterTypeClass)中的時候, 是怎樣從 Mapper XML 或方法注解上讀取SQL內容生成動態SqlSource的呢?現在來一探究竟,

首先需要獲取langDriver實作XMLLanguageDriver/RawLanguageDriver,現在使用默認的XMLLanguageDriver::createSqlSource(configuration, context, parameterTypeClass)開啟創建,再使用XMLScriptBuilder::parseScriptNode()決議生成SqlSource

  • DynamicSqlSource: 動態的 SqlSource 實作類 , 適用于使用了 OGNL 運算式,或者使用了 ${} 運算式的 SQL
  • RawSqlSource原始SqlSource 實作類 , 適用于僅使用 #{} 運算式,或者不使用任何運算式的情況
public SqlSource parseScriptNode() {
        MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
        Object sqlSource;
        if (this.isDynamic) {
            sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
        } else {
            sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
        }

        return (SqlSource)sqlSource;
    }

那就選擇其中一種來分析一下RawSqlSource,怎么完成構造的呢?看看RawSqlSource建構式:

 public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> clazz = parameterType == null ? Object.class : parameterType;
        this.sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap());
    }

使用SqlSourceBuilder::parse()去決議SQl,里面又什么神奇的地方呢?

 public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        SqlSourceBuilder.ParameterMappingTokenHandler handler = new SqlSourceBuilder.ParameterMappingTokenHandler(this.configuration, parameterType, additionalParameters);
     	//創建基于#{}的GenericTokenParser
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        String sql = parser.parse(originalSql);
        return new StaticSqlSource(this.configuration, sql, handler.getParameterMappings());
    }

ParameterMappingTokenHandlerSqlSourceBuilder 的內部私有靜態類, ParameterMappingTokenHandler ,負責將匹配到的 #{} 對,替換成相應的 ? 占位符,并獲取該 ? 占位符對應的 org.apache.ibatis.mapping.ParameterMapping 物件,

并基于ParameterMappingTokenHandler使用GenericTokenParser::parse()將SQL中的#{}轉化占位符? 占位符后創建一個StaticSqlSource回傳,

總結

在 MyBatis 初始化程序中,會加載 mybatis-config.xml 組態檔、Mapper.xml映射組態檔以及 Mapper 介面中的注解資訊,決議后的配置資訊會形成相應的物件并全部保存到 Configuration 物件中,并創建DefaultSqlSessionFactory供SQl執行程序創建出頂層介面SqlSession供給用戶進行操作,

各位看官還可以嗎?喜歡的話,動動手指點個贊💗唄!!謝謝支持!
歡迎掃碼關注,原創技術文章第一時間推出

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/122178.html

標籤:其他

上一篇:ipad揚聲器

下一篇:面試官:說一下你們線上JVM是如何優化的?一不小心聊了2個小時!!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more