Spring 迴圈引用(三)源碼深入分析版

来源:https://www.cnblogs.com/burg-xun/archive/2020/05/10/12865205.html
-Advertisement-
Play Games

@ "TOC" 前言 關於Spring 迴圈引用 網上的分析文章很多,寫的水平良莠不齊,雖然看完了 知道怎麼個回事 但是過段時間還是忘記了,主要本人沒過目不忘的本領哈,但是只要記住主要的點就好了 但是如果你自己想更深入的瞭解,還是要自己去看源碼分析一波,因為別人分析的時候,有些知識點你是get不到的 ...


@目錄

前言

關於Spring 迴圈引用 網上的分析文章很多,寫的水平良莠不齊,雖然看完了 知道怎麼個回事 但是過段時間還是忘記了,主要本人沒過目不忘的本領哈,但是只要記住主要的點就好了

但是如果你自己想更深入的瞭解,還是要自己去看源碼分析一波,因為別人分析的時候,有些知識點你是get不到的,只有當自己走進源碼去看的時候,才有get到更多的!比如網上很多文章都分析Springs是怎麼解決迴圈依賴的 但是為什麼只有單類的才可以,Prototype的就不行呢,在哪裡不行,或者說構造器的註入為什麼也不可以,最後如果解決迴圈依賴,或者說 怎麼去換中寫法去解決問題。

紙上得來終覺淺 絕知此事要躬行! 這句話獻給正在讀文章的你,看完記得點贊,還有就是自己去下載Spring 源碼 去看看

正文

OK,進入正文,當然上面也不是廢話啦,Spring 的迴圈引用 我想讀者們應該知道,不知道的話,算了 來個code把!

@Component
public class CycleTestServiceA {  
  private CycleTestServiceB b;
  public void setB(CycleTestServiceB b) {
    this.b = b;
  }
}

@Component
public class CycleTestServiceB {  
  private CycleTestServiceA a;
  public void setA(CycleTestServiceA a) {
    this.a = a;
  }
}

上面的 代碼 就是一個普通的set註入的方式,A裡面依賴B,B裡面依賴A,這樣就導致了迴圈依賴,Component預設是Singleton的

分析

我們從Spring Beanc創建開始作為入口,在Spring IoC 容器中一個完整的Bean 要進過實例化 和初始化的階段

Spring Bean 實例化就getBean的過程

那我們接進入源碼去看下getBean的過程

doGetBean

getBean方法時 BeanFactory 介面的方法 他的實現類有很多,我們跟進去他的抽象實現類org/springframework/beans/factory/support/AbstractBeanFactory.java 類,其實都是調用了doGetBean方法

下麵是我截取的核心代碼

   protected <T> T doGetBean(
   		final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
   		throws BeansException {

   	final String beanName = transformedBeanName(name);
   	Object bean;

      	/*
       * 檢測是否 有緩存對象  這個方法時處理迴圈依賴的關鍵入口
       * 記住這個的代碼 我還會回來的
      	* */
   	Object sharedInstance = getSingleton(beanName);
   	if (sharedInstance != null && args == null) {
   		if (logger.isDebugEnabled()) {
   			if (isSingletonCurrentlyInCreation(beanName)) {
   				logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
   						"' that is not fully initialized yet - a consequence of a circular reference");
   			}
   			else {
   				logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
   			}
   		}
   		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   	}
   	else {
        	/*
   		*Prototype bean 是否在創建當中 如果存在 說明產生了迴圈依賴  處理Bean 迴圈依賴的地方
   		*這個地方就是為什麼Scope 是Prototype的時候 會報迴圈依賴的錯誤,慢慢看 後面會解釋
   		* */
   		if (isPrototypeCurrentlyInCreation(beanName)) {
   			throw new BeanCurrentlyInCreationException(beanName);
   		}

   	    ...

   		if (!typeCheckOnly) {
   			markBeanAsCreated(beanName);//這個方法就是把當前的bean 加入到alreadyCreated的set集合中 後面有些判斷需要
   		}

   		try {
   		    ...
   			
   			/*
   			* 獲取Bean 的依賴項 這邊的依賴 是我們在xml 有時候可以配置的depends-on的依賴 和我們本次講的迴圈依賴不是同一個
   			* 我特別說明下
   			* */
   			String[] dependsOn = mbd.getDependsOn();
   			if (dependsOn != null) {
   				for (String dep : dependsOn) {
   				  //註冊依賴 創建Bean 等
   				}
   			}

   			/*
   			* 如果是單列 創建createBean  記住這個的代碼 我還會回來的
   			* */
   			if (mbd.isSingleton()) {
   				sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
   					@Override
   					public Object getObject() throws BeansException {
   						try {
   							return createBean(beanName, mbd, args);
   						}
   						catch (BeansException ex) {
   						  ...
   						}
   					}
   				});
   				bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
   			}

   			/*
   			* Prototype對象  
   			* */
   			else if (mbd.isPrototype()) {
   				// It's a prototype -> create a new instance.
   				Object prototypeInstance = null;
   				try {
   					beforePrototypeCreation(beanName);
   					prototypeInstance = createBean(beanName, mbd, args);
   				}
   				finally {
   					afterPrototypeCreation(beanName);
   				}
   				bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
   			}
   			/*
   			* 不是Singleton也不是Prototype,可能是自定義scope的對象
   			* */
   			else {
   			   ...
   			}
   		}
   	}
       ...
   	return (T) bean;
   }

