Spring Ioc源碼分析系列--Ioc容器註冊BeanPostProcessor後置處理器以及事件消息處理

来源:https://www.cnblogs.com/codegitz/archive/2022/05/18/16285975.html
-Advertisement-
Play Games

Spring Ioc源碼分析系列--Ioc容器註冊BeanPostProcessor後置處理器以及事件消息處理 前言 上一篇分析了BeanFactoryPostProcessor的作用,那麼這一篇繼續在refresh()方法里游蕩,相信對Spring熟悉點的朋友,在看完BeanFactoryPost ...


Spring Ioc源碼分析系列--Ioc容器註冊BeanPostProcessor後置處理器以及事件消息處理

前言

上一篇分析了BeanFactoryPostProcessor的作用,那麼這一篇繼續在refresh()方法里游蕩,相信對Spring熟悉點的朋友,在看完BeanFactoryPostProcessor後,一定會想到Spring裡面還有個BeanPostProcessor,那這個東西是什麼作用呢?下麵會進行介紹,同時由於註冊BeanPostProcessor的邏輯比較簡單,這裡會穿插一下BeanPostProcessor生效的時機和源碼邏輯,實際上這部分應該是Bean實例化出現的邏輯。

介紹完這部分之後,會介紹Spring的消息源初始化、廣播器的初始以及監聽器的初始化。這部分工作完成之後,Spring容器就會進入到Bean的創建過程,因為準備工作已經做得差不多了,容器已經準備好,接下來就是初始化Bean放進去容器裡面。

BeanFactoryPostProcessor和BeanPostProcessor之間的區別

這兩個的區別還是很顯而易見的,主要表現在應用的階段不同BeanFactoryPostProcessor是對BeanDefinition直接生效的,這更加底層,也更加原始,所以直接使用BeanFactoryPostProcessor會比較少。BeanPostProcessor是對bean實例生效的,相對於對BeanDefinition的處理,這個階段更加靠後,BeanFactoryPostProcessor階段bean是尚未初始化出來的,BeanPostProcessor處理的時候已經生成了實例對象,BeanPostProcessor會在對象的實例基礎上進行一個更進一步的加工。

不熟悉的朋友看起來可能有點抽象,那麼這裡舉一個例子吧。

BeanFactoryPostProcessor的類比:

假設你要造一個杯子,那麼杯子需要一份原材料列表,材質你可以選擇鐵、銅、金、銀等等,樣式你可以選擇圓型、方形、橢圓等等。假設開始原料選擇鐵,形狀為圓形,那麼這一份原料列表對應的就是一個BeanDefinition。原料列表出來後,沒什麼問題就會按照這一份列表去創建一個杯子。但是有時候需要一些額外的操作,例如對某些BeanDefinition進行檢查,假設有一個檢查員BeanFactoryPostProcessor去檢查每個BeanDefinition。他看到杯子的材質是鐵,覺得有失身份,於是把材料改成了金子,於是後續再去創建杯子的時候,就是個金杯了。

BeanPostProcessor的類比:

BeanPostProcessor的處理階段則要靠後,在上面杯子創建完成之後,才到了BeanPostProcessor出場。BeanPostProcessor會在實例的基礎上進行一些加工,拿杯子來舉例,上一個階段拿到的是一個粗糙的杯子,這裡會進行一些處理,例如給杯子加點花紋樣式,給杯子拋光等等。**註意這些操作都是在一個已有的杯子上進行的,但是請註意,這不是絕對的。**BeanPostProcessor除了能對Bean進行深加工外,還能直接進行Bean替換,類比來說,就是換了個杯子,偷梁換柱。Spring Aop的功能就是這樣實現的,把經過代理的Bean放了進去,替換了原有的Bean。

所以比較一下得出一個很明顯的結論:

  • BeanFactoryPostProcessor對BeanDefinition生效

  • BeanPostProcessor對bean實例生效

源碼分析

registerBeanPostProcessors(beanFactory)

話不多說,下麵繼續分析refresh()方法裡面的子方法,上一篇分析到了第五個子方法,那這篇從第六個registerBeanPostProcessors(beanFactory)開始。

