Spring5源碼解析-Spring框架中的單例和原型bean

来源:https://www.cnblogs.com/lfs2640666960/archive/2018/08/03/9416472.html
-Advertisement-
Play Games

Spring5源碼解析-Spring框架中的單例和原型bean 最近一直有問我單例和原型bean的一些原理性問題,這裡就開一篇來說說的 通過Spring中的依賴註入極大方便了我們的開發。在xml通過<bean>定義(或者通過@Bean在配置類里定義)對象之後,然後只需簡單地使用@Autowired註 ...


Spring5源碼解析-Spring框架中的單例和原型bean

最近一直有問我單例和原型bean的一些原理性問題,這裡就開一篇來說說的

通過Spring中的依賴註入極大方便了我們的開發。在xml通過<bean>定義(或者通過@Bean在配置類里定義)對象之後,然後只需簡單地使用@Autowired註解,就可以使用由Spring上下文管理的每個對象。需要註意的是,所有這些對象在Spring中預設都是單例。

這一次我們會去討論Spring如何來管理這些定義的bean。在第一部分中,我們將講解單例和原型作用域的概念。第二部分中,我們將分析單例和原型作用域之間的依賴關係。其後說一下方法註入。最後專門對相關Spring的代碼來做下分析,具體看看bean是如何構建出來的。

Spring中的bean預設都是單身貴族

