Spring源碼載入BeanDefinition過程

来源:https://www.cnblogs.com/chenyanbin/archive/2019/12/17/12054967.html
-Advertisement-
Play Games

本文主要講解Spring載入xml配置文件的方式,跟蹤載入BeanDefinition的全過程。 源碼分析 源碼的入口 ClassPathXmlApplicationContext構造函數 new ClassPathXmlApplicationContext(“spring.xml”)用於載入CLA ...


  本文主要講解Spring載入xml配置文件的方式,跟蹤載入BeanDefinition的全過程。

源碼分析

源碼的入口

ClassPathXmlApplicationContext構造函數

  new ClassPathXmlApplicationContext(“spring.xml”)用於載入CLASSPATH下的Spring配置文件,將配置文件傳給構造函數,然後調用類內部的另外一個重載方法。

 

 

從構造函數中,可以看到一共做了3件事

super(parent)

  super(parent)的作用是為容器設置Bean資源載入器,層層跟蹤,可知實際是由其父類AbstractApplicationContext完成設置的,parent為null,setParent(parent)就不繼續跟蹤了,這裡需要註意的是,該類繼承了DefaultResourceLoader,所以該類也作為資源載入器

AbstractApplicationContext.java

 

 

跟蹤該類this()無參構造函數進去看看

AbstractApplicationContext.java

 

 

AbstractApplicationContext.java

 

 

 PathMatchingResourcePatternResolver.java

setConfigLocations(configLocations)

  設置Bean定義資源的路徑,由其父類AbstractRefreshableConfigApplicationContext完成,resolvePath解析路徑,一直跟蹤到底層是調用PropertyPlaceholderHelper的parseStringValue完成設置的

 

 

 refresh()

  這個就是整個Spring Bean載入的核心裡面十二大步,用於刷新整個Spring上下文信息,定義了整個Spring上下文載入的流程。

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            //1、 Prepare this context for refreshing.
            prepareRefresh();

               //創建DefaultListableBeanFactory(真正生產和管理bean的容器)
               //載入BeanDefition並註冊到BeanDefitionRegistry
               //通過NamespaceHandler解析自定義標簽的功能(比如:context標簽、aop標簽、tx標簽)
            //2、 Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            //3、 Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                //4、 Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                     //實例化並調用實現了BeanFactoryPostProcessor介面的Bean
                     //比如:PropertyPlaceHolderConfigurer(context:property-placeholer)
                     //就是此處被調用的,作用是替換掉BeanDefinition中的占位符(${})中的內容
                //5、 Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                     //創建並註冊BeanPostProcessor到BeanFactory中(Bean的後置處理器)
                     //比如:AutowiredAnnotationBeanPostProcessor(實現@Autowired註解功能)
                     //      RequiredAnnotationBeanPostProcessor(實現@d註解功能)
                     //這些註冊的BeanPostProcessor
                //6、 Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                //7、 Initialize message source for this context.
                initMessageSource();

                //8、 Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                //9、 Initialize other special beans in specific context subclasses.
                onRefresh();

                //10、 Check for listener beans and register them.
                registerListeners();

                     //創建非懶載入方式的單例Bean實例(未設置屬性)
                     //填充屬性
                     //初始化實例(比如調用init-method方法)
                     //調用BeanPostProcessor(後置處理器)對實例bean進行後置處理
                //11、 Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                //12、 Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

總結:

  1. 方法加了個類鎖避免多線程同時刷新Spring上下文
  2. 的這個對象this.startupShutdownMonitor,有兩個好處
    1. refresh()方法和close()方法使this.startupShutdownMonitor保證在調用refresh()方法的時候無法使用close()方法,反之亦然,避免了衝突
    2. 使用對象鎖可以減少同步的範圍,只對不能併發的代碼塊進行加鎖,提高整體代碼運行效率
  3. refresh()函數一個模板方法,執行多個方法,而且提供了各個protected方法(預設實現),其子類可以重寫他們
  4. 模板方法模式:在一個方法中定義一個演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類(protected方法)可以在不改變演算法結構的情況下,重新定義演算法中的某些步驟。

refresh()核心調用obtainFreshBeanFactory()

  obtainFreshBeanFactory()函數調用,完成了容器初始化的最基礎的功能,Bean定義資源的Resource定位、載入解析和註冊

AbstractApplicationContext.java

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        //使用的委派模式,調用2個抽象方法,定義了obtainFreshBeanFactory的演算法骨架,實際的行為交給了子類AbstractRefreshableApplicationContext實現
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }

AbstractRefreshableApplicationContext.java

    /**
     * This implementation performs an actual refresh of this context's underlying
     * bean factory, shutting down the previous bean factory (if any) and
     * initializing a fresh bean factory for the next phase of the context's lifecycle.
     */
    @Override
    protected final void refreshBeanFactory() throws BeansException {
         //若有容器,銷毀容器中的bean,關閉容器,以此保證refresh()之後使用的是新建立起來的IoC容器
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //創建IoC容器
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            //調用載入bean定義的方法,使用了委派模式,在當前類中定義了抽象的loadBeanDefinitions方法,具體實現交給子類
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }st

  這個方法中先判斷BeanFactory是否存在,若存在,則先銷毀並關閉BeanFactory接著創建DefaultListableBeanFactory,並調用loadBeanDefinitions裝在bean使用了委派模式,在當前類中定義抽象的loadBeanDefinitions方法,具體的實現交給子類AbstractXmlApplicationContext