跟進代碼,可以看到實現都委托給了PostProcessorRegistrationDelegate#registerBeanPostProcessors(beanFactory, this)方法。

	/**
	 * Instantiate and register all BeanPostProcessor beans,
	 * respecting explicit order if given.
	 * <p>Must be called before any instantiation of application beans.
	 *
	 * 實例化並註冊所有 BeanPostProcessor bean,如果給定順序,則按照順序排序。
	 * <p>必須在應用程式 bean 的任何實例化之前調用。
	 */
	protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
	}

繼續跟進,這個方法的邏輯也是比較簡單的,跟上篇的BeanFactoryPostProcessor註冊類似,這裡也會按照優先順序去對BeanPostProcessor進行排序然後按順序進行註冊。都是些家常套路了,可以跟著註釋去看一下。值得註意的是,這裡會額外加入兩個BeanPostProcessor,分別為BeanPostProcessorCheckerApplicationListenerDetectorBeanPostProcessorChecker主要是用來記錄一些日誌,ApplicationListenerDetector是用來檢測實現了ApplicationListener但是getBeanNamesForType()沒探測出來的漏網之魚。這裡的漏網之魚可能是一些動態註冊的bean或者一些內部類,這裡再次獲取後會放入到applicationListeners集合里。

	public static void registerBeanPostProcessors(
			ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {

		// 獲取容器中所有的 BeanPostProcessor
		String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

		// Register BeanPostProcessorChecker that logs an info message when
		// a bean is created during BeanPostProcessor instantiation, i.e. when
		// a bean is not eligible for getting processed by all BeanPostProcessors.
		int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
		// 註冊 BeanPostProcessorChecker,當 bean 不符合所有 BeanPostProcessor 處理的條件時,它會在 BeanPostProcessor 實例化期間創建 bean 時記錄一條信息消息
		beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

		// Separate between BeanPostProcessors that implement PriorityOrdered,
		// Ordered, and the rest.
		// 按照順序分類區分 BeanPostProcessor
		List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
		List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
		List<String> orderedPostProcessorNames = new ArrayList<>();
		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
		for (String ppName : postProcessorNames) {
			if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
				BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
				priorityOrderedPostProcessors.add(pp);
				if (pp instanceof MergedBeanDefinitionPostProcessor) {
					internalPostProcessors.add(pp);
				}
			}
			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
				orderedPostProcessorNames.add(ppName);
			}
			else {
				nonOrderedPostProcessorNames.add(ppName);
			}
		}

		// First, register the BeanPostProcessors that implement PriorityOrdered.
		// 首先註冊實現了 PriorityOrdered 介面的 BeanPostProcessor
		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
		registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

		// Next, register the BeanPostProcessors that implement Ordered.
		// 其次註冊實現了 Ordered 介面的 BeanPostProcessor
		List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
		for (String ppName : orderedPostProcessorNames) {
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			orderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}
		sortPostProcessors(orderedPostProcessors, beanFactory);
		registerBeanPostProcessors(beanFactory, orderedPostProcessors);

		// Now, register all regular BeanPostProcessors.
		// 現在到了註冊沒有實現上述介面的 BeanPostProcessor
		List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
		for (String ppName : nonOrderedPostProcessorNames) {
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			nonOrderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}
		registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

		// Finally, re-register all internal BeanPostProcessors.
		// 最後,重新註冊所有內部 BeanPostProcessor MergedBeanDefinitionPostProcessor。
		sortPostProcessors(internalPostProcessors, beanFactory);
		registerBeanPostProcessors(beanFactory, internalPostProcessors);

		// Re-register post-processor for detecting inner beans as ApplicationListeners,
		// moving it to the end of the processor chain (for picking up proxies etc).
		// 重新註冊用於將內部 bean 檢測為 ApplicationListeners 的後處理器,將其移動到處理器鏈的末尾(用於拾取代理等)。
		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
	}

initMessageSource()