上面是dogetBean()的核心方法

為什麼Prototype不可以

帶著這個問題 我們可以從上面的代碼中 看下 Spring在處理麽Prototype的時候 有2個方法beforePrototypeCreation(),afterPrototypeCreation(),
上下代碼

/** Names of beans that are currently in creation */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
			new NamedThreadLocal<Object>("Prototype beans currently in creation");

    protected void beforePrototypeCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		if (curVal == null) {
			this.prototypesCurrentlyInCreation.set(beanName);
		}
		else if (curVal instanceof String) {
			Set<String> beanNameSet = new HashSet<String>(2);
			beanNameSet.add((String) curVal);
			beanNameSet.add(beanName);
			this.prototypesCurrentlyInCreation.set(beanNameSet);
		}
		else {
			Set<String> beanNameSet = (Set<String>) curVal;
			beanNameSet.add(beanName);
		}
	}
	
	protected void afterPrototypeCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		if (curVal instanceof String) {
			this.prototypesCurrentlyInCreation.remove();
		}
		else if (curVal instanceof Set) {
			Set<String> beanNameSet = (Set<String>) curVal;
			beanNameSet.remove(beanName);
			if (beanNameSet.isEmpty()) {
				this.prototypesCurrentlyInCreation.remove();
			}
		}
	}

上面的代碼 我相信小伙伴都能看的懂,就是用一個set集合存儲當前正在創建的Bean的BeanName,而且是用ThreadLocal去存儲Set集合的 ThreadLocal是每個線程私有的。看到這個 我們再把目光往代碼上面看一看 isPrototypeCurrentlyInCreation這個方法的判斷

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		return (curVal != null &&
				(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
	}

看到了麽 這邊就是用這個ThreadLocal裡面的set集合去判斷的,為什麼用ThreadLocal想下,你想呀,A依賴B,而B依賴A,AB都是Prototype的,A創建的時候 A會加入到這個set集合中,然後A去填充實例的時候,因為要依賴B,所以去getB,發現B又依賴A,這個時候有要getA,你看 當執行到 最上面的判斷isPrototypeCurrentlyInCreation的時候,是不報了迴圈引用的錯,因為A已經在prototypesCurrentlyInCreation的Set集合中了,因為整個流程一定是一個線程走下去的,所以存入ThreadLocal中,一點問題沒有,而且還不受其他線程影響~

createBean

不管是哪種Scope 都是要調用createBean方法的,我們跟進去代碼 發現唯一重寫的實現在org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java 中
我們進入代碼看下

    protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
		RootBeanDefinition mbdToUse = mbd;
	    ...
		try {
			// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
			// 該函數的作用是給 BeanPostProcessors 後置處理器返回一個代理對象的機會
			// 這裡是實現AOP處理的重要地方
			// AOP是通過BeanPostProcessor機制實現的,而介面InstantiationAwareBeanPostProcessor是實現代理的重點
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
			if (bean != null) {
				return bean;
			}
		}
		...

		/*
		* 後置處理器 沒有返回有效的bean 就創建
		* */
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		if (logger.isDebugEnabled()) {
			logger.debug("Finished creating instance of bean '" + beanName + "'");
		}
		return beanInstance;
	}