AbstractXmlApplicationContext.java

    /**
     * Loads the bean definitions via an XmlBeanDefinitionReader.
     * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
     * @see #initBeanDefinitionReader
     * @see #loadBeanDefinitions
     */
    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 為給定的bean工廠創建一個新的xmlbeanfinitionReader
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        //使用此上下文的bean定義讀取器配置
        // 資源載入環境
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // 允許子類提供讀取器的自定義初始化,然後繼續實際載入bean定義
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }

看下new XmlBeanDefinitionReader(beanFactory)做了哪些工作,底層初始化了BeanDefinitionRegistry=BeanDefinitionRegistry也就是this.registry = registry

XmlBeanDefinitionReader.java

    /**
     * Create new XmlBeanDefinitionReader for the given bean factory.
     * @param registry the BeanFactory to load bean definitions into,
     * in the form of a BeanDefinitionRegistry
     */
    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

AbstractBeanDefinitionReader.java

    /**
     * Create a new AbstractBeanDefinitionReader for the given bean factory.
     * <p>If the passed-in bean factory does not only implement the BeanDefinitionRegistry
     * interface but also the ResourceLoader interface, it will be used as default
     * ResourceLoader as well. This will usually be the case for
     * {@link org.springframework.context.ApplicationContext} implementations.
     * <p>If given a plain BeanDefinitionRegistry, the default ResourceLoader will be a
     * {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}.
     * <p>If the passed-in bean factory also implements {@link EnvironmentCapable} its
     * environment will be used by this reader.  Otherwise, the reader will initialize and
     * use a {@link StandardEnvironment}. All ApplicationContext implementations are
     * EnvironmentCapable, while normal BeanFactory implementations are not.
     * @param registry the BeanFactory to load bean definitions into,
     * in the form of a BeanDefinitionRegistry
     * @see #setResourceLoader
     * @see #setEnvironment
     */
    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;

        // Determine ResourceLoader to use.
        if (this.registry instanceof ResourceLoader) {
            this.resourceLoader = (ResourceLoader) this.registry;
        }
        else {
            this.resourceLoader = new PathMatchingResourcePatternResolver();
        }

        // Inherit Environment if possible
        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
        }
        else {
            this.environment = new StandardEnvironment();
        }
    }

接著看AbstractXmlApplicationContext下的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法,看最下麵一行的loadBeanDefinitions(beanDefinitionReader)

 

 AbstractXmlApplicationContext.java

    /**
     * Load the bean definitions with the given XmlBeanDefinitionReader.
     * <p>The lifecycle of the bean factory is handled by the {@link #refreshBeanFactory}
     * method; hence this method is just supposed to load and/or register bean definitions.
     * @param reader the XmlBeanDefinitionReader to use
     * @throws BeansException in case of bean registration errors
     * @throws IOException if the required XML document isn't found
     * @see #refreshBeanFactory
     * @see #getConfigLocations
     * @see #getResources
     * @see #getResourcePatternResolver
     */
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }

接著跟蹤第一個reader.loadBeanDefinitions(configResources)

AbstractBeanDefinitionReader.java

    @Override
    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        Assert.notNull(resources, "Resource array must not be null");
        int counter = 0;
        for (Resource resource : resources) {
            counter += loadBeanDefinitions(resource);
        }
        return counter;
    }

開始迴圈載入loadBeanDefinitions,繼續跟蹤loadBeanDefinitions方法

XmlBeanDefinitionReader.java

    /**
     * Load bean definitions from the specified XML file.
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     */
    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

繼續跟蹤loadBeanDefinitions(new EncodedResource(resource))

XmlBeanDefinitionReader.java

    /**
     * Load bean definitions from the specified XML file.
     * @param encodedResource the resource descriptor for the XML file,
     * allowing to specify an encoding to use for parsing the file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     */
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

利用currentResources.add(encodedResource)用set判斷,如果重覆載入資源就拋出異常,繼續跟蹤doLoadBeanDefinitions(inputSource, encodedResource.getResource())

XmlBeanDefinitionReader.java

    /**
     * Actually load bean definitions from the specified XML file.
     * @param inputSource the SAX InputSource to read from
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     * @see #doLoadDocument
     * @see #registerBeanDefinitions
     */
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            Document doc = doLoadDocument(inputSource, resource);
            return registerBeanDefinitions(doc, resource);
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (SAXParseException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
        }
        catch (SAXException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "XML document from " + resource + " is invalid", ex);
        }
        catch (ParserConfigurationException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Parser configuration exception parsing XML from " + resource, ex);
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "IOException parsing XML document from " + resource, ex);
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Unexpected exception parsing XML document from " + resource, ex);
        }
    }