Spring使用單例設計模式來管理bean?不完全是。Singleton設計模式假定它們是由Java的類載入器管理的jvm中給定類的唯一一個實例。在Spring中,還是有點不一樣。預設情況下,它們為每個給定的org.springframework.context.ApplicationContext實例存在唯一的一個bean (有點彆扭,也就是可以有多個Spring容器,每一個容器記憶體在唯一bean實例,之前的文章中有涉及例子的)。這意味著如果你有兩個或更多上下文,所有這些上下文都由同一Java的類載入器管理(因為在同一個jvm環境中),則可能會有多個給定bean的實例。唯一需要做到的是必須在每個上下文中定義此bean。講那麼多不如代碼更有說服力:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class MultipleContextes {   public static void main(String[] args) { try { // retreive two different contexts ApplicationContext firstContext = new FileSystemXmlApplicationContext("/home/bartosz/webapp/src/main/resources/META-INF/applicationContext.xml"); ApplicationContext secondContext = new FileSystemXmlApplicationContext("/home/bartosz/webapp/src/main/resources/META-INF/applicationContext.xml");   // compare the objects from different contexts ShoppingCart firstShoppingCart = (ShoppingCart) firstContext.getBean("shoppingCart"); ShoppingCart secondShoppingCart = (ShoppingCart) secondContext.getBean("shoppingCart"); System.out.println("1. Are they the same ? " + (firstShoppingCart == secondShoppingCart));   // compare the objects from the same context ShoppingCart firstShoppingCartBis = (ShoppingCart) firstContext.getBean("shoppingCart"); System.out.println("2. Are they the same ? "+ (firstShoppingCart == firstShoppingCartBis)); } catch (Exception e) { e.printStackTrace(); } } }

通過執行此代碼,你應該得到:

1 2 1. Are they the same ? false 2. Are they the same ? true

所以你可以看到,bean只是一個上下文的單例。這就是為什麼你不應該將Spring的單例概念與設計模式中的的單例混合在一起。

但是,如果要為一個定義的bean在一個上下文內可以使用不同的實例,應該怎麼做?很簡單,你應該將此Bean配置為原型作用域:

1 2 <bean id="shoppingCart" class="com.migo.data.ShoppingCart" scope="prototype"> </bean>

現在,在運行以前的代碼之後,你可以看到如下輸出:

1 2 1. Are they the same ? false 2. Are they the same ? false

我們已經知道兩個作用域之間的區別。但在哪種情況下我們應該選擇使用單例還是原型?Singleton適用於無狀態的bean,即沒有狀態的bean。比如一個serviceDAO或者controller。他們都沒有自己的狀態(舉個簡單的例子,一個函數sin(x),這個函數本身就是無狀態的,所以我們現在喜歡的函數式編程也遵循這個理念)。而是根據傳輸的參數執行一些操作(作為HTTP請求參數)。另一方面,我們可以通過狀態bean管理一些狀態。比如購物車bean,假如它是一個單例,那麼兩個不同消費者購買的產品將被放置在同一個對象上。而如果其中一個消費者想要刪除一個產品,另一個消費者就鐵定不高興。這也就是狀態類對象應該是原型

這裡說點題外話,不能確定時間的保證,未來會出一個用Java的代碼習慣去解析vue的一些東西,內容已經總結完畢,也應用到自己的項目中了,然後得出的一些方法論,為什麼在這裡去說,就是因為vue也是遵循這個無狀態和狀態專門管理的原則的,扯遠了,接著進行下一部分。

將原型放在單例中,反之亦然

通過上面的描述,很多概念都很清楚了吧,但有時候會發生一些更複雜的情況。第一個是在原型bean中放置單例。顯然,如果註入的單例對象真的是一個單例的bean(沒有狀態),這個真的沒一點問題。想象一下,對於我們的購物車,我們需要註入產品服務。此服務只會檢查添加到購物車的產品是否庫存。由於服務沒有狀態,並且會基於在方法簽名中所傳遞的對象進行驗證,因此不存在風險。

另一方面,將原型bean放在單例中需要做更多的工作。我們不能在單例bean中通過使用自動註入(比如@Autowired註解)註入原型bean。當Spring初始化所有具有依賴關係的單例bean時,這些註入只會執行一次。這也就意味著在以下代碼,ShoppingCart的實例將始終是相同的:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Controller public class TestController { @Autowired private ShoppingCart shoppingCart;   @RequestMapping(value = "/addProduct/{productName}") public String testAdd(@PathVariable(value="productName") String productName) { Product product = new Product(); product.setName(productName); this.shoppingCart.addProduct(product); LOGGER.debug("ShoppingCart is "+this.shoppingCart); return "test"; } }

編譯此類併進行一些URL調用:http://localhost:8080/addProduct/ice%20tea,http://localhost:8080/addProduct/milk。你將看到如下輸出的順序:

1 2 3 4 // after http://localhost:8080/addProduct/ice%20tea ShoppingCart is ShoppingCart {products: [Product {ice tea}]} // after http://localhost:8080/addProduct/milk ShoppingCart is ShoppingCart {products: [Product {ice tea}, Product {milk}]}

為了在按照我們預想情況下工作(要求不一樣的ShoppingCart),我們可以通過bean工廠手動獲取ShoppingCart實例(這樣就可以再一次生成一個不一樣的ShoppingCart實例了):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Controller public class TestController { @Autowired private ApplicationContext context;   @RequestMapping(value = "/addProduct/{productName}") public String testAdd(@PathVariable(value="productName") String productName) { Product product = new Product(); product.setName(productName);   ShoppingCart shoppingCart = (ShoppingCart) context.getBean("shoppingCart"); shoppingCart.addProduct(product); LOGGER.debug("ShoppingCart is "+shoppingCart); return "test"; } }

這樣,你就可以日誌中看到,每次調用都會有新的ShoppingCart實例的生成:

1 2 3 4 // after http://localhost:8080/addProduct/ice%20tea ShoppingCart is ShoppingCart {products: [Product {ice tea}]} // after http://localhost:8080/addProduct/milk ShoppingCart is ShoppingCart {products: [Product {milk}]}

方法註入

有沒有別的方法在每次調用都會產生一個新實例?這就是接下來要說的方法註入的技術。它看起來有點像我們的手動去進行bean的查找,但更優雅。一個可以被上下文所感知(訪問應用程式上下文可以得到)的bean將負責在單例bean中生成原型bean實例:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Service("shoppingCartProvider") public class ShoppingCartProvider implements ApplicationContextAware {   private ApplicationContext context;   @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; }   public ShoppingCart getInstance() { return (ShoppingCart) context.getBean("shoppingCart"); }   }

經過上面的修改,controller這裡相應修改:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Controller public class TestController { @Autowired private ShoppingCartProvider shoppingCartProvider;   @RequestMapping(value = "/addProduct/{productName}") public String testAdd(@PathVariable(value="productName") String productName) { Product product = new Product(); product.setName(productName);   ShoppingCart shoppingCart = shoppingCartProvider.getInstance(); shoppingCart.addProduct(product); System.out.println("ShoppingCart is "+shoppingCart); return "test"; } }

也可以在XML配置文件中定義。裡面會有一個屬性引用原型bean,並允許在每次調用時創建新的實例。它可以很輕鬆地在一個bean中混合更多東西:

1 2 3 4 5 6 7 <bean id="shoppingCartProvider" class="com.migo.data.ShoppingCartProvider"> <lookup-method name="getInstance" bean="shoppingCart"> </lookup-method> </bean>   <bean id="shoppingCart" class="com.migo.data.ShoppingCart" scope="prototype"> </bean>
1 2 3 public abstract class ShoppingCartProvider { public abstract ShoppingCart getInstance(); }

Controller的代碼與實現ApplicationContextAware介面的provider的那個例子是一樣的。而區別也僅在於provider的bean定義和實現。該定義包含一個標簽查找方法。它指定必須使用哪個方法來獲取bean屬性中指定的bean的新實例。在我們的這個例子中,我們通過調用ShoppingCartProvider類的getInstance方法來尋找新的ShoppingCart的實例。需要註意的一點,類和方法都可以是抽象的。通過這樣做,你可以讓Spring生成將實現該方法並返回所需bean的子類。如果這個方法不是抽象的,Spring會重寫覆蓋它。

Spring中的Bean類

單例的源碼實現主要存在於org.springframework.beansorg.springframework.context包中。首先,從Bean包中查看BeanFactory介面。它包含兩個我們絕對感興趣的方法,可用來確定bean是單例還是原型:

  • boolean isSingleton(String name)throws NoSuchBeanDefinitionException
  • boolean isPrototype(String name)throws NoSuchBeanDefinitionException

接下來,我們來深入一下AbstractFactoryBean,從這個類的註釋可以知道它是作為“FactoryBean實現的簡單模板超類(還是直白翻譯下比較好,說預設實現也覺得不靠譜)”。它包含一個用來返回單例或創建原型bean的getObject方法的實現。原型和單例是通過createInstance方法在不同的時間段進行的。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 /** * Simple template superclass for {@link FactoryBean} implementations that * creates a singleton or a prototype object, depending on a flag. * * <p>If the "singleton" flag is {@code true} (the default), * this class will create the object that it creates exactly once * on initialization and subsequently return said singleton instance * on all calls to the {@link #getObject()} method. * * <p>Else, this class will create a new instance every time the * {@link #getObject()} method is invoked. Subclasses are responsible * for implementing the abstract {@link #createInstance()} template * method to actually create the object(s) to expose. * * @author Juergen Hoeller * @author Keith Donald * @since 1.0.2 * @see #setSingleton * @see #createInstance() */ public abstract class AbstractFactoryBean<T> implements FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean { /** * Expose the singleton instance or create a new prototype instance. * @see #createInstance() * @see #getEarlySingletonInterfaces() */ @Override public final T getObject() throws Exception { if (isSingleton()) { return (this.initialized ? this.singletonInstance : getEarlySingletonInstance()); } else { return createInstance(); } } ... /** * Template method that subclasses must override to construct * the object returned by this factory. * <p>Invoked on initialization of this FactoryBean in case of * a singleton; else, on each {@link #getObject()} call. * @return the object returned by this factory * @throws Exception if an exception occurred during object creation * @see #getObject() */ protected abstract T createInstance() throws Exception; ... }

另一個我們會感興趣的一個點是BeanDefinition介面bean如其名,它定義了一個bean屬性,例如:scope,class name,factory method name,properties或constructor arguments。

org.springframework.beans.factory.config.BeanDefinition

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * A BeanDefinition describes a bean instance, which has property values, * constructor argument values, and further information supplied by * concrete implementations. * * <p>This is just a minimal interface: The main intention is to allow a * {@link BeanFactoryPostProcessor} such as {@link PropertyPlaceholderConfigurer} * to introspect and modify property values and other bean metadata. * * @author Juergen Hoeller * @author Rob Harrop * @since 19.03.2004 * @see ConfigurableListableBeanFactory#getBeanDefinition * @see org.springframework.beans.factory.support.RootBeanDefinition * @see org.springframework.beans.factory.support.ChildBeanDefinition */ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { //限於篇幅,請自行查看源碼,能發現很多有用的東西 }

想要看到bean被初始化的位置,我們需要跳轉到context包中,更準確地說就是在AbstractApplicationContext類(這個類我們已經接觸過好多次了)中。在它的public void refresh()throws BeansException,IllegalStateException我們可以找到一些關於bean創建的片段,特別是:

  • protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory):實現org.springframework.beans.factory.config .BeanFactoryPostProcessor介面的所有bean 都被初始化和調用。這種類型bean允許修改另一個bean的屬性或構造函數參數(請看PostProcessorRegistrationDelegate的相應代碼可以知道,使用BeanFactoryPostProcessor來處理我們所要用beanFactory生成的bean,這裡可以直接把beanFactory看成是我們需要的bean即可)。但是請註意,在此階段只能修改bean定義。“正常”bean實例尚未創建。關於這塊會請參考文章Spring中的bean工廠後置處理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /** * Instantiate and invoke all registered BeanFactoryPostProcessor beans, * respecting explicit order if given. * <p>Must be called before singleton instantiation. */ protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());   // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor) if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); } }

