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
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...