doLoadDocument(inputSource, resource)將xml解析成org.w3c.dom,具體底層如何實現,自行跟蹤,主要看registerBeanDefinitions(doc, resource)

XmlBeanDefinitionReader.java

    /**
     * Register the bean definitions contained in the given DOM document.
     * Called by {@code loadBeanDefinitions}.
     * <p>Creates a new instance of the parser class and invokes
     * {@code registerBeanDefinitions} on it.
     * @param doc the DOM document
     * @param resource the resource descriptor (for context information)
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of parsing errors
     * @see #loadBeanDefinitions
     * @see #setDocumentReaderClass
     * @see BeanDefinitionDocumentReader#registerBeanDefinitions
     */
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

繼續跟蹤documentReader.registerBeanDefinitions(doc, createReaderContext(resource))

DefaultBeanDefinitionDocumentReader.java

    /**
     * This implementation parses bean definitions according to the "spring-beans" XSD
     * (or DTD, historically).
     * <p>Opens a DOM Document; then initializes the default settings
     * specified at the {@code <beans/>} level; then parses the contained bean definitions.
     */
    @Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        doRegisterBeanDefinitions(root);
    }

繼續跟蹤doRegisterBeanDefinitions(root)

DefaultBeanDefinitionDocumentReader.java

    /**
     * Register each bean definition within the given root {@code <beans/>} element.
     */
    protected void doRegisterBeanDefinitions(Element root) {
        // Any nested <beans> elements will cause recursion in this method. In
        // order to propagate and preserve <beans> default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        BeanDefinitionParserDelegate parent = this.delegate;
        //初始化bean預設的解析器BeanDefinitionParserDelegate
        this.delegate = createDelegate(getReaderContext(), root, parent);

        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching: " + getReaderContext().getResource());
                    }
                    return;
                }
            }
        }

        preProcessXml(root);
        //解析dom
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }

createDelegate(getReaderContext(), root, parent)初始化bean預設的解析器,BeanDefinitionParserDelegate開始解析dom,前面各有一個預留的空方法,方便以後版本擴展,繼續跟蹤parseBeanDefinitions(root, this.delegate)

DefaultBeanDefinitionDocumentReader.java

    /**
     * Parse the elements at the root level in the document:
     * "import", "alias", "bean".
     * @param root the DOM root element of the document
     */
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

獲取節點的命名空間,判斷是不是spring預設的,是的話就執行parseDefaultElement(ele, delegate),不是的話,就執行delegate.parseCustomElement(root),跟蹤parseDefaultElement(ele, delegate)

DefaultBeanDefinitionDocumentReader.java

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { //import
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { //alias
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { //bean
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { //beans
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }

標簽分別是import、alias、bean、beans,至此BeanDefinition載入完成,這就是refresh()方法中的

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 之前的aop是通過手動創建代理類來進行通知的,但是在日常開發中,我們並不願意在代碼中硬編碼這些代理類,我們更願意使用DI和IOC來管理aop代理類。Spring為我們提供了以下方式來使用aop框架 一、以聲明的方式配置AOP(就是使用xml配置文件) 1.使用ProxyFactoryBean的方式: ...
  • tcp傳輸的數據是以流的形式傳輸的,因此就沒有辦法判斷到哪裡結束算是自己的一個消息,這樣就會出現粘包問題,多個包粘在一起了 可以使用這樣一個自定義的形式來解決,一個消息分為 head+body head包括數據的長度和數據編號 , 長度和編號都是uint32類型 也就是32位 占有4個位元組 , 總共 ...
  • Shiro是一個功能強大且易於使用的Java安全框架,主要功能有身份驗證、授權、加密和會話管理。 ...
  • 一、初識socket socket(套接字)起源於20世紀70年代加利福尼亞大學伯克利分校版本的Unix,即人們所說的BSDUnix。因此,有時人們也把套接字稱為“伯克利套接字”或“BSD套接字”。一開始,套接字被設計用在同一臺主機上多個應用程式之間的通訊。這也被稱進程間通訊,或IPC。socket ...
  • 狀態模式可以看做是在運行時改變對象行為的一種方式。狀態模式允許對象在其內部狀態變化時改變其行為,此時感覺就像對象本身已經改變了一樣。 參與者: State介面:State基類,定義不同狀態共同需要執行的介面。 ConcreteSate對象:State基類的子類,不同狀態的可以在子類介面中實現不同的操 ...
  • 一、概述 本篇介紹自適應擴展,方法getAdaptiveExtension()的實現。ExtensionLoader類本身很多功能也使用到了自適應擴展。包括ExtensionFactory擴展。 通俗的講,自適應擴展要實現的邏輯是:調用擴展點的方法時,自動判斷要調用那個擴展點實現類的方法。我們知道, ...
  • softmax函數的作用 對於分類方面,softmax函數的作用是從樣本值計算得到該樣本屬於各個類別的概率大小。例如手寫數字識別,softmax模型從給定的手寫體圖片像素值得出這張圖片為數字0~9的概率值,這些概率值之和為1。預測的結果取最大的概率表示的數字作為這張圖片的分類。 可以從下麵這張圖理解 ...
  • ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...