org.springframework.context.support.PostProcessorRegistrationDelegate

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 /** * Delegate for AbstractApplicationContext's post-processor handling. * * @author Juergen Hoeller * @since 4.0 */ class PostProcessorRegistrationDelegate {   public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {   // Invoke BeanDefinitionRegistryPostProcessors first, if any. Set<String> processedBeans = new HashSet<>();   if (beanFactory instanceof BeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; List<BeanFactoryPostProcessor> regularPostProcessors = new LinkedList<>(); List<BeanDefinitionRegistryPostProcessor> registryPostProcessors = new LinkedList<>();   for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) { if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) { BeanDefinitionRegistryPostProcessor registryPostProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor; registryPostProcessor.postProcessBeanDefinitionRegistry(registry); registryPostProcessors.add(registryPostProcessor); } else { regularPostProcessors.add(postProcessor); } } ... }
  • protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory):這裡上下文實例化並調用實現了org.springframework.beans.factory.config.BeanPostProcessor介面的所有bean 。實現此介面的bean包含可以在其他bean初始化之前或之後調用的回調。因為內容比較多,關於這塊會請參考文章Spring中的bean工廠後置處理器
1 2 3 4 5 6 7 8 /** * Instantiate and invoke all registered BeanPostProcessor beans, * respecting explicit order if given. * <p>Must be called before any instantiation of application beans. */ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this); }
  • protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory):主要調用定義在org.springframework.beans.factory.config.ConfigurableListableBeanFactory介面內的preInstantiateSingletons方法。該方法的目的是實例化所有被定義為非延遲載入的bean。如果在應用程式上下文載入時遇到BeansException異常,則可能來自此方法。當bean無法創建時,它會拋出BeansException異常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 /** * Finish the initialization of this context's bean factory, * initializing all remaining singleton beans. */ protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // Initialize conversion service for this context. if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { beanFactory.setConversionService( beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); }   // Register a default embedded value resolver if no bean post-processor // (such as a PropertyPlaceholderConfigurer bean) registered any before: // at this point, primarily for resolution in annotation attribute values. if (!beanFactory.hasEmbeddedValueResolver()) { beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal)); }   // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early. String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false); for (String weaverAwareName : weaverAwareNames) { getBean(weaverAwareName); }   // Stop using the temporary ClassLoader for type matching. beanFactory.setTempClassLoader(null);   // Allow for caching all bean definition metadata, not expecting further changes. beanFactory.freezeConfiguration();   // Instantiate all remaining (non-lazy-init) singletons. beanFactory.preInstantiateSingletons(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh();   // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();   // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory);   try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory);   // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory);   // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory);   // Initialize message source for this context. initMessageSource();   // Initialize event multicaster for this context. initApplicationEventMulticaster();   // Initialize other special beans in specific context subclasses. onRefresh();   // Check for listener beans and register them. registerListeners();   // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory);   // 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(); } } }