這邊 我看到一句英文註釋,都沒捨得替換中文,Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. 哈哈 給後置處理器一個返回代理bean的機會,這邊就是Spring 中實現AOP的重點,動態代理 其實就是使用後置處理器 替換了target Bean 的實例,從而達到代理的作用,這個以後聊到AOP 的時候在慢慢聊吧!這個最核心的代碼還在再doCreateBean中,繼續跟進

doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
			throws BeanCreationException {

        // Instantiate the bean.
		BeanWrapper instanceWrapper = null;//BeanWrapper 是Bean 的包裝類  方便對Bean 實例的操作
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
		Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
		mbd.resolvedTargetType = beanType;

		....
		
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));//滿足三個條件  單列  運行迴圈引用  bean 是否正在創建中
		if (earlySingletonExposure) {
			addSingletonFactory(beanName, new ObjectFactory<Object>() {
				@Override
				public Object getObject() throws BeansException {
					return getEarlyBeanReference(beanName, mbd, bean);//提前暴露引用  獲取早期的引用
				}
			});
		}

		// Initialize the bean instance. 初始化Bean 實例
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);//填充Bean
			if (exposedObject != null) {
				exposedObject = initializeBean(beanName, exposedObject, mbd);//執行初始化Bean裡面的方法
			}
		}
		...

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
			   /*
			   *這邊其實還是做了一個判斷,exposedObject是經過了 initializeBean方法方法的 
			   *而bean還是那個提前暴露的Bean,
			   *為什麼要做這個判斷你,是因為exposedObject經過了initializeBean裡面的後置處理器的修改 可能Object 已經改變了 
			   **/
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					/*
					*有興趣的可以根據到上面的每一個方法看下 ,這邊就是判斷如果提前暴露的bean已經和在後置處理器裡面修改了並且不一樣了,就拋出異常,因為提前暴露的Bean 可能作為了另外的bean的依賴 這樣就會導致單類的bean在容器中有2個實例的出現,這是非法的!
					*/
					if (!actualDependentBeans.isEmpty()) {
					  //拋出一個異常 由於很多文字我就刪掉了
					}
				}
			}
		}
	   ...
		return exposedObject;
	}

earlySingletonExposure這個主要關註的是earlySingletonExposure 這邊的代碼,這個就是在Bean 實例化完成後,開始填充屬性之間發的代碼
earlySingletonExposure為true 要滿足三個條件

  • 單類
  • 允許迴圈引用
  • 當時單類正在創建中

前面2個可以理解 那最後一個又是什麼呢?話不多說 進入方法看下

private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
public boolean isSingletonCurrentlyInCreation(String beanName) {
		return this.singletonsCurrentlyInCreation.contains(beanName);
	}

這個方法 一看很簡答 就是判斷當前的bean是否在singletonsCurrentlyInCreation的set集合中 那這個集合又是什麼時候加入的呢?帶著這個想法 我又重頭掃描了一篇代碼 還記的org/springframework/beans/factory/support/AbstractBeanFactory.java代碼中的doGetBean()方法裡面Singleton的Bean 在創建Instance的時候是調用了getSingleton方法麽,不清楚的話 可以往上看下

getEarlyBeanReference

這個方法 是 addSingletonFactory 方法 構建ObjectFactory的參數的時候 裡面返回使用方法
看下代碼:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
					if (exposedObject == null) {
						return null;
					}
				}
			}
		}
		return exposedObject;
	}

裡面最主要的就是看下getEarlyBeanReference方法 這個方法時SmartInstantiationAwareBeanPostProcessor裡面的方法,他的實現有2個 一個是org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java 還有一個是動態代理使用的 我就不列舉了,

public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		return bean;
	}

看了下 其實InstantiationAwareBeanPostProcessorAdapter的重寫就是 返回了當前的bean 沒有做任何操作。這邊其實就是做了一個引用的保存。

getSingleton