接下來繼續進行下一步的準備工作,初始化消息源。這裡是預設使用了父類的消息源,如果沒有就初始化一個DelegatingMessageSource,這個DelegatingMessageSource會預設將所有的調用都委派到父容器的消息源去解析,如果沒有父容器的消息源,那麼它不會解析任何消息。

	/**
	 * Initialize the MessageSource.
	 * Use parent's if none defined in this context.
	 * 初始化消息源。如果沒有在此上下文中定義,則使用父容器的。
	 */
	protected void initMessageSource() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		// 返回當前容器職工是否存在 messageSource,忽略祖先容器
		if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
			this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
			// Make MessageSource aware of parent MessageSource.
			// 如果存在祖先,並且 messageSource 類型是 HierarchicalMessageSource,則獲取祖先的 messageSource 設置到當前 messageSource 里。
			if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
				HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
				if (hms.getParentMessageSource() == null) {
					// Only set parent context as parent MessageSource if no parent MessageSource
					// registered already.
					hms.setParentMessageSource(getInternalParentMessageSource());
				}
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Using MessageSource [" + this.messageSource + "]");
			}
		}
		else {
			// Use empty MessageSource to be able to accept getMessage calls.
			// 本地不存在 messageSource,使用空 MessageSource 能夠接受 getMessage 調用
			DelegatingMessageSource dms = new DelegatingMessageSource();
			dms.setParentMessageSource(getInternalParentMessageSource());
			this.messageSource = dms;
			beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
			if (logger.isTraceEnabled()) {
				logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
			}
		}
	}

initApplicationEventMulticaster()

初始化 ApplicationEventMulticaster,如果上下文中沒有定義,則使用 SimpleApplicationEventMulticaster

	/**
	 * Initialize the ApplicationEventMulticaster.
	 * Uses SimpleApplicationEventMulticaster if none defined in the context.
	 *
	 * 初始化 ApplicationEventMulticaster。
	 * 如果上下文中沒有定義,則使用 SimpleApplicationEventMulticaster。
	 *
	 * @see org.springframework.context.event.SimpleApplicationEventMulticaster
	 */
	protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		// 如果本地容器里存在 applicationEventMulticaster,直接使用本地容器里的 applicationEventMulticaster
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
			}
		}
		else {
			// 否則使用 SimpleApplicationEventMulticaster 廣播器
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
			// 將給定的單例對象添加到該工廠的單例緩存中
			beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
			if (logger.isTraceEnabled()) {
				logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
						"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
			}
		}
	}

這個事件廣播器是乾什麼的呢?其實很簡單,就是把一個事件廣播到所有的ApplicationListener上。可以看一下裡面的關鍵方法SimpleApplicationEventMulticaster#multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType),這裡就是獲取所有的listener,如果有非同步線程池,則非同步執行,否則逐個調用。

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		// 解析事件的類型,這個type會用於後續的 ListenerCacheKey 緩存 key 構建
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		// 獲取線程池
		Executor executor = getTaskExecutor();
		// 逐個廣播事件到 listener,就是將 listener 都遍歷調用一遍
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
		// 錯誤處理器,記錄任務處理期間發生的錯誤
		ErrorHandler errorHandler = getErrorHandler();
		if (errorHandler != null) {
			try {
				doInvokeListener(listener, event);
			}
			catch (Throwable err) {
				errorHandler.handleError(err);
			}
		}
		else {
			doInvokeListener(listener, event);
		}
	}

	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			// 將事件傳入listener,完成事件的監聽回調
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			// ...
		}
	}

onRefresh()

這是個空方法,交給子類實現。這裡可以用來初始化特定上下文子類中的其他特殊 bean,也是留出來的一個擴展口。

	/**
	 * Template method which can be overridden to add context-specific refresh work.
	 * Called on initialization of special beans, before instantiation of singletons.
	 *
	 * 可以重寫以添加特定於上下文的刷新工作的模板方法。在單例實例化之前調用特殊 bean 的初始化。
	 * 
	 * <p>This implementation is empty.
	 * @throws BeansException in case of errors
	 * @see #refresh()
	 */
	protected void onRefresh() throws BeansException {
		// For subclasses: do nothing by default.
	}

registerListeners()

上一步已經初始化完成了廣播器,那接下來就是檢查偵聽器並註冊它們。