總結

首先,我們講了單例和原型作用域之間的區別。第一個為每個容器創建一個對象,而第二個在每個請求時創建一個新的bean對象。單例和原型都可以一起交叉使用,但原型不能通過@Autowired或其他註入方式來解決。它們應該使用getBean()方法方法查找來生成新實例。最後隨意說了一說關於bean及其初始化的內容。


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

-Advertisement-
Play Games
更多相關文章
  • 經過大半年的工作的經歷,我覺得作為一個IT工作者,學習一門編程語言是非常有必要的,所以還是撿起最初的夢想,開始吧Today!!! 本人小白,給個機會,多多關照。不喜勿噴,謝謝! 第一個程式:Hello word ! 附代碼: 執行方式: 1. MDdos中執行:python c:/pycc/Hell ...
  • 生成器 我們調用一個普通的Python函數時,一般是從函數的第一行代碼開始執行,結束於return語句、異常或者函數結束(可以看作隱式的返回None)。一旦函數將控制權交還給調用者,就意味著全部結束。函數中做的所有工作以及保存在局部變數中的數據都將丟失。再次調用這個函數時,一切都將從頭創建。 對於在 ...
  • [TOC] 程式安裝 首先,我們需要在本機下載安裝 "Visual Studio Installer" ,然後通過它來進行相應 Python 組件安裝,如下圖所示 這裡需要註意一點是,由於一些圖像化的 Package 是通過 來進行實現的,所以建議讀者安裝 Python本機開發工具 ,要不然到時候有 ...
  • 自從學習Java開始一直使用的是jdk1.8,聽同事說版本不符要報錯出什麼52的錯誤 周末測試一下。java.lang.UnsupportedClassVersionError Unsupported major.minor version 52.0 出現這個錯誤是編譯時使用的版本高於運行時的版本。 ...
  • 6.1.爬取第一頁的職位信息 第一頁職位信息 6.2.爬取所有頁的職位信息 ...
  • DDOS攻擊的一些基礎知識點在這裡就不再贅述,由於Server端要控制client,所以這裡需要使用argparse,在這裡我們先規定好命令格式:#-H XXX.XXX.XXX.XXX -p xxxx -c<start|stop> -H後面接的是被攻擊主機的IP地址。 -p指定被攻擊的埠號。 -c ...
  • 異常在Java中有兩種分類:Error(OutOfMemoryError之類的我們自己程式無法處理的非常嚴重的錯誤,Java推薦不catch,讓程式隨之崩潰)、Excepiton(NullPointerException之類的並不致命的錯誤,Java覺得indicates conditions th ...
  • StringBuilder用法 StringBuilder str=new StringBuilder(); 和String用法的區別是 string 對象時恆定不變的,stringBuider對象表示的字元串是可變的。 StringBuilder 類提供了很多方法來操作字元串: 包裝類 基本數據類 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...