代碼位於org/springframework/beans/factory/support/DefaultListableBeanFactory.java中

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
			    ...
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
			    ...
				try {
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				finally {
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return (singletonObject != NULL_OBJECT ? singletonObject : null);
		}
	}

這個方法其所就是SingletonBean的核心創建流程

beforeSingletonCreation

protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

當我看到這個singletonsCurrentlyInCreation.add的時候 我很欣慰 因為我之前的問題解決了 就是這個方法把Bean 放入到之前的singletonsCurrentlyInCreation的集合中的

singletonFactory.getObject

這個應該都很清楚了 就是我們方法傳入的匿名的ObjectFactory對象,當之前getObject的時候 才會執行我們剛纔的看的createBean方法

afterSingletonCreation

protected void afterSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}

看下afterSingletonCreation方法裡面的東西也很簡單,就是從singletonsCurrentlyInCreation集合中移除

addSingleton

先看下代碼

protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
			this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
			this.singletonFactories.remove(beanName);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}

看到 這邊 就不得表介紹下三級緩存了

  • singletonObjects 實例化 初始化都完成的bean 緩存
  • earlySingletonObjects 可提前引用的 Bean 緩存,這裡面的Bean 是一個非完整的bean,屬性填充 後置處理器都未執行的bean
  • singletonFactories 單類bean的創建工廠函數對象

說道這裡 我們就清楚了 這個方法其所就是在二級緩存和三級緩存中刪除當前的Bean,把當前的Bean 放入到一級緩存中,因為到了這一步 bean 的實例化,屬性填充,後置處理器執行,初始化等方法都已經執行了。

addSingletonFactory

這個方法 哪裡用的呢 那我們有要回到上面的代碼doCreateBean中 當earlySingletonExposure為true的時候 會調用這個方法addSingletonFactory
這個方法就是 當前的Bean可以提前引用的話執行的方法
看下代碼也很簡答

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

看下這個方法 說白了就是往三級緩存裡面存放bean的ObjectFactory對象 這個地方也是處理迴圈引用的關鍵,這個時候Bean 剛剛進行了實例化 還沒有進行bean的屬性填充和初始化等一些列方法

那怎麼去解決提前引用的呢?可以看下ObjectFactory返回的是getEarlyBeanReference對象

getSingleton(beanName)

這個方法是在doGetBean方法中 從緩存中獲取Bean 對象的方法 這個方法很關鍵 是處理迴圈依賴的入口,那我們跟進去看下方法

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return (singletonObject != NULL_OBJECT ? singletonObject : null);
	}

最終調用的方法如上,allowEarlyReference是為true的,我們還是用最上面的ServiceA和ServiceB 為例,第一次ServiceA 進入的時候 是沒法進入下麵的判斷的 應為當前ServiceA不在SingletonCurrentlyInCreation中,但是當第二次進來,第二次是什麼時候呢,就是在填充ServiceB的時候 需要依賴 ServiceB,這個時候ServiceB也要執行getBean的流程,發現又依賴ServiceA,這個時候 ServiceA就是在SingletonCurrentlyInCreation的集合中了,而且在三級緩存中,這個時候會進行判斷條件裡面的方法,先找一級緩存,找不到就找二級緩存,最後找三級緩存,然後將取出三級緩存裡面的ObjectFactory執行getObject方法 就是獲取我們上面提到的提前引用的bean,最後將bean 放入到二級緩存,從三級緩存中移除~

核心說明

看完了 上面的一推 也許很懵逼,可能也是我文字組織能力差,只能以後慢慢改變

緩存的說明

上面涉及到幾個緩存 我在邊在重寫描述一下

名稱 類型 使用說明 所屬類
singletonObjects Map<String, Object> 實例化 初始化都完成的bean的緩存 DefaultSingletonBeanRegistry.java
earlySingletonObjects Map<String, Object> 可提前引用的 Bean 緩存,這裡面的Bean 是一個非完整的bean,屬性填充 後置處理器都未執行的bean DefaultSingletonBeanRegistry.java
singletonFactories Map<String, ObjectFactory<?>> 單類bean的創建工廠函數對象 DefaultSingletonBeanRegistry.java
singletonsCurrentlyInCreation Set 馬上要創建的單類Bean的集合 DefaultSingletonBeanRegistry.java
prototypesCurrentlyInCreation ThreadLocal object 是一個Set 馬上要創建的prototype的Bean的集合 AbstractBeanFactory.java
alreadyCreated Set 至少創建過一次的Bean 在提前暴露的bean修改了導致不一致時 判斷會用到 AbstractBeanFactory.java