事件可以按照註冊的類型進行區分,可以分為以下三種:

  • 通過addApplicationListener()手動添加進去的
  • 容器里實現了ApplicationListener介面的
  • 容器啟動早期需要的事件earlyApplicationEvents,早期事件是需要在這裡直接發佈的
	/**
	 * Add beans that implement ApplicationListener as listeners.
	 * Doesn't affect other listeners, which can be added without being beans.
	 *
	 * 添加實現 ApplicationListener 的 bean作為偵聽器。
	 * 不影響其他監聽器,可以添加而不是 bean。
	 */
	protected void registerListeners() {
		// Register statically specified listeners first.
		// 首先註冊靜態指定的監聽器,也就是通過addApplicationListener(ApplicationListener<?> listener) 註冊的listener。
		for (ApplicationListener<?> listener : getApplicationListeners()) {
			getApplicationEventMulticaster().addApplicationListener(listener);
		}

		// Do not initialize FactoryBeans here: We need to leave all regular beans
		// uninitialized to let post-processors apply to them!
		// 這裡只是獲取beanName,是為了避免初始化 bean 導致後置處理器失效
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
		// 逐個註冊listener
		for (String listenerBeanName : listenerBeanNames) {
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		}

		// Publish early application events now that we finally have a multicaster...
		// 發佈早期應用程式事件,因為我們終於有了一個廣播器......
		// 忍辱負重,早期事件存到了這裡才能進行發佈,因為之前沒有廣播器
		Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
		this.earlyApplicationEvents = null;
		if (earlyEventsToProcess != null) {
			// 逐個發佈事件
			for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
				getApplicationEventMulticaster().multicastEvent(earlyEvent);
			}
		}
	}

finishBeanFactoryInitialization(beanFactory)

準備工作已經基本完成,接下來就到了finishBeanFactoryInitialization(beanFactory)方法了。從方法名可以看到,這個方法是負責完成此上下文的 bean 工廠的初始化,初始化所有剩餘的單例 bean。可以看到這個方法開始也進行了一些準備工作,例如註冊類型裝換器、占位符處理器以及LoadTimeWeaverAware載入等。最後會調用beanFactory.preInstantiateSingletons()進行對象創建,由於這裡是比較複雜的過程,會分幾篇文章去詳細分析,這篇文章就是大概從錶面上走完refresh()方法的源碼。

	/**
	 * Finish the initialization of this context's bean factory,
	 * initializing all remaining singleton beans.
	 *
	 * 完成此上下文的 bean 工廠的初始化,初始化所有剩餘的單例 bean。
	 * 
	 */
	protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
		// Initialize conversion service for this context.
		// 初始化一個ConversionService用於類型轉換,這個ConversionService會在實例化對象的時候用到
		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.
		// 添加一個StringValueResolver,用於處理占位符,可以看到,預設情況下就是使用環境中的屬性值來替代占位符中的屬性
		if (!beanFactory.hasEmbeddedValueResolver()) {
			beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
		}

		// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
		// 創建所有的LoadTimeWeaverAware
		String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
		for (String weaverAwareName : weaverAwareNames) {
			getBean(weaverAwareName);
		}

		// Stop using the temporary ClassLoader for type matching.
		// 靜態織入完成後將臨時的類載入器設置為null,所以除了創建LoadTimeWeaverAware時可能會用到臨時類載入器,其餘情況下都為空
		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();
	}

finishRefresh()

到這裡容器已經準備好了,bean也已經實例化完成,就差最後的一些事件通知和後續的兜底處理。這裡比較重要的是會調用到所有實現了LifecycleProcessor#onRefresh()的Bean,在這裡可以讓生命周期Bean實現很多擴展。其次比較重要的是會發佈一個ContextRefreshedEvent事件,通知所有監聽器容器已經啟動完成,這裡就可以實現一些容器啟動完成後的回調或者是一些任務等,任君發揮。

	/**
	 * Finish the refresh of this context, invoking the LifecycleProcessor's
	 * onRefresh() method and publishing the
	 * {@link org.springframework.context.event.ContextRefreshedEvent}.
	 *
	 * 完成容器的刷新啟動,調用所有 LifecycleProcessor#onRefresh() 方法來發佈 ContextRefreshedEvent 事件
	 *
	 */
	protected void finishRefresh() {
		// Clear context-level resource caches (such as ASM metadata from scanning).
		// 清除容器上下文級別的資源緩存(例如ASM掃描的元數據)
		clearResourceCaches();

		// Initialize lifecycle processor for this context.
		// 初始化上下文的 lifecycle processor
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		// 首先將刷新傳播到生命周期處理器。
		getLifecycleProcessor().onRefresh();

		// Publish the final event.
		// 發佈最終事件。
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		// 參與 LiveBeansView MBean(如果處於活動狀態)。
		LiveBeansView.registerApplicationContext(this);
	}