執行流程圖

最終我還是用一個方法執行的流程圖 來描述下 迴圈依賴的處理

執行流程圖1
執行流程圖2

構造器的註入解決

那麼為什麼構造器的註入方式不行呢?原因是因為 Bean在實例化階段的時候createBeanInstance的時候就會去創建依賴的B,這樣的話A根本就走不到提前暴露的代碼塊,所以會報一個迴圈引用的錯誤,報錯的地方就是構造函數參數bean 創建的地方,自己可以寫個demo,調試下 在哪一步報錯,博主可是看了半天 才找到,哈哈!

解決方法

關於如果解決構造器的迴圈註入
https://www.baeldung.com/circular-dependencies-in-spring
這是一篇外國博文,小伙伴們可以看下

  • 使用懶載入
  • 修改使用setter註入的方式
  • 使用PostConstruct註解
  • InitializingBean 後置處理器的方式

總結

Spring 處理迴圈依賴的核心就是 三級緩存,讓Bean 提前暴露出來,可以提前引用,讓互相依賴的Bean 可以流程上執行下去,從而解決了迴圈依賴的問題

最後的最後 還是自己對照源碼 自己理解一遍,我相信一定會加深你的理解,一定會有收穫

碼字不易,花了一個周末的時間,各位看官喜歡的話點個贊,鼓勵下博主,繼續創造,多謝~


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

-Advertisement-
Play Games
更多相關文章
  • 導語:JavaScript定時器是window的一個對象介面,並不是JavaScript的一部分,它的功能是由瀏覽器實現的,在不同瀏覽器之間會有所不同。定時器也可以由node.js運行時本身實現。 幾周前我在推特上發佈了這樣一個面試問題: JavaScript面試問題: 在哪裡可以找到setTime ...
  • RefreshIndicator RefreshIndicator是Material風格的下拉刷新組件。 基本用法如下: var _list = [1, 2, 3, 4, 5]; RefreshIndicator( onRefresh: () async { setState(() { _list. ...
  • 文章以河南省為例 一、先下載eacherts相關js文件(echarts.min.js)和echarts使用到的地圖插件(map) ecarts.min.js在echarts官網下載,map插件下載地址:https://github.com/zhxiangfei/echarts-map (包含全國、 ...
  • (一)關於C/S、B/S,HTTP協議 1.1-C/S結構 C/S結構(伺服器-客戶機),即Client-Server結構。C/S結構通常採取兩層結構。伺服器負責數據的管理,客戶機負責完成與用戶的交互任務。 客戶機通過區域網與伺服器相連,接受用戶的請求,並通過網路向伺服器提出請求,對資料庫進行操作。 ...
  • 實踐內容 從 MariaDB 一張表內讀 10 萬條記錄,經處理後寫到 MongoDB 。 具體實現 1、新建 Spring Boot 應用,依賴如下: 2、創建一張表,並生成 10 萬條數據 3、創建 Person 類 4、創建一個中間處理器 5、創建 ,用戶資料庫映射 6、創建任務完成的監聽 7 ...
  • 一、可變長參數 package com.bjpowernode.java_learning; import java.util.Date; ​ public class D114_1_VariableLengthParameter { public static void main(String[] ...
  • 1. 選用捲積之前填充(強烈建議) 小生非常推薦大家不再使用捲積所帶的填充方式,雖然那種方式簡單,但缺陷太多。① 不能根據自己的需要來決定上與下填充不等的邊界,左右填充不等的邊界;② 邊界填充零容易出現偽影的情況,對實驗效果影響比較大。將捲積中的Padding方式換為捲積前Padding效果會更佳, ...
  • 變數只能有一次定義:定義聲明(定義)、引用聲明(聲明) 引用聲明: 關鍵字 extern 不初始化(否則變為定義,分配記憶體) 註意: 一個文件定義後,其他文件中使用須用 extern ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...