總結

本文的重點有點分散,更像是走馬觀花,但是分散里的重點毫無疑問是理解和區分BeanFactoryPostProcessorBeanPostProcessor之間的區別,文章開頭通過一個例子去類比了一下這二者的作用階段和分別可以完成什麼工作,個人覺得還是比較貼切的,希望能夠幫助到理解。

到這裡已經基本把refresh()方法走了一遍,當然這裡看到的大部分都是一些基礎準備工作,最關鍵的Bean實例化是還沒有開始分析的,Bean的實例化會後續分好幾篇文章繼續去分析。

今天這篇文章是比較簡單的,沒有太多邏輯,基本上都是一個一個小方法,嵌套不深,因為深入的我都不寫了哈哈。

這系列寫到這裡,才完成了準備工作,接下來的Bean創建才是真正開始了重頭戲。那接下來繼續慢慢分析吧。

如果有人看到這裡,那在這裡老話重提。與君共勉,路漫漫其修遠兮,吾將上下而求索。


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

-Advertisement-
Play Games
更多相關文章
  • 前端處理二進位流數據--轉下載 導言 ​ 因業務需要,實現分類導出功能。篩選導出一定條件的數據,後端處理成Excle數據流,前端實現導出下載。 實現 方法一 ​ 將條件格式化成key=value&...文本格式,接到<a>標簽url介面之後,每當點擊導出按鈕的時候,創建一個<a>標簽,寫入介面地址+ ...
  • 本章是系列文章的第三章,介紹了基於數據流分析的一些優化方法。包括生命周期管理,可獲得表達式,常用表達式,可達性定義。本章在介紹這4中分析方法的基礎上提取出它們的通用模式。這一章形式化的內容比較多,看的時候有點燒腦,最好自己手工推導一下,要不然基本上看不懂:) 本文中的所有內容來自學習DCC888的學 ...
  • 一、吐槽 已經是凌晨12點了我還是睡不著 我所有的實體類時間用的j8的LocalDateTime 這就導致一個問題:jackson不能序列化時間,因為它不支持j8的Api,讓我添加 jackson-datatype-jsr310 解決 二、問題 如果是這樣做統一返回結果集需要 private sta ...
  • 知識回顧 解析完Bean信息的合併,可以知道Spring在實例化Bean之後,屬性填充前,對Bean進行了Bean的合併操作,這裡的操作主要做了對Bean對象標記了@Autowired、@Value、@Resource、@PostConstruct、@PreDestroy註解的欄位或者方法進行解析, ...
  • Cookied的設置會造成XSS攻擊,所以出現了防禦機制HttpOnly標誌(可選),客戶端腳本將無法訪問cookie(如果瀏覽器支持該標誌的話)。因此即使客戶端存在跨站點腳本(XSS)漏洞,瀏覽器也不會將Cookie透露給第三方。Cookie和Session後面還會設計到xss和csrf漏洞的利用... ...
  • 為了修複生產數據,需要執行一段一次性的代碼。 鑒於是spring老項目,就想到了InitializingBean。 代碼如下。服務啟動後,log里發現出現2條“一次性任務開始”。 好在裡面邏輯做了防重控制,沒有受到什麼影響。 @Slf4j @Component public class TransT ...
  • 什麼是隱藏類 隱藏類,是一種不能被其他類直接使用的類。引入隱藏類的主要目的是給框架來使用,使得框架可以在運行時生成類,並通過反射間接使用它們。可能有點抽象,不要緊,下麵我們通過一個例子來直觀的認識它! 隱藏類案例 第一步:先創建一個普通的Java類 public class JEP371Hidden ...
  • 下麵介紹的是JUC包下一些線程安全類的一些簡單使用和一些小demo。 Semaphore 信號量,即可以同時使用的線程數,tryrequire就是將信號量減一,release就是信號量+1,當等於0就會阻塞,大於零才會喚醒。 當需要控制線程訪問數量,可以使用信號量來做控制,比較簡單。 下麵是使用信